mirror of
https://github.com/joelhooks/joelclaw.git
synced 2026-09-19 01:24:04 +08:00
809 lines
35 KiB
Markdown
809 lines
35 KiB
Markdown
# Workloads
|
||
|
||
Canonical operator/design doc for **ADR-0217 Phase 4** as currently shipped.
|
||
|
||
This document defines the canonical vocabulary, schema, and front door for **agent-first coding/repo workloads** in joelclaw, plus the proven runtime behavior as of **2026-03-17**. When design intent and shipped runtime behavior diverge, shipped truth wins.
|
||
|
||
## Status
|
||
|
||
- **Canonical for planning:** yes
|
||
- **Shipped front door:** `joelclaw workload plan`, `joelclaw workload dispatch`, `joelclaw workload run`, and `joelclaw workload sandboxes ...`
|
||
- **Still planned:** `joelclaw workload status|explain|cancel`
|
||
- **Current use:** planning, saved plan artifacts, dispatch/handoff contracts, queue-backed runtime admission, and real `shell`/`infer` stage execution in the `restate-worker` pod
|
||
|
||
Do not pretend the whole workload command family already ships. `plan`, `dispatch`, `run`, and `sandboxes` are real right now; `status|explain|cancel` are still not. DAG completion is still poll-based because no finish event is emitted to the gateway yet.
|
||
|
||
### Current runtime truth (as of 2026-03-17)
|
||
|
||
- Proven pipeline: `joelclaw workload plan` → Redis queue → Restate `dagOrchestrator` → `dagWorker` → execution
|
||
- `shell` handler: ✅ runs real commands inside the `restate-worker` pod. Git clone, pi agent edits, git commit, and git push are proven.
|
||
- `infer` handler: ✅ runs `pi -p` inside the pod for research, review, planning, and analysis.
|
||
- `microvm` handler: ⚠️ Firecracker v1.15.0 boots/restores inside the pod via `/dev/kvm` with ~9ms snapshot restore, but the exec-in-VM workspace protocol is not wired yet.
|
||
- Multi-stage DAG with `dependsOn`: ✅ proven across 3-5 stage pipelines.
|
||
- Stage outputs: ✅ upstream outputs can flow into downstream stages via `{{nodeId}}` interpolation.
|
||
- Explicit stage DAGs from file: ✅ `--stages-from` validates duplicate ids, unknown deps, self-deps, and cycles before runtime admission; critical path and phase grouping are calculated.
|
||
- `restate-worker` image: ✅ pi 0.58.4, 76 skills, Firecracker, `/dev/kvm`, git push auth from a k8s secret, and pi auth mounted from the host so sessions stay fresh.
|
||
- Autonomous codegen: ✅ pi agent mode can write files directly via tools inside `shell` stages, then commit and push.
|
||
- Performance truth: ✅ `/app/repo-cache` cuts workspace setup to ~200ms vs ~3s fresh clone fallback. `dagWorker` inactivity timeout is 15m and hard abort is 30m; the worker heartbeat pi extension keeps active runs alive.
|
||
|
||
### Known gaps
|
||
|
||
- `microvm` cannot execute stage commands inside the guest yet
|
||
- no gateway completion notification when a DAG finishes; the operator must poll
|
||
- large-file pi agent edits can take 3-5 minutes even when successful
|
||
|
||
## Why this exists
|
||
|
||
The runtime substrate got legible before the workload model did.
|
||
|
||
That was backwards.
|
||
|
||
If an agent asks:
|
||
|
||
> how should I run this coding task?
|
||
|
||
it should not need to learn Restate, Redis queue families, or sandbox backend trivia first.
|
||
|
||
The point of this doc is to give agents one stable way to describe:
|
||
|
||
- what the work is
|
||
- how it should be shaped
|
||
- what proof/artifacts matter
|
||
- how handoffs should work
|
||
- why a given execution mode was chosen
|
||
|
||
## The workload stack
|
||
|
||
For coding/repo work, think in this order:
|
||
|
||
1. **Joel steering** — what outcome matters
|
||
2. **workload request** — the structured description of the task
|
||
3. **shape selection** — `serial`, `parallel`, `chained`, or `auto`
|
||
4. **execution mode** — `inline`, `durable`, `sandbox`, `loop`, or `blocked`
|
||
5. **backend selection** — host, local sandbox, k8s sandbox, queue/restate, etc.
|
||
6. **handoff contract** — what the next worker gets
|
||
|
||
Substrate comes last.
|
||
|
||
## Pi sessions in the model
|
||
|
||
A pi session is the default **operator control surface** for workload planning.
|
||
|
||
Use a pi session to:
|
||
|
||
- capture Joel steering
|
||
- decide the workload shape
|
||
- produce or refine the structured request
|
||
- dispatch downstream workers if needed
|
||
- synthesize results back into one answer
|
||
|
||
A pi session is usually the **planner/integrator**, not necessarily the worker that mutates code.
|
||
|
||
Typical split:
|
||
|
||
- **pi session** — operator conversation, planning, synthesis, coordination
|
||
- **codex/worker session** — focused implementation stage
|
||
- **clawmail** — reservations and baton passing
|
||
|
||
## Canonical vocabulary
|
||
|
||
### Workload kinds
|
||
|
||
Use one of these for coding/repo work unless the task clearly needs a new class:
|
||
|
||
| kind | Use for |
|
||
| ------------------------ | ------------------------------------------------------------- |
|
||
| `repo.patch` | local bugfix or targeted code change |
|
||
| `repo.refactor` | multi-file code reshaping with regression risk |
|
||
| `repo.docs` | docs / ADR / truth-grooming work |
|
||
| `repo.review` | review, verification, or audit without primary implementation |
|
||
| `research.spike` | bounded investigation or comparison work |
|
||
| `runtime.proof` | canary, soak, or live proof windows |
|
||
| `cross-repo.integration` | work spanning multiple repos or an external repo bridge |
|
||
|
||
### Shapes
|
||
|
||
| shape | Meaning |
|
||
| ---------- | ----------------------------------------------------- |
|
||
| `auto` | planner chooses |
|
||
| `serial` | ordered dependent stages |
|
||
| `parallel` | independent branches with later synthesis |
|
||
| `chained` | stage-specialized flow with explicit artifact handoff |
|
||
|
||
### Execution modes
|
||
|
||
| mode | Meaning |
|
||
| --------- | ---------------------------------------------------- |
|
||
| `inline` | one session can do it directly |
|
||
| `durable` | should run through a tracked background/durable path |
|
||
| `sandbox` | isolate side effects or repo mutation |
|
||
| `loop` | repeated autonomous coding cycle is warranted |
|
||
| `blocked` | cannot safely proceed yet |
|
||
|
||
### Backend classes
|
||
|
||
| backend | Meaning |
|
||
| --------------- | ------------------------------- |
|
||
| `host` | direct local execution on Panda |
|
||
| `local-sandbox` | isolated local sandbox |
|
||
| `k8s-sandbox` | isolated cluster-backed sandbox |
|
||
| `queue` | queued/durable dispatch path |
|
||
| `restate` | Restate-backed durable executor |
|
||
| `none` | no execution selected yet |
|
||
|
||
### Autonomy levels
|
||
|
||
| autonomy | Meaning |
|
||
| ------------ | ----------------------------------------------- |
|
||
| `inline` | answer or patch directly in-session |
|
||
| `supervised` | execute, but keep operator checkpoints tight |
|
||
| `afk` | the system can take the batch and run |
|
||
| `blocked` | do not proceed until ambiguity/risk is resolved |
|
||
|
||
### Proof postures
|
||
|
||
| proof | Meaning |
|
||
| --------- | --------------------------------------------------- |
|
||
| `none` | normal implementation |
|
||
| `dry-run` | simulate or plan without mutating reality |
|
||
| `canary` | narrow live proof |
|
||
| `soak` | longer live confidence window |
|
||
| `full` | direct implementation without a staged proof window |
|
||
|
||
### Risk postures
|
||
|
||
| risk | Meaning |
|
||
| ------------------ | ----------------------------------- |
|
||
| `reversible-only` | every step must be easy to back out |
|
||
| `sandbox-required` | isolation is mandatory |
|
||
| `host-okay` | local/host execution is acceptable |
|
||
| `deploy-allowed` | deployment is in scope |
|
||
| `human-signoff` | explicit human review gate required |
|
||
|
||
### Artifact names
|
||
|
||
Use these values in workload specs and stage outputs:
|
||
|
||
- `patch`
|
||
- `tests`
|
||
- `verification`
|
||
- `summary`
|
||
- `docs`
|
||
- `adr`
|
||
- `deploy-proof`
|
||
- `telemetry-proof`
|
||
- `handoff`
|
||
- `research-note`
|
||
- `comparison`
|
||
- `rollback-plan`
|
||
|
||
## Shipped CLI surface
|
||
|
||
Current shipped commands:
|
||
|
||
```bash
|
||
joelclaw workload plan "<intent>" \
|
||
[--preset docs-truth|research-compare|refactor-handoff] \
|
||
[--kind auto|repo.patch|repo.refactor|repo.docs|repo.review|research.spike|runtime.proof|cross-repo.integration] \
|
||
[--shape auto|serial|parallel|chained] \
|
||
[--autonomy inline|supervised|afk|blocked] \
|
||
[--proof none|dry-run|canary|soak|full] \
|
||
[--risk reversible-only,host-okay] \
|
||
[--artifacts patch,verification,summary] \
|
||
[--acceptance "criterion one|criterion two"] \
|
||
[--repo /abs/path/or/owner/repo] \
|
||
[--paths docs/workloads.md,docs/cli.md] \
|
||
[--paths-from status|head|recent:<n>] \
|
||
[--stages-from /abs/path/to/stages.json] \
|
||
[--write-plan ~/.joelclaw/workloads/] \
|
||
[--requested-by Joel]
|
||
|
||
joelclaw workload dispatch <plan-artifact> \
|
||
[--stage stage-2] \
|
||
[--to BlueFox] \
|
||
[--from MaroonReef] \
|
||
[--send-mail] \
|
||
[--write-dispatch ~/.joelclaw/workloads/]
|
||
|
||
joelclaw workload run <plan-artifact> \
|
||
[--stage stage-2] \
|
||
[--tool pi|codex|claude] \
|
||
[--execution-mode auto|host|sandbox] \
|
||
[--sandbox-backend local|k8s] \
|
||
[--skip-dep-check] \
|
||
[--repo-url git@github.com:owner/repo.git] \
|
||
[--dry-run]
|
||
```
|
||
|
||
Semantics:
|
||
|
||
- returns a canonical `request` + `plan` envelope using the vocabulary below
|
||
- also returns `guidance` so the CLI can recommend what to do next instead of just listing technical affordances
|
||
- `guidance` includes:
|
||
- `recommendedExecution` — whether to execute inline now, tighten scope first, or dispatch only after health checks
|
||
- `operatorSummary` — plain-spoken recommendation for the operator/agent
|
||
- `adrCoverage` — which ADRs appear to govern the slice so the planner does not send the agent on a pointless ADR hunt; on fast-moving repo-local ADR clusters this is still high-signal guidance, not infallible closure
|
||
- `recommendedSkills` — skill readiness, including `joelclaw skills ensure <name>` for local repo skills and `npx skills add -y -g <source>` for external skills
|
||
- `executionExamples` — serial / parallel / chained setup + execution few-shot examples for coding tasks
|
||
- `executionLoop` — approval prompt, approved-next-step, progress reporting expectation, and closeout expectation so the agent knows what to do after the operator says yes
|
||
- infers `kind`, `shape`, `mode`, and `backend` when the caller leaves them open
|
||
- supports reusable presets via `--preset docs-truth|research-compare|refactor-handoff`
|
||
- preserves explicit acceptance embedded in the intent when the prompt includes an `Acceptance:` section and `--acceptance` is omitted
|
||
- implementation signals like `refactor` or `extend` outrank docs follow-through, so `refactor ... then update docs` stays implementation-shaped instead of collapsing into `repo.docs`
|
||
- validates known `risk` and `artifacts` values and warns on unknown ones
|
||
- mentions of sandboxes as the _subject_ of research do not by themselves force `sandbox-required`; isolation has to be explicit or implied by AFK autonomy
|
||
- `deploy-allowed` is inferred only from explicit release/deploy intent; nouns like `published skills` do not count as a deploy request
|
||
- supervised repo work can use `proof=canary|soak` without being forced into `durable` / `restate`; proof posture alone is not a runtime decision
|
||
- `--paths-from status|head|recent:<n>` can seed path scope from local git activity when explicit `--paths` would be tedious
|
||
- `--stages-from <file>` loads an explicit JSON stage DAG, validates dependencies/cycles, carries stage acceptance into plan verification, and exposes DAG metadata under `result.metadata`
|
||
- when `--shape auto` is still in effect, an explicit stage DAG now decides whether the plan is `serial`, `parallel`, or `chained`
|
||
- chained `repo.patch` / `repo.refactor` plans can decompose a `Goal:` section into explicit milestones and add a `reflect and update plan` stage when the prompt asks for it
|
||
- `--write-plan <path>` writes the full CLI envelope to a reusable JSON artifact for handoff or later dispatch prep
|
||
- treats a missing `--repo` as the current working directory and infers `branch` / `baseSha` when that target is a local git repo; if the cwd is not a git repo, the planner warns and tells the caller to pass `--repo`
|
||
- **does not execute code or mutate repos**
|
||
|
||
`joelclaw workload dispatch` semantics:
|
||
|
||
- reads a saved `joelclaw workload plan --write-plan ...` envelope and turns it into a machine-usable dispatch/handoff contract
|
||
- chooses the first stage by default, or a caller-selected stage via `--stage`
|
||
- carries forward scoped file boundaries via `reservedPaths`
|
||
- also returns `guidance` so dispatch can say whether handing this off is actually smart:
|
||
- `execute-dispatched-stage-now`
|
||
- `dispatch-is-overkill-keep-it-inline`
|
||
- `dispatch-after-health-check`
|
||
- `clarify-recipient-before-sending`
|
||
- plus `executionLoop` so the agent gets the honest plan → approve → execute/watch → summarize posture instead of cargo-cult orchestration
|
||
- carries forward ADR coverage + recommended skill setup so the receiving agent does not start half-blind
|
||
- turns the saved plan into a canonical `handoff` object instead of forcing another agent to reconstruct the task from chat
|
||
- can optionally emit a second saved artifact with `--write-dispatch`
|
||
- can optionally send the dispatch contract through `joelclaw mail` with `--to ... --from ... --send-mail`
|
||
- still **does not execute code or mutate repos**
|
||
|
||
`joelclaw workload run` semantics:
|
||
|
||
- reads a saved `joelclaw workload plan --write-plan ...` envelope and normalizes it into one canonical runtime request
|
||
- uses the queue family `workload/requested`, which is registry-routed to the real runtime event `system/agent.requested`
|
||
- current durable execution path is `Redis queue → Restate dagOrchestrator → dagWorker`
|
||
- proven handler truth today:
|
||
- `shell` ✅ executes in the `restate-worker` pod and can clone repos, run pi agent mode, write files, commit, and push
|
||
- `infer` ✅ runs `pi -p` in-cluster for research, planning, review, and analysis
|
||
- `microvm` ⚠️ boots/restores Firecracker guests, but guest command execution is not wired yet
|
||
- the `restate-worker` image is a full agent environment: pi 0.58.4, 76 skills, Firecracker, `/dev/kvm`, git push auth from a k8s secret, and pi auth mounted from the host
|
||
- workspace setup uses the pre-cloned `/app/repo-cache` when available (~200ms) and falls back to a fresh clone (~3s)
|
||
- `dagWorker` uses a 15m inactivity timeout and 30m hard abort; the worker heartbeat pi extension keeps active agent runs alive
|
||
- stage outputs can flow into downstream stages via `{{nodeId}}` interpolation
|
||
- chooses the first stage by default, or a caller-selected stage via `--stage`
|
||
- reuses the dispatch/handoff contract so the runtime payload carries explicit scope, acceptance, remaining gates, and closeout expectations instead of vague chat sludge
|
||
- defaults to `--tool pi`; `--tool codex|claude` is opt-in
|
||
- infers `executionMode=host|sandbox` from the planned workload unless the operator overrides it
|
||
- when sandbox execution is selected, the canonical front door can also carry `--sandbox-backend local|k8s` and `--sandbox-mode minimal|full`
|
||
- explicit-stage plans refuse to run a stage until each `dependsOn` stage has terminal inbox truth; use `--skip-dep-check` only for deliberate manual recovery or replay
|
||
- nested workflow-rig execution from inside a sandboxed stage is blocked by default; stage work should use direct proof commands inside the current sandbox instead of launching another `joelclaw workload run`
|
||
- supports `--dry-run` so the operator can inspect the normalized runtime request before enqueueing it
|
||
- returns queue admission details (`streamId`, `eventId`, priority, triage mode) once enqueued
|
||
- completion is still poll-based: there is no automatic gateway finish event when a DAG lands
|
||
- large-file pi agent edits can take 3-5 minutes even when the run succeeds
|
||
- **does not pretend queue emit is the only front door anymore** — raw `joelclaw queue emit` remains the substrate escape hatch, while `workload run` is the canonical bridge from workload artifacts to runtime admission
|
||
|
||
`joelclaw workload sandboxes` semantics:
|
||
|
||
- `list` is the operator-facing registry surface for local ADR-0221 sandboxes
|
||
- reconciles the registry against each sandbox’s `sandbox.json` metadata before reporting state
|
||
- shows state, mode, retention/cleanup posture, teardown state, and whether the sandbox still exists on disk
|
||
- supports `--state`, `--mode`, `--expired`, and `--limit`
|
||
- `cleanup` is the bounded manual cleanup path
|
||
- target by `--request-id`, `--sandbox-id`, `--expired`, or `--all-terminal`
|
||
- reconciles registry drift before deciding whether a sandbox is still active, so terminal metadata can unblock cleanup without `--force`
|
||
- non-terminal sandboxes are protected unless `--force` is explicit
|
||
- `--dry-run` previews deletions without mutating disk or registry
|
||
- `janitor` is the dedicated expired-sandbox cleanup path
|
||
- no longer waits for the next sandbox startup to prune TTL-expired entries
|
||
- reconciles registry drift before computing expired candidates
|
||
- `--dry-run` previews candidates; the live path removes expired sandbox directories and trims the registry
|
||
- the repo-managed launchd service `com.joel.local-sandbox-janitor` now runs this path automatically at load and every 30 minutes so ADR-0221 cleanup is not purely on-demand
|
||
|
||
## Workload request schema
|
||
|
||
This is the canonical request envelope.
|
||
|
||
```json
|
||
{
|
||
"version": "2026-03-08",
|
||
"kind": "repo.refactor",
|
||
"intent": "make queue observation planning agent-first instead of substrate-first",
|
||
"requestedBy": "Joel",
|
||
"shape": "auto",
|
||
"autonomy": "supervised",
|
||
"proof": "dry-run",
|
||
"risk": ["reversible-only", "host-okay"],
|
||
"targets": [
|
||
{
|
||
"repo": "/Users/joel/Code/joelhooks/joelclaw",
|
||
"branch": "main",
|
||
"baseSha": "42065cc3",
|
||
"paths": ["docs/", "skills/"]
|
||
}
|
||
],
|
||
"acceptance": [
|
||
"canonical workload vocabulary exists",
|
||
"serial parallel chained semantics are explicit",
|
||
"another agent can use one workload contract without reading runtime internals"
|
||
],
|
||
"artifacts": ["docs", "summary", "handoff"],
|
||
"constraints": {
|
||
"mustFollow": [
|
||
"use clawmail for shared-file work",
|
||
"keep shipped docs separate from planned CLI behavior"
|
||
],
|
||
"avoid": ["pretending joelclaw workload already exists"]
|
||
},
|
||
"context": {
|
||
"adr": ["ADR-0217"],
|
||
"steering": "ergonomics is phase 4",
|
||
"notes": [
|
||
"the current restate-workflows front door makes agents sad and confused"
|
||
]
|
||
}
|
||
}
|
||
```
|
||
|
||
## Guidance envelope
|
||
|
||
`joelclaw workload plan` also returns a guidance block aimed at getting the next step right, not just technically available.
|
||
|
||
Treat `guidance.executionLoop` as the operator contract: **plan → approve → execute/watch → summarize**. The CLI is supposed to steer the next move, not shrug and dump affordances.
|
||
|
||
Treat `guidance.adrCoverage` as a best-effort guardrail, not divine revelation. On a live repo-local ADR cluster — like fresh Gremlin follow-on ADRs — reconcile the suggested records against nearby related ADRs before declaring coverage complete.
|
||
|
||
```json
|
||
{
|
||
"recommendedExecution": "execute-inline-now",
|
||
"operatorSummary": "This is a bounded local slice with explicit file scope. Execute inline now; dispatch only if you want separate ownership or a clean baton pass.",
|
||
"adrCoverage": {
|
||
"records": ["ADR-0217"],
|
||
"note": "Workload planning and dispatch posture are covered by ADR-0217; only open another ADR if this changes the workload model itself."
|
||
},
|
||
"recommendedSkills": [
|
||
{
|
||
"name": "workflow-rig",
|
||
"reason": "Canonical front door for workload planning, dispatch posture, runtime mode selection, and handoff contracts",
|
||
"canonicalPath": "/Users/joel/Code/joelhooks/joelclaw/skills/workflow-rig/SKILL.md",
|
||
"installedConsumers": ["agents", "pi"],
|
||
"missingConsumers": ["claude"],
|
||
"ensureCommand": "joelclaw skills ensure workflow-rig",
|
||
"readPath": "/Users/joel/Code/joelhooks/joelclaw/skills/workflow-rig/SKILL.md"
|
||
}
|
||
],
|
||
"executionLoop": {
|
||
"approvalPrompt": "Present the shaped workload, confirm the scoped paths and acceptance criteria, then ask 'approved?' before mutating code or dispatching another agent.",
|
||
"approvedNextStep": "If approved, reserve the scoped files and execute the bounded slice directly. Do not widen it into dispatch, queue, or adjacent ops theatre.",
|
||
"progressUpdateExpectation": "While the work is running, let the pi extension/TUI show real stage status and only interrupt the operator for blockers, changed scope, or a decision that actually needs human input.",
|
||
"completionExpectation": "When the slice lands, report what changed, what was verified, what remains, and whether the operator wants the commit pushed."
|
||
},
|
||
"executionExamples": [
|
||
{
|
||
"shape": "serial",
|
||
"title": "One agent, ordered checkpoints",
|
||
"setup": ["Pin the repo and path scope before touching code."],
|
||
"execute": [
|
||
"Implement the change.",
|
||
"Run narrow verification.",
|
||
"Update docs/skills truth immediately after code truth."
|
||
],
|
||
"exampleTask": "Refactor a CLI command, rerun its tests, then update the matching docs.",
|
||
"exampleCommand": "joelclaw workload plan \"Refactor the CLI surface, verify with narrow tests, then update the matching docs\" --shape serial --repo /Users/joel/Code/joelhooks/joelclaw --paths packages/cli/src/commands/workload.ts,docs/cli.md"
|
||
}
|
||
]
|
||
}
|
||
```
|
||
|
||
### Required fields
|
||
|
||
- `version`
|
||
- `kind`
|
||
- `intent`
|
||
- `requestedBy`
|
||
- `shape`
|
||
- `autonomy`
|
||
- `proof`
|
||
- `risk`
|
||
- `targets`
|
||
- `acceptance`
|
||
- `artifacts`
|
||
|
||
### Optional fields
|
||
|
||
- `constraints`
|
||
- `context`
|
||
- `handoffSeed`
|
||
- `priority`
|
||
- `deadline` (only when externally imposed; don't invent one)
|
||
|
||
## Workload plan schema
|
||
|
||
This is what the planner should produce.
|
||
|
||
```json
|
||
{
|
||
"workloadId": "WL_20260308_165513",
|
||
"version": "2026-03-08",
|
||
"status": "planned",
|
||
"kind": "repo.docs",
|
||
"shape": "serial",
|
||
"mode": "inline",
|
||
"backend": "host",
|
||
"summary": "serial repo.docs planned for inline execution on host",
|
||
"why": [
|
||
"kind pinned by caller to repo.docs",
|
||
"repo.docs defaults to ordered gates because one stage depends on the previous one being correct",
|
||
"nothing about the workload justifies background durability or isolation yet",
|
||
"inline planning or execution defaults to the local host session"
|
||
],
|
||
"risks": ["risk posture: reversible-only", "risk posture: host-okay"],
|
||
"artifacts": ["docs", "summary", "adr"],
|
||
"verification": [
|
||
"request and plan use the canonical fields from docs/workloads.md",
|
||
"the chosen shape, mode, and backend are explained in plain language"
|
||
],
|
||
"stages": [
|
||
{
|
||
"id": "stage-1",
|
||
"name": "scope and prepare",
|
||
"owner": "planner",
|
||
"mode": "inline",
|
||
"inputs": ["intent", "acceptance criteria"],
|
||
"outputs": ["workload plan"],
|
||
"verification": ["scope boundary and artifacts are explicit"],
|
||
"stopConditions": ["acceptance criteria are still mush"]
|
||
},
|
||
{
|
||
"id": "stage-2",
|
||
"name": "execute primary task",
|
||
"owner": "planner",
|
||
"mode": "inline",
|
||
"inputs": ["workload plan"],
|
||
"outputs": ["docs", "adr"],
|
||
"verification": ["primary artifact is produced"],
|
||
"stopConditions": ["execution drifts outside planned boundaries"],
|
||
"dependsOn": ["stage-1"]
|
||
},
|
||
{
|
||
"id": "stage-3",
|
||
"name": "verify and summarize",
|
||
"owner": "planner",
|
||
"mode": "inline",
|
||
"inputs": ["stage-2 outputs"],
|
||
"outputs": ["summary"],
|
||
"verification": ["result and next action are explicit"],
|
||
"stopConditions": ["closeout cannot explain done vs remaining work"],
|
||
"dependsOn": ["stage-2"]
|
||
}
|
||
],
|
||
"next_actions": [
|
||
{
|
||
"command": "joelclaw workload plan \"groom ADR-0217 truth and docs\" --kind repo.docs --shape serial",
|
||
"description": "re-run the planner with the inferred kind/shape pinned explicitly"
|
||
}
|
||
]
|
||
}
|
||
```
|
||
|
||
## Stage schema
|
||
|
||
Every stage in a `serial` or `chained` plan should carry:
|
||
|
||
- `id`
|
||
- `name`
|
||
- `owner`
|
||
- `mode`
|
||
- `inputs`
|
||
- `outputs`
|
||
- `verification`
|
||
- `stopConditions`
|
||
|
||
Recommended optional fields:
|
||
|
||
- `reservedPaths`
|
||
- `dependsOn`
|
||
- `handoffTo`
|
||
- `estimatedBlastRadius` (small/medium/large if useful)
|
||
|
||
The shipped planner now uses `reservedPaths` on scoped implementation stages so the file boundary survives handoff instead of disappearing into prose.
|
||
|
||
### Explicit stage file schema
|
||
|
||
`joelclaw workload plan --stages-from <file>` accepts a JSON array of explicit stages:
|
||
|
||
```json
|
||
[
|
||
{
|
||
"id": "build-rootfs",
|
||
"name": "Build agent rootfs + guest kernel",
|
||
"executionMode": "sandbox",
|
||
"dependsOn": ["install-firecracker"],
|
||
"acceptance": [
|
||
"microVM boots from rootfs",
|
||
"bun --version succeeds inside microVM"
|
||
],
|
||
"files": [
|
||
"infra/firecracker/Dockerfile.rootfs",
|
||
"infra/firecracker/build-rootfs.sh"
|
||
],
|
||
"artifacts": ["rootfs.ext4", "vmlinux"],
|
||
"owner": "worker",
|
||
"tool": "codex",
|
||
"timeout": 1800,
|
||
"phase": "B",
|
||
"notes": "Build inside the isolated prep environment."
|
||
}
|
||
]
|
||
```
|
||
|
||
Rules:
|
||
|
||
- required fields: `id`, `name`, `acceptance`
|
||
- `dependsOn` is a true DAG edge list, not just a linear previous-stage pointer
|
||
- planner validation now rejects unknown dependencies, duplicate stage ids, self-dependencies, and cycles before any runtime work starts
|
||
- DAG analysis returns:
|
||
- `topologicalOrder`
|
||
- `criticalPath`
|
||
- `hasParallel`
|
||
- `isLinear`
|
||
- `inferredShape`
|
||
- `phases` grouped by the explicit `phase` labels in the file
|
||
|
||
## Handoff schema
|
||
|
||
Use this when one worker hands off to another.
|
||
|
||
```json
|
||
{
|
||
"workloadId": "WL_20260308_001",
|
||
"stageId": "stage-2",
|
||
"goal": "align skills to the canonical workload vocabulary",
|
||
"currentState": "docs/workloads.md landed; skill updates still pending",
|
||
"artifactsProduced": ["docs/workloads.md"],
|
||
"verificationDone": [
|
||
"schema terms enumerated",
|
||
"planned CLI surface marked as not shipped"
|
||
],
|
||
"remainingGates": [
|
||
"update skill reference",
|
||
"groom ADR/PRD truth",
|
||
"commit and push"
|
||
],
|
||
"reservedPaths": ["skills/agent-workloads/SKILL.md"],
|
||
"releasedPaths": ["docs/workloads.md"],
|
||
"risks": ["skill drift from canonical schema"],
|
||
"nextAction": "update the skill and reference to use the exact vocabulary from docs/workloads.md"
|
||
}
|
||
```
|
||
|
||
If the next worker has to reconstruct the task from raw chat, the handoff is bad.
|
||
|
||
## Dispatch contract schema
|
||
|
||
`joelclaw workload dispatch` wraps the selected stage plus the canonical handoff into a reusable dispatch envelope.
|
||
|
||
```json
|
||
{
|
||
"version": "2026-03-08",
|
||
"dispatchId": "WD_20260308_191500",
|
||
"sourcePlan": {
|
||
"path": "/Users/joel/.joelclaw/workloads/WL_20260308_191410.json",
|
||
"workloadId": "WL_20260308_191410"
|
||
},
|
||
"selectedStage": {
|
||
"id": "stage-2",
|
||
"name": "verify independently",
|
||
"owner": "reviewer",
|
||
"mode": "inline",
|
||
"inputs": ["stage-1 outputs"],
|
||
"outputs": ["verification"],
|
||
"reservedPaths": ["packages/cli/src/commands/workload.ts"],
|
||
"verification": ["verification is recorded separately from implementation"],
|
||
"stopConditions": [
|
||
"verification cannot explain what changed or what remains"
|
||
],
|
||
"dependsOn": ["stage-1"]
|
||
},
|
||
"target": {
|
||
"repo": "/Users/joel/Code/joelhooks/joelclaw",
|
||
"branch": "main",
|
||
"baseSha": "abc1234",
|
||
"paths": ["packages/cli/src/commands/workload.ts"]
|
||
},
|
||
"guidance": {
|
||
"recommendation": "execute-dispatched-stage-now",
|
||
"summary": "The dispatch contract is ready. Reserve the scoped paths and execute the selected stage instead of re-planning it from scratch.",
|
||
"stageReason": "Caller explicitly chose stage-2; dispatch is honoring that pinned stage instead of guessing.",
|
||
"adrCoverage": {
|
||
"records": ["ADR-0217"],
|
||
"note": "Workload planning and dispatch posture are covered by ADR-0217; only open another ADR if this changes the workload model itself."
|
||
},
|
||
"recommendedSkills": [],
|
||
"executionLoop": {
|
||
"approvalPrompt": "Present the stage-specific dispatch contract, confirm the recipient/stage, then ask 'approved?' before sending the baton.",
|
||
"approvedNextStep": "If approved, reserve the scoped files, send the contract if another agent owns the stage, and execute without re-planning it from scratch.",
|
||
"progressUpdateExpectation": "While the stage is running, let the pi extension/TUI show handoff and execution status. Only interrupt the operator for blockers, changed scope, or a genuine decision point.",
|
||
"completionExpectation": "Finish with the selected stage outcome, verification, remaining gates, and whether another handoff, a push, or a stop is warranted."
|
||
}
|
||
},
|
||
"handoff": {
|
||
"workloadId": "WL_20260308_191410",
|
||
"stageId": "stage-2",
|
||
"goal": "verify independently",
|
||
"currentState": "planned from /Users/joel/.joelclaw/workloads/WL_20260308_191410.json; stage-2 is the next executable stage",
|
||
"artifactsProduced": [
|
||
"/Users/joel/.joelclaw/workloads/WL_20260308_191410.json"
|
||
],
|
||
"verificationDone": [
|
||
"request and plan use the canonical fields from docs/workloads.md"
|
||
],
|
||
"remainingGates": [
|
||
"stage-2: verify independently",
|
||
"stage-3: handoff and closeout"
|
||
],
|
||
"reservedPaths": ["packages/cli/src/commands/workload.ts"],
|
||
"releasedPaths": [],
|
||
"risks": ["risk posture: reversible-only", "risk posture: host-okay"],
|
||
"nextAction": "execute stage-2: verify independently"
|
||
},
|
||
"mail": {
|
||
"subject": "Task: WL_20260308_191410 stage-2 verify independently",
|
||
"body": "...markdown dispatch summary..."
|
||
}
|
||
}
|
||
```
|
||
|
||
## Selection rules
|
||
|
||
### When to choose `serial`
|
||
|
||
Choose `serial` when:
|
||
|
||
- stage order is strict
|
||
- risk is high
|
||
- the same operator should inspect each gate
|
||
- runtime proof or cleanup is part of the task
|
||
|
||
### When to choose `parallel`
|
||
|
||
Choose `parallel` when:
|
||
|
||
- branches are independent
|
||
- uncertainty reduction matters more than immediate integration
|
||
- each branch can own non-overlapping files or remain read-only
|
||
- one synthesis owner is assigned
|
||
|
||
### When to choose `chained`
|
||
|
||
Choose `chained` when:
|
||
|
||
- different stage specializations add value
|
||
- artifacts need to be consumed downstream explicitly
|
||
- implementation is not the final step
|
||
|
||
## Common workload examples
|
||
|
||
### Example: single-pass patch
|
||
|
||
```json
|
||
{
|
||
"kind": "repo.patch",
|
||
"shape": "serial",
|
||
"mode": "inline",
|
||
"backend": "host",
|
||
"artifacts": ["patch", "verification", "summary"]
|
||
}
|
||
```
|
||
|
||
### Example: compare two approaches
|
||
|
||
```json
|
||
{
|
||
"kind": "research.spike",
|
||
"shape": "parallel",
|
||
"mode": "inline",
|
||
"backend": "host",
|
||
"artifacts": ["research-note", "comparison", "summary"]
|
||
}
|
||
```
|
||
|
||
### Example: implement → verify → docs
|
||
|
||
```json
|
||
{
|
||
"kind": "repo.refactor",
|
||
"shape": "chained",
|
||
"mode": "sandbox",
|
||
"backend": "local-sandbox",
|
||
"artifacts": ["patch", "tests", "verification", "docs", "handoff"]
|
||
}
|
||
```
|
||
|
||
### Example: live canary window
|
||
|
||
```json
|
||
{
|
||
"kind": "runtime.proof",
|
||
"shape": "serial",
|
||
"mode": "durable",
|
||
"backend": "restate",
|
||
"artifacts": ["telemetry-proof", "summary", "rollback-plan"]
|
||
}
|
||
```
|
||
|
||
## Operator use until status/cancel/notifications exist
|
||
|
||
Use `joelclaw workload plan` first whenever it fits. Use `joelclaw workload dispatch` when a saved plan should become an explicit handoff contract. Use `joelclaw workload run` when the work should enter the real durable runtime.
|
||
|
||
Because `status|explain|cancel` and automatic completion notifications are still missing, operate with this posture:
|
||
|
||
1. capture Joel steering in the request fields
|
||
2. run `joelclaw workload plan` and save the artifact with `--write-plan` when another worker or the durable runtime may need it
|
||
3. if the plan should be handed to another worker, turn it into a dispatch contract with `joelclaw workload dispatch`
|
||
4. when the work should really execute, use `joelclaw workload run`
|
||
5. poll progress with `joelclaw runs`, `joelclaw run <run-id>`, and OTEL until the DAG lands
|
||
6. use `clawmail` for reservation and handoff
|
||
7. keep the final summary in the same vocabulary, including any known gaps that affected the run
|
||
|
||
## Phase 4.3 scheduling helpers
|
||
|
||
These are the first real ergonomics features aimed at making scheduling actual repo work less of a pain:
|
||
|
||
- `--preset` seeds common kind/shape/artifact/acceptance bundles without inventing a fake dispatch surface
|
||
- `--paths-from status|head|recent:<n>` pulls scope from real git activity when the operator does not want to hand-type a long path list
|
||
- `--write-plan <path>` emits the full workload envelope as a reusable JSON artifact
|
||
- `joelclaw workload dispatch <plan-artifact>` turns that saved plan into a stage-specific dispatch/handoff contract
|
||
- `--write-dispatch <path>` emits the dispatch contract as a second reusable JSON artifact
|
||
- `result.inference.target.scope` records whether the scope came from explicit paths, repo-wide planning, or git-derived seeding
|
||
- `result.artifact` records the written plan artifact path when `--write-plan` is used
|
||
|
||
## Anti-patterns
|
||
|
||
Avoid:
|
||
|
||
- answering a workload question with only substrate docs
|
||
- mixing shipped CLI truth with planned surfaces
|
||
- parallel edits without a synthesis owner
|
||
- vague handoffs like “continue from above”
|
||
- choosing `durable` or `sandbox` because it sounds fancy rather than because the workload needs it
|
||
|
||
## Relationship to other docs and skills
|
||
|
||
- `Vault/docs/decisions/0217-event-routing-queue-discipline.md` — why Phase 4 exists
|
||
- `Vault/Projects/09-joelclaw/0217-phase-4-agent-first-workload-ergonomics.md` — broader Phase 4 PRD
|
||
- `skills/workflow-rig/` — canonical agent-facing front door for this model
|
||
- `skills/agent-workloads/` — compatibility alias for older prompts that still name the legacy front door
|
||
- `skills/restate-workflows/` — substrate bridge compatibility alias after workload planning is already clear
|
||
- `docs/cli.md` — shipped CLI semantics and the remaining planned workload surfaces
|
||
|
||
## Story 4.1 done criteria
|
||
|
||
Story 4.1 is earned when:
|
||
|
||
- this vocabulary is canonical in the repo
|
||
- the `workflow-rig` skill uses the same contract, with `agent-workloads` retained only as a compatibility alias
|
||
- Phase 4 PRD and ADR-0217 point at the same model
|
||
- future CLI implementation can start from this doc instead of rediscovering the shape from scratch
|
||
|
||
## Story 4.2 done criteria
|
||
|
||
Story 4.2 is earned when:
|
||
|
||
- `joelclaw workload plan` returns the canonical `request` + `plan` envelope
|
||
- the planner uses the shared vocabulary from this doc instead of inventing fresh field names
|
||
- shipped behavior is explicit: `plan`, `dispatch`, and `run` are real; `status|explain|cancel` are still not
|
||
- docs, skill guidance, ADR truth, and CLI docs all describe the same workload-front-door reality
|
||
|
||
## Story 4.3 done criteria
|
||
|
||
Story 4.3 is earned when:
|
||
|
||
- scheduling real repo work no longer requires retyping scope and handoff context every bloody time
|
||
- presets, git-derived path seeding, plan artifacts, and dispatch contracts are real shipped features
|
||
- chained repo work can preserve explicit milestones, reflection/update stages, and scoped paths when the prompt supplies them
|
||
- supervised repo work is not shoved into `durable` just because the prompt mentions `canary` or `soak`
|
||
- a saved plan artifact can become a machine-usable handoff contract and a canonical queue-backed runtime request without inventing fake `workload status` or `workload explain` surfaces
|