Task directory contract
One directory per run, the same shape whether Farmslot dispatched the worker or an engineer started the task from a skill. Farmslot defines the files; @farmslot/agent-runtime and any harness built on it consume them.
Two documents, one checklist
The task document says what the work is. The execution checklist says how to do it, step by step. Only the checklist has steps.
| File | Holds | Steps counted? |
|---|---|---|
TASK.md | Ticket block, description, acceptance criteria, screenshots, comments, PR comment summary (pr-complete), mode preamble, project addendum, mark instructions, pointers to inputs/, appended gateway contracts | no |
CHECKLIST.md | The selected execution template, placeholders expanded, nothing injected or appended | yes |
Because the checklist carries no run data, a project can select a template from any catalog source and get the same bytes a standalone skill materializes for the same template id. The provenance digest proves it.
This is the only layout the task writer produces. One flow keeps its own pairing: lightweight interactive dev writes the operator-agreed plan into CHECKLIST.md and renders its template into TASK.md, because the plan there is decided live with the operator rather than selected from a catalog.
Layout
<task>/
TASK.md task document (built by @farmslot/agent-runtime)
CHECKLIST.md execution checklist (template, placeholders rendered, otherwise verbatim)
mark shim: exec ${FARMSLOT_MARK_CMD:-<recorded command>} "$DIR" "$@"
SIGNAL.json written only by mark
checklist-target.json role switches write it; absent = CHECKLIST.md + SIGNAL.json
inputs/
handoff.json the task record: identity, flow, task, report paths,
executionTemplate (selected checklist + digests),
templateProvenance (Farmslot only: repo revisions, selection source)
worker-terminal-contract.json artifacts required before a terminal mark
bug-input.json ticket as fetched, comments included (when a ticket exists)
… flow-specific inputs (planning context, PR comments, inherited context)
assets/ ticket attachments
artifacts/ worker output: reports, recipes, evidence; sandbox.json readiness record
acceptance-status.json acceptance-criteria ledger; written only by `farmslot-agent ac`
subtasks/ child checklist units, written only by mark sub
index.json registry of registered units (id, parent step, paths, source digests)
<id>.md child checklist, materialized from a skill, template, or inline text
<id>-SIGNAL.json child signal (WorkerSignal + the parent link)
Producers and consumers
One producer writes the shared layer on every surface: taskInit / farmslot-agent task init in @farmslot/agent-runtime. The gateway composes the same pieces (renderTemplatePlaceholders, buildTaskDocument, writeTaskDir) around its control-plane steps; mm-harness wraps the CLI with MetaMask defaults; the recipe-cook skill calls mm-harness.
| File | Producer | Consumer |
|---|---|---|
TASK.md | task init; worker updates STATUS and may append notes | worker, family follow-ups, review brief |
CHECKLIST.md | task init | worker, mark, progress parser, Command Center progress |
mark | task init; command is a project value (vars.mark_cmd) | worker |
checklist-target.json | role switch only | mark, progress path resolution (default when absent) |
SIGNAL.json | mark only | run monitor, publication gate, closeout |
inputs/handoff.json | task init | handoff closeout, learning packages, replay and eval, farmslot run |
inputs/worker-terminal-contract.json | task init from project.json worker_terminal | mark terminal commands, artifact contract check, monitor hold |
inputs/bug-input.json | task init from the fetched ticket | farmslot run, review inputs |
subtasks/index.json | mark sub only | worker, gateway task watcher, review-workspace progress publisher, progress projection, view mirror |
subtasks/<id>.md | mark sub start (materialized from the named source) | worker, mark sub, progress projection, view mirror |
subtasks/<id>-SIGNAL.json | mark sub only | gateway task watcher, review-workspace progress publisher, progress projection, terminal contract check, view mirror |
artifacts/acceptance-status.json | farmslot-agent ac only | terminal contract check, PR body / gate summary, run detail AC panel |
artifacts/sandbox.json | harness preparation (mm-harness prepare) | worker, evidence package, Command Center (later) |
artifacts/* | worker | publication gate, review, retrospective (see worker artifacts by flow) |
Provenance
The selected checklist travels inside inputs/handoff.json as executionTemplate, identical in shape on both surfaces:
{
"executionTemplate": {
"selectionReason": "configured-default",
"id": "fix-bug/autonomous.mobile",
"sourceId": "package:example-checklists",
"flow": "fix-bug",
"platforms": ["mobile", "ios", "android"],
"labels": [],
"relativePath": "fix-bug/autonomous.mobile.md",
"sha256": "<source digest>",
"renderedSha256": "<CHECKLIST.md digest>"
}
}
sha256 is the template source. renderedSha256 is the checklist file as written. For a placeholder-free template they are equal, and equal to a skill-side materialization of the same id. Farmslot adds templateProvenance beside it (project repository revision, selection source, render time) for the run record; the reference is never stored twice. Task dirs written before 0.9 keep inputs/template-provenance.json; readers fall back to it for one release.
Handoff metadata
inputs/handoff.json is the run's identity for closeout and learning packages:
| Field | Farmslot value | Skill value |
|---|---|---|
attemptId | run id | random UUID |
surface | farmslot | skill |
project | project name | checkout basename |
repo | owner/name from repo_url or ci.repo | owner/name from the git remote |
domain | effective run domain, when any | --domain, when any |
flow | flow type | task kind |
task | title, sourceKind (jira, github-issue, github-pr, text), ticket, source URL | title, text or file, ticket, ref |
task.acceptanceCriteria | the ticket's criteria, in order; position N is ledger id AC-N | --acceptance values, same ids |
taskDocument | TASK.md | TASK.md |
report | the terminal contract's complete.report (artifacts/pr-description.md for dev / fix-bug) | artifacts/pr-description.md (dev / fix-bug) |
learnings | artifacts/learnings.md | artifacts/learnings.md |
Outcome file: one name on both surfaces
dev and fix-bug workers finish with artifacts/pr-description.md whether Farmslot dispatched the run or an engineer ran the skill. It is the PR body in the repository's PR-template shape and carries the proof summary (what changed, root cause, validation, evidence paths). Most runs want a PR, and the file is usable before anyone decides to open one.
| Stage | Farmslot | Skill |
|---|---|---|
Worker outcome (terminal contract complete.report) | artifacts/pr-description.md | artifacts/pr-description.md |
| Publication | the gateway fills the evidence section from evidence-manifest.json and publishes after the human gate | the evidence packaging step adds the evidence images and artifact index into pr-package/pr-desc.md, then a PR is opened only on request |
review-pr writes artifacts/report.md (with its QA artifacts) on both surfaces; no-change writes artifacts/no-change-report.md. inputs/handoff.json report names the outcome file, so closeout and learning packages read the same file wherever the run happened.
Layers: shared spec, control plane on top
Both surfaces produce the same task directory. Without the control plane (an engineer running the skill) the first two layers exist; with it (Farmslot) the last two are added beside them, and no shared file is renamed.
| Layer | Producer | Files | Surfaces |
|---|---|---|---|
| Worker outputs | the agent following CHECKLIST.md | outcome file (pr-description.md, report.md, no-change-report.md), learnings.md, recipe.json, recipe-run/, recipe-coverage.md, recipe-quality.json, evidence-manifest.json, validation-summary.{json,md}, before/after media, tool logs (coverage.log, jest.log, …); subtasks/index.json, subtasks/<id>.md, subtasks/<id>-SIGNAL.json through mark sub | both, same names |
| Harness readiness records | mm-harness during preparation | harness-provenance.json, sandbox.md, doctor-fix.json, status.json, launch-verify.txt, fixtures-set.txt, verify.json; task-local .mm-harness/ lock | skill today; the farm prepares the same environment in preflight.sh without leaving these records (open) |
| Gateway-owned | the gateway, on the orchestrator copy | diff.txt, diff.txt.previous.*, diff-stat.json, session-metrics.json, workflow.mmd, pr-package.json, pr-package.md, publication-gate-<slug>.md (slug: letters, digits, dashes), and per review round <n> (digits): self-review-<n>/, self-review-<n>.json, self-review-<n>.md, independent-review-<n>/, independent-review-<n>.json, independent-review-<n>.md, review-loop-<n>/ | Farmslot only |
| Review-loop outputs | reviewer roles on the slot, change ledger | review-feedback.<context>.md, review-result.<context>.json, iteration-diff*.{json,txt} | Farmslot only |
The gateway-owned row is exactly what isGatewayOwnedArtifactMirrorEntry in services/gateway/src/core/artifact-copy-policy.ts recognises: those files are never mirrored back from the slot and never count as worker evidence. Review-loop outputs are mirrored like worker files but belong to the control plane. Under inputs/, runtime-capability-catalog.json and the planning and inherited context files are Farmslot-only as well. A new gateway feature adds a name to one of these rows; it does not reuse a worker name.
Shared inputs/ names: bug-input.json for the ticket as fetched and assets/ for its attachments, on both surfaces. Task directory paths still differ (temp/tasks/<flow>/<slug>-<stamp> on the farm, temp/tasks/recipe-cook/<stamp>-<slug> for the skill); nothing reads the path, so it stays a naming difference.
Simplification ledger
Kept current with the layout. Each row is something the layout still carries that a good default could remove. Rows leave when shipped or rejected. Shipped 2026-09-13: one task-dir producer; provenance folded into handoff.json; ticket-comments.json removed; checklist-target.json optional for readers. Shipped 2026-09-14: the gateway no longer writes the default-valued checklist-target.json (every node runs the 0.9 mark engine).
| Candidate | Today | Simpler default | State |
|---|---|---|---|
| two naming schemes for review outputs | review-feedback.<context>.md / review-result.<context>.json and self-review-N.* / independent-review-N.* | one scheme, one row in the layers table | candidate |
| harness identity | PATH resolve through the pack's recipe_runner_resolve_cmd on the farm, task-local lock on the skill | one preparation step both surfaces run, which also writes the readiness records | decision open |
farm defaults pinning farm copies of dev / fix-bug / review-pr | three near-copies of the skill templates | delete them so the catalog default (skills package) wins | deferred until the five-label inventory |
| task directory path | <flow>/<slug>-<stamp> vs recipe-cook/<stamp>-<slug> | leave; nothing reads it | rejected |
What travels to the slot
Dispatch copies TASK.md, then the task-root sidecars (mark, CHECKLIST.md, and checklist-target.json when present), then assets/, inputs/, artifacts/, and subtasks/ as directories. Re-sync and warm-session handoff use the same list. At completion the gateway mirrors artifacts/, TASK.md, and CHECKLIST.md back beside the orchestrator copy as *.worker, and every file under subtasks/ as subtasks/<name>.worker from a directory listing — child ids are chosen at registration, so there is no fixed name list.
A slot-free static review workspace (ADR-058) mirrors the same directory into its operator-visible view/, under the worker's own names rather than *.worker, so subtasks/index.json's own relative paths still resolve there after the workspace is cleaned up.
The mirror travels one way. *.worker files are orchestrator-owned output written from the slot, so the outbound copy skips them: re-dispatching, nudging, or warm-handing off a task directory that already completed once must not put stale copies of the worker's own files back beside the live ones.
subtasks/ is also the one directory the gateway creates on the slot without writing anything into it: both file-watch primitives observe a file through its parent directory, and the directory otherwise appears only with the first mark sub start, so the task watcher would never see a child registered mid-run. The registry, the child checklists, and the child signals stay mark-written.
Project addendum
A project may ship templates/task-document.md. The writer renders it with the same placeholders as worker templates and inserts it into TASK.md before the checklist pointer. Use it for project tooling the worker needs before the first step (runner resolution, environment notes). It is not a checklist: checkboxes there are not counted.
Boundaries
- The checklist never contains run data. Ticket text, acceptance criteria, and slot facts live in
TASK.mdandinputs/. TASK.mdis never enumerated. A- [ ]inside an acceptance criterion cannot shift a step number.- A ticked box is not proof. Proof is the recipe run, its evidence, and the artifacts the terminal contract requires.
SIGNAL.jsonis written bymarkonly. Hand-written signals are rejected by the monitor.subtasks/is written bymark subonly, and a step may own one child unit for the life of the task directory. A child unit is observed, never spawned: registering one writes files and starts no process. A parent terminal mark is refused while any registered child is unsettled, bymarkand again by the gateway's terminal check. Settled iscompleteordone: ablockedchild is terminal for the run but keeps its step. The verbs and their refusals live in Agent runtime.- A child unit is not a role switch. It never writes
checklist-target.jsonand never changes the run's active task file, so a child may hang offCHECKLIST.mdor off a role checklist such asSELF-REVIEW.md; the parent link names which. A child progress update is live only while its parent checklist is the active one, andrunningwith no recent mark projects asstale— a projection only, never a status in the file. artifacts/acceptance-status.jsonis written byfarmslot-agent aconly. Enforcement is a per-project opt-in: withworker_terminal.acceptance.require, every criterion ininputs/handoff.jsonneeds a verdict before a terminal success mark, andweakormissingfails unless the contract also setsacceptance.allowWeak. Without it the ledger is informational — watched, shown in run detail and preferred for coverage when present, but never a reason a run cannot close. The gateway createsartifacts/on the slot so its watch has a parent directory to observe, and writes nothing into it.- Lightweight interactive dev keeps its own pairing:
CHECKLIST.mdis the operator-agreed plan andTASK.mdthe context.
See also: Agent runtime, Template variables, and Worker artifacts by flow.