# TaskChef specification

This is TaskChef's normative agent-facing contract. **MUST**, **MUST NOT**,
**SHOULD**, and **MAY** are requirements in the RFC 2119 sense. The
[advanced guide](advanced-guide.md) owns detailed user setup and operation;
[workflows](workflows.md) owns implementation walkthroughs; the
[FirstMate comparison](firstmate-taskchef-comparison.md) is dated research,
not contract.

## Terminology

| Term | Definition |
| --- | --- |
| **TaskChef** | The plugin, skills, MCP server, CLI, dashboard, and current workspace formats defined here. |
| **Dispatcher workspace** | The per-user local data project selected by `--workspace`, then `TASKCHEF_WORKSPACE`, then `~/.agents/taskchef`. |
| **Dispatcher** | The Codex task that accepts a request, routes it, records it, creates executors, and returns without supervising them. |
| **Configured project** | One routing target in `taskchef.json`, identified by canonical local path and described by `name`, optional `description`, `isGitRepository`, and `githubRepos`. |
| **Mutation preview** | A read-only, hash-bound exact before/after project-index diff required before destructive replacement, removal, or restore. |
| **State backup** | A private, verified snapshot under the dispatcher workspace's `backups/` directory. It never restores automatically. |
| **Routing** | Selecting exactly one configured project and exactly one matching native Codex project for an outcome. |
| **Delegated task** | One independently useful outcome represented by one TaskChef task UUID and snapshot. |
| **Executor** | The native Codex task created to own and perform one delegated task. |
| **Task record** | One complete JSON object in `tasks.jsonl`; it contains immutable intent/project fields and mutable identity/result fields. |
| **Marker** | The exact correlation line `<!-- taskchef_id=<lowercase full UUID> -->`; new instructions place it on the final line, immediately after the executor-skill invocation. |
| **Record-before-create** | Persisting a link-pending task before asking Codex to create its executor. |
| **Self-linking** | The executor's one-way registration of its own canonical Codex UUIDv7 from `CODEX_THREAD_ID`. |
| **Link-pending** | A working task whose `threadId` is null and `updatedBy` is `dispatcher`. |
| **Current execution state** | The latest reported executor turn and its `working`, `needs_input`, `completed`, or `failed` status. |
| **Turn timeline** | The ordered collection pairing each turn's concise request summary with its semantic result, TaskChef-generated interruption outcome, or current in-progress state. |
| **Interrupted turn** | A formerly active turn that lacked a terminal report when a newer valid turn started; TaskChef closes it with the fixed timeline-only `interrupted` outcome. |
| **Last semantic result** | The final result-history entry, exposed through the derived `lastResult` compatibility alias. |
| **Turn reference** | Required lifecycle identity for one executor prompt. It is the native Codex turn ID when available, otherwise a retained client-generated UUID. |
| **Current turn ID** | Optional Codex metadata for the reported prompt; null when native turn reading is unavailable. |
| **Dashboard** | The loopback UI derived from validated workspace snapshots, with optional local usage projections, bounded native actions, and an explicit audited manual-outcome mutation. |
| **Skill** | One packaged agent procedure: `taskchef-bootstrap`, `taskchef-dashboard`, `taskchef-delegate`, `taskchef-executor`, or `taskchef-copilot`. |

## Components and ownership

- `taskchef-bootstrap` MUST own initialization, project configuration, and
  diagnostics. It MUST NOT dispatch or report unless separately requested.
- `taskchef-delegate` MUST own routing and record-before-create delegation.
  It MUST return after creation and MUST NOT poll, supervise, or infer identity.
- `taskchef-executor` MUST own executor assignment ownership, self-linking,
  exact thread/turn identity, per-turn state reporting, failure behavior,
  privacy, and idempotency. It MUST NOT dispatch the owned assignment again.
- `taskchef-copilot` MUST own conversational outcome explanation, attention,
  and next-action recommendations. It MUST use cached normalized briefs by
  default and MUST NOT poll or persist inferred state.
- `taskchef-dashboard` MUST own manual dashboard ensure and recovery. It MUST
  NOT dispatch work or inspect task outcomes, and browser navigation failure
  MUST NOT suppress the returned dashboard URL.
- The MCP server MUST expose `ensure_dashboard`, four primary lifecycle tools,
  and the deprecated `report_result` compatibility alias specified below.
- The CLI MAY administer and inspect the workspace, but MUST NOT provide a
  second agent lifecycle protocol.
- The dashboard MUST be read-only with respect to dispatcher files except for
  the explicit audited manual-transition operation defined below.
- `docs/spec.md` is the single normative behavior source. Other documents
  MUST link here rather than redefine the contract.

## Workspace contract

TaskChef MUST manage only `AGENTS.md`, `taskchef.json`, `tasks.jsonl`, and the
optional `.taskchef-usage.json` cache inside the dispatcher workspace. It MUST
preserve unrelated paths. Dashboard process identity and Codex-session leases
MUST remain in memory and MUST NOT create owner, secret, or handoff files.

`taskchef.json` MUST have schema version 2, the following required fields, and
optional exact `dashboard` and `projectIndex` objects:

```json
{
  "schemaVersion": 2,
  "projects": []
}
```

Each project MUST contain `name`, normalized absolute `path`, boolean
`isGitRepository`, and array `githubRepos`; `description` is optional.
Names and paths MUST be unique. Git projects MUST be exact Git roots.
Repository URLs MUST canonicalize to `https://github.com/<owner>/<repository>`
and be case-insensitively deduplicated.

When present, `dashboard` MUST contain exactly boolean `autostart`. Its absence
is backward-compatible and means `true`; new workspaces SHOULD write
`{"autostart": true}`. `false` disables MCP-lifecycle autostart but MUST NOT
disable the explicit `ensure_dashboard` tool.

When present, `projectIndex` MUST contain exactly `bindings`, `exclusions`, and
`routingHints` arrays. Bindings map one `(hostId: local, projectId)` to one
configured canonical path. Exclusions retain exact removed native identities
or paths. Routing hints are bounded per canonical configured path and retain
aliases, canonical repositories, concise responsibilities, and forgotten-value
suppression. Every fact has one compact provenance record; accepted-report
facts include the exact task, thread, and current terminal turn. Absence means
empty state. Older releases reject the optional field rather than rewriting it.

`tasks.jsonl` MUST contain zero or more newline-terminated schema-4 through
schema-10 records, one per line. Schemas 4 through 9 are supported
migration/read formats; every new record and state mutation MUST write schema 10. Other schemas
or unsupported fields MUST be rejected without conversion.
Reads and writes MUST reject symlinked managed files. Mutations
MUST hold the shared workspace lock and replace state atomically; read-only
operations MUST NOT require write permission.

## Task schema

Every record MUST contain exactly these fields:

| Field | Contract |
| --- | --- |
| `schemaVersion` | Integer `10`; schema-4/5/6/7/8/9 records remain readable until explicit migration or their next mutation. |
| `id` | Unique safe TaskChef ID; delegation uses a lowercase full UUID. |
| `project` | Immutable configured-project snapshot. |
| `title` | Non-empty display title. |
| `instruction` | Non-empty executor instruction; link-pending records MUST contain the exact marker for `id`. |
| `threadId` | Null while link-pending; after self-link, canonical Codex UUIDv7. Low-level current-schema direct records may hold another durable non-provisional ID but are outside the MCP delegation journey. |
| `createdAt` | ISO 8601 creation timestamp. |
| `status` | `working`, `needs_input`, `completed`, or `failed`. |
| `summary` | Null while working; otherwise the current semantic state's non-empty summary of at most 2,000 characters. |
| `turnRef` | Null before reporting begins; otherwise the required lifecycle identity of the current turn. Self-linking journeys use a native or fallback UUID. Migrated low-level `report_result` compatibility records may retain an opaque ref equal to their opaque `turnId`. |
| `turnId` | Optional Codex metadata. When non-null it equals `turnRef`; null indicates fallback identity. |
| `updatedAt` | ISO 8601 timestamp not earlier than `createdAt` or the prior `updatedAt`; clock rollback cannot backdate a transition. |
| `updatedBy` | `dispatcher`, `mcp`, or `dashboard`. `dashboard` is valid only when the latest turn is a manual dashboard transition. |
| `turns` | Ordered oldest-first array of `{turnRef, turnId, requestSummary, startedAt, result, provenance}`. Every `turnRef` is required. `requestSummary` is null only for migrated/compatibility turns. `result` is null only for the latest working turn, a semantic `{status, summary, updatedAt}` result, or the fixed TaskChef `interrupted` outcome. `provenance` is `{kind: legacy}` or `{kind: mcp}` for ordinary turns, or the audited manual-transition record defined below. New self-linking and manual turn refs are unique. A migrated low-level opaque record may retain one final reused native-derived ref for its legacy ambiguity. |

Returned Task objects MUST additionally expose `latestTurn` as null for an empty
timeline or the final `turns` entry. They MUST derive `results` only from
semantic `needs_input`, `completed`, and `failed` turn results and `lastResult`
from the final derived semantic result. Interrupted outcomes MUST be excluded.
These projections MUST NOT be persisted in schema 10 and remain compatibility
aliases for existing callers.

## Project-index mutation and recovery

TaskChef MUST provide a targeted update that preserves every unrelated project
and replaces an explicitly supplied repository list in full. Project list JSON
MUST expose stable semantic hashes. Whole-index replacement, project removal,
and restore MUST first return an exact diff and MUST reject stale hashes,
incomplete removed-name confirmation, mismatched counts, and unconfirmed large
count collapse. Agents MUST obtain explicit user permission for every removed
project entry and verify the complete post-state.

Every actual configuration mutation and actual dashboard-server start MUST
create or deduplicate a mode-private, manifest-and-hash-verified state snapshot
before writing. Mutations MUST append bounded attribution-free prepared and
terminal audit records. Backup list, create, and verify are read-only except for
explicit creation. Restore MUST default to projects only, show a diff, require
explicit approval, and create a pre-restore safety snapshot. TaskChef MUST NOT
restore automatically. State restore authorization MUST bind the selected
backup and every selected live/source payload hash. Missing or malformed live
configuration requires config or state scope plus exact raw-state and explicit
unreadable-current confirmation; a forensic safety snapshot remains eligible
for rollback when its own integrity verifies.

The dashboard MUST distinguish an unreadable index from missing historical
project paths, warn without rewriting state, and expose recovery guidance
without backup payload contents.

## Optional usage projection

TaskChef MUST pin one tested `ccusage` version as an optional dependency. It
MUST prefer that package-local executable and MAY use an exact-version,
cache-friendly `npx` invocation when a plugin-only installation has no local
dependency. The `npx` invocation MUST only resolve the pinned native executable
and MUST NOT launch the analyzer against Codex data. TaskChef MUST execute that
native binary directly. The adapter MUST first request structured Codex output
with live-capable online pricing and retry with ccusage's bundled offline
pricing after a bounded failure. TaskChef MUST NOT parse, store, or serve raw
Codex rollout files, prompts, responses, transcripts, or reasoning. Analyzer,
npm, network, timeout, malformed output, unknown pricing, or an unresolved
thread MUST NOT block lifecycle tools or dashboard loading.

Dashboard-manual turns are administrative events, not Codex executions. They
MUST immediately report per-turn usage as unavailable, MUST NOT schedule or
store a cumulative usage boundary, and MUST NOT receive a token or cost delta.
A later executor turn MUST use the nearest preceding reliable executor
boundary, skipping any intervening dashboard-manual turns.

The mode-0600 `.taskchef-usage.json` schema 2 cache stores only normalized cumulative
token boundaries, per-turn deltas, model names, estimated cost, source version,
and freshness. It is independent of task-log schema versions so legacy logs remain
readable. Schema 1 usage caches MUST migrate in memory by discarding their
TaskChef-specific cost-coverage classification while preserving valid ccusage
cumulative estimates. Writes MUST use the workspace lock and atomic replacement. Symlinked
or unsupported cache files MUST be rejected. Writes MUST compact the derived
cache to recent task projections, recent per-turn results, and the latest
cumulative boundary. An oversized legacy cache MUST be treated as rebuildable
derived data so it cannot permanently disable usage reporting.

A linked Codex thread MAY map to multiple ccusage session segments. TaskChef
MUST aggregate only records whose primary durable thread UUID is that exact
identity; a UUID appearing only as a nested suffix MUST NOT be attributed to
the parent or child TaskChef task. It MUST retain input, cached-input, output,
reasoning-output, and total fields without adding cached or reasoning subsets
into totals a second time.

After a terminal report, TaskChef MUST mark the turn `calculating` and perform
bounded deferred reconciliation without delaying the lifecycle response.
The dashboard MUST also preload eligible derived usage in its existing service
lifecycle without requiring a task-detail request. Startup, task-log changes,
and periodic recovery passes MUST consider missing, interrupted, and
cooldown-expired records; successful current-generation records MUST remain
cached. Background work MUST be deduplicated, MUST enforce a small global
concurrency limit, MUST use bounded delayed retries, and MUST stop when the
dashboard closes.
Per-turn usage MUST be a non-negative delta between adjacent reliable cumulative
boundaries. A first recorded turn MAY use zero as its baseline. Historical
turns without boundaries and decreasing or ambiguous snapshots MUST be labeled
`unavailable`, never zero or estimated. A historical task MAY still show its
resolvable cumulative total. A boundary is reliable only after two consecutive
samples agree. Exhausted unstable sampling and a newer turn beginning before
stabilization MUST leave that turn unavailable and MUST NOT establish a delta
baseline. Zero is valid for the first turn only when TaskChef observed that turn
in progress before its terminal report; a first historical terminal turn MUST
remain unavailable even when its cumulative task total is resolvable.

Every available projection MUST identify ccusage, its version when available,
the requested online or fallback offline pricing mode, and usage freshness.
The online mode records TaskChef's request, not a claim about ccusage's internal
cache. Dollar values MUST be labeled API-equivalent estimates. Positive token
usage with a zero or missing analyzer cost MUST display cost unavailable, not
`$0.00`. A per-turn dollar delta MUST be unavailable when its adjacent
cumulative boundaries use different analyzer versions or requested pricing
modes. When ccusage supplies a valid cumulative estimate, TaskChef MUST display
it without applying its own model-family or cache-write coverage policy. A model
family change between otherwise compatible boundaries MUST NOT prevent TaskChef
from deriving the per-turn estimate.
The dashboard Settings response MUST report the provider version returned by
the executable resolved for runtime use, not merely copy the dependency pin.

Task IDs and non-null thread identities MUST be unique. The immutable intent
fields MUST NOT change after recording.

## Required lifecycle

Before the canonical TaskChef MCP server connects and exposes its transport, it
MUST invoke the same serialized dashboard ensure path once by default. It MUST read the
canonical configuration and skip this only for explicit
`dashboard.autostart: false`. Initialization failures, invalid workspace state,
port conflicts, and dashboard errors MUST NOT prevent tool registration or MCP
availability. They MUST emit only a bounded non-sensitive diagnostic through
the MCP process logging channel. MCP initialization MUST NOT open a browser.
If transport connection fails after dashboard startup, the MCP server MUST
best-effort close its manager and partially attached transport, then propagate
the original connection failure even when cleanup also fails. Closing that
individual MCP MUST NOT directly close a valid Codex-session dashboard.

At the start of every dispatcher turn, the dispatcher SHOULD call
`ensure_dashboard` best-effort. Failure MUST NOT block direct TaskChef answers,
reporting, or delegation. Every dispatcher final response MUST end with the
exact clickable `[TaskChef Dashboard](http://127.0.0.1:3210/)` link even when
ensure failed. A created-thread directive MUST remain on its own line before
the final link, preserving the delegate skill's immediate-return contract.

1. The dispatcher MUST list native projects once per batch and call
   `reconcile_projects` once with that exact schema-2 snapshot.
2. It MUST call `prepare_dispatch` once per outcome and choose exactly one
   configured project. Before `record_task` or native creation, the selected
   native project's exact `(hostId, projectId, canonical path)` MUST occur in
   the reconciliation response's `available` array. Presence in the native
   snapshot or configured preparation is insufficient. Curated and learned
   routing matches are both ambiguous when more than one project matches.
3. It MUST build the instruction with the user's outcome beginning on line 1
   and remaining uninterrupted, followed by one blank line and the authorization
   `Report this task and its follow-ups to my local TaskChef dashboard. Relevant private-repository links and concise work, test, and deployment results are authorized; exclude secrets.`
   followed by exactly two newline characters
   (one blank line), exactly one concise explicit `$taskchef-executor`
   invocation, one newline, and the returned marker on the final line. It MUST
   NOT place a blank line between the invocation and marker or inline the
   executor protocol into a new instruction.
4. It MUST call `record_task` with `threadId: null` before native creation.
5. It MUST create exactly one native Codex executor using the resolved
   Orchestrator preference when configured, then return immediately.
6. The executor MUST read its own `CODEX_THREAD_ID` and call `link_task`
   before substantive work. It MUST NOT use parent/session identity or guess.
7. After initial linking, the executor MUST establish a `turnRef`. It MUST use
   the native Codex turn ID for both `turnRef` and `turnId` when available;
   otherwise it MUST retain a fresh UUID `turnRef` and use `turnId: null`.
   It MUST call `report_state` with that identity, `working`, and no summary before work.
8. Before ending, it MUST call `report_state` with the same `turnRef` and
   `turnId` metadata as the working report and a
   semantic status and concise summary.
9. A follow-up MUST report `working` with a new `turnRef` before work.
   It MUST NOT reuse a prior prompt's ref. If the preceding turn is still unfinished,
   TaskChef MUST atomically close it as `interrupted` before appending the new
   working turn; the executor MUST NOT report semantic `failed` for recovery.

For repository work, the visible executor MUST route an explicit combined
investigate-or-plan-and-implement request through a fresh Planner, then a fresh
Implementer, then a fresh Reviewer. A direct implementation request, or a later
request to implement prior investigation or planning, MUST skip the Planner and
use a fresh Implementer followed by a fresh Reviewer in the same visible task.
It MUST resolve the applicable role preference immediately before creating a
child when agent TOML exists; absent agent configuration inherits the parent's
model settings without requiring Python. It MUST keep at most one writing agent
active and verify child results. TaskChef persists only the visible executor's
lifecycle and MUST NOT store or infer child phases, identities, or usage.

Normal executor completion MUST stop after the terminal callback and return
normally. It MUST NOT archive, hand off, close, navigate away from, or otherwise
terminate the Codex task merely because work completed. Archive is authorized
only by an explicit request in the current assignment or follow-up for that
exact Codex task; `finish`, `complete`, `done`, `ship`, and ordinary cleanup do
not imply authorization.

For an explicitly authorized archive, the executor MUST finish and verify the
work, read its exact task identity, submit the current turn's terminal
`report_state`, verify TaskChef accepted that state, and only then invoke the
native archive operation as the final state-changing action. It MUST require
native confirmation before claiming archive succeeded. The same callback-first
ordering applies to any separately authorized action that can make the executor
unavailable before reporting, including handoff or terminating or restarting
the process that owns the TaskChef MCP transport; this rule does not itself
authorize those actions. A terminal reporting failure MUST prevent the later
action and leave the executor accessible. Failure of a later action MUST NOT
change or reopen the already accepted semantic terminal state.

Request and result summaries are the durable source for dashboard related-link
projection; TaskChef does not scan full Codex transcripts. When known, an
executor MUST preserve the selected repository as a canonical GitHub repository
URL and issues or pull requests as canonical URLs. A result spanning both a
managed child repository and its workspace/root repository MUST include both
pull-request URLs. Executors MUST NOT guess unresolved repository identity.

If native creation fails after recording, the dispatcher MUST call
`report_state` with `failed`, a retained fallback UUID `turnRef`, null thread
and Codex turn IDs, and a bounded summary.
A link failure MUST remain visible and retryable; the executor MUST report it
visibly and MUST NOT continue substantive work.

Previously recorded instructions with the trailing marker before the
invocation, with or without the former blank line before that marker; a
former compact assignment-to-invocation boundary with the marker last; a
first-line HTML marker; the older first-line
`# taskchef_id=<full UUID>` heading, or the former blank line and inline
executor protocol MUST remain marker-readable and executable. Their
`report_result` calls MUST remain supported by the deprecated alias. New
instructions MUST use the explicit executor invocation followed by the final
trailing marker contract above. A historical first-line instruction with an
executor-skill invocation MUST contain exactly one invocation as its final
line. A former inline-protocol instruction MUST retain non-whitespace
task-specific content beyond its known lifecycle paragraphs.

`needs_input` MUST mean a semantic user decision or missing fact. A native
approval prompt MUST remain live Codex state and MUST NOT be stored as
`needs_input`.

## MCP interface

All tools resolve the workspace internally. Callers MUST NOT supply a workspace
path. Success returns both one text content item and the stated structured
object. Validation, marker, identity, uniqueness, freshness, or filesystem
failures are surfaced as tool errors and MUST NOT partially mutate the log.

### `ensure_dashboard`
**Caller:** dispatcher. **Mutation:** starts or reuses the
Codex-session-scoped loopback dashboard.

**Input:** empty object.

**Structured output:**

```text
{ dashboard: {
  action: "started" | "reused",
  launcher: "session" | "standalone" | "mcp",
  url: "http://127.0.0.1:3210/",
  workspace: string,
  taskchefVersion: string,
  serverVersion: string
} }
```

MCP initialization MUST complete its best-effort ensure before exposing the
MCP transport. Calls MUST serialize within one MCP process. The first call
starts a dashboard session process or reuses the exact current listener; later
and concurrent calls are idempotent. The stable default MUST bind only to
`127.0.0.1:3210` and MUST NOT accept a model-supplied workspace, host, port,
session PID, or credential.

Exact reuse MUST require the fixed service/schema, TaskChef version,
dashboard-server version, canonical workspace, and recognized launcher identity.
A `session` listener MUST register the MCP's original Codex parent PID in its
in-memory lease. Every launcher MUST complete another exact health probe before
ensure reports `reused`, and the response MUST report the observed launcher.
The independent dashboard MUST survive closure of an individual MCP transport.
The session manager MUST require an explicit nonzero port because a detached
child cannot safely return an ephemeral bound port without an additional IPC
readiness channel. Direct foreground server callers MAY continue to bind port
zero when they retain the returned listener.

The dashboard MUST track only same-version explicitly registered PIDs using non-signalling
existence probes. Once every registered PID is absent for the grace period, it
MUST gracefully close its HTTP listener and exit. A new live
registration during the grace period MUST cancel expiry. This is a local
best-effort session guard, not a Codex restart guarantee.

When exact structured health identifies an older TaskChef dashboard for the
same canonical workspace, the new MCP MUST request simple graceful shutdown,
wait a bounded time for port release, and start its installed `session` version
within the same ensure operation. It MUST NOT transfer in-memory PID leases
across versions. The replacement initially knows only the activating session;
a later ensure MAY register another live session or restart the dashboard after
the activating lease expires.

A recognized newer TaskChef listener MUST NOT be downgraded. Concurrent
activation and replacement MUST converge through health probing and exclusive
port binding. Unknown, malformed, different-workspace, or unrelated occupants
MUST receive no control request or process signal and MUST remain untouched.
TaskChef MUST NOT discover or kill port owners.

TaskChef's dashboard uses a trusted, single-user localhost threat model. Exact
bounded health identity is sufficient service recognition and MUST NOT be
described as cryptographic authentication. The dashboard MUST NOT create or use
an owner secret, HMAC challenge, durable ownership record, handoff record,
prepare/commit protocol, recovery lease, or cross-version lease transfer.

The session-registration and shutdown controls MUST remain loopback-only and
MUST require the exact listener Host and Origin, JSON content type, a non-simple
TaskChef control header, and an exact bounded request body containing the exact
structured health identity observed by the caller. The receiver MUST compare
that expected identity with its own identity before registering a session or
accepting shutdown. It MUST refuse a mismatch, and the manager MUST probe again
before applying lifecycle rules to the current occupant. These request-shape
checks protect against ordinary cross-site browser requests; they MUST NOT be
described as authentication. Future LAN access is outside this contract and
requires a separate opt-in design with password-backed user authentication and
suitable transport protection.

A listener using the former HMAC control protocol will refuse the new simple
shutdown request. It MUST be reported as a recognized older TaskChef conflict
and left running. Crossing that one-time compatibility boundary requires the
verified legacy listener to stop through its existing lifecycle before ensure
is retried; TaskChef MUST NOT add a process-kill fallback.

Detached session startup MUST use a private child readiness acknowledgement
sent only after listener bind, initialization, and initial in-memory lease
creation. Startup errors MUST propagate through that channel. A bounded
readiness timeout MUST cooperatively cancel the exact child so late
initialization cannot strand an unreported listener.

MCP EOF, explicit close, protocol close, SIGINT, SIGTERM, transport-start
failure, and MCP-parent loss MUST still bound MCP shutdown. They MUST close the
MCP manager and transport without directly closing the independently hosted
session dashboard. Foreground `taskchef dashboard` remains `standalone`, is
never adopted by MCP, and follows its foreground process lifetime.

The packaged `$taskchef-dashboard` skill MUST call this tool, report `started`
or `reused`, and return the canonical clickable URL. It MAY use an available
in-app browser when permitted; absent or blocked browser navigation MUST fall
back to the link without failing. It MUST NOT inspect task outcomes or dispatch.

**Annotations:** `readOnlyHint: false`, `destructiveHint: false`,
`openWorldHint: false`.

The complete operational rationale and limits are documented in
[Dashboard lifecycle](dashboard-lifecycle.md).

### `prepare_dispatch`

**Caller:** dispatcher. **Mutation:** none.

**Input:** empty object.

**Structured output:**

```text
{ preparation: {
  schemaVersion: 1,
  workspace: string,
  taskId: string,
  preparedAt: string,
  marker: string,
  projectCount: number,
  projects: Project[],
  routingHints: {path, aliases[], githubRepos[], responsibilities[]}[]
} }
```

The tool generates a fresh task UUID and exact marker and returns current
routing targets. An invocation is not idempotent: each successful call creates
new preparation values, though it writes no state.

**Annotations:** `readOnlyHint: true`, `destructiveHint: false`,
`openWorldHint: false`.

### `record_task`

**Caller:** dispatcher. **Mutation:** atomically appends one task.

**Input:**

| Field | Type and rule |
| --- | --- |
| `id` | Non-empty string; MUST equal the instruction marker. |
| `project` | Non-empty configured project path. |
| `title` | Non-empty string. |
| `instruction` | Non-empty string containing exactly one accepted marker and a non-empty assignment. New instructions use the required trailing marker and executor-invocation scaffold; historical first-line forms remain accepted. |
| `threadId` | Literal null. |

**Structured output:** `{ task: Task }`.

The returned task has schema 10, `working`, null summary/turn/thread/latestTurn/lastResult,
empty `turns` and derived `results` arrays,
`updatedBy: dispatcher`, and equal creation/update timestamps. Duplicate IDs,
unknown projects, malformed markers, and invalid input fail. Repeating a
successful call is not idempotent; it fails as a duplicate.

Configuration is read structurally so unavailable unrelated entries do not
block preparation or recording. `record_task` validates the selected actual
directory or Git root before snapshotting it.

**Annotations:** `readOnlyHint: false`, `destructiveHint: false`,
`openWorldHint: false`.

### `reconcile_projects`, `include_project`, and `update_project_hint`

`reconcile_projects` accepts one agent-supplied native Codex schema-2 project
snapshot. Node does not call native tools or inspect private Codex state. It
considers only local projects with `hostId: local`, canonicalizes each eligible
path, excludes the dispatcher and its ancestors, isolates per-project failures,
and commits additions and exact identity bindings under the configuration lock.
It preserves curated metadata and absent projects. Repeated identical or empty
snapshots do not write. Duplicate IDs, canonical targets, or identity/path moves
produce bounded diagnostics. Its MCP response contains counts, current eligible
targets, diagnostics, and changed state rather than full configuration copies.

Removing a configured project records an exclusion. `include_project` removes
one exact exclusion by native project ID or canonical path; a later reconcile
must validate and add the target. A move is handled through exact preview-bound
removal, explicit inclusion, and reconciliation of the new path.

`update_project_hint` remembers, corrects, or forgets one bounded fact. Aliases
require explicit user evidence; correction is the only operation that removes
the same alias from another project. Repository ownership requires an inspected
exact origin from the selected project or a contained Git root. Responsibilities
require an accepted current terminal task/thread/turn. Forgetting requires
explicit user evidence and leaves bounded suppression against stale report
replay. Learning remains separate from terminal reporting and a learning error
cannot undo an accepted result.

### `link_task`

**Caller:** executor. **Mutation:** one atomic identity transition.

**Input:** `{ taskId: non-empty string, threadId: non-empty string }`.
The workspace layer requires `threadId` to be a canonical Codex UUIDv7.

**Structured output:** `{ task: Task }`.

The eligible record MUST be link-pending, working, dispatcher-written, and have
the exact marker. The thread ID MUST be unused. Success sets `threadId`,
refreshes `updatedAt`, and sets `updatedBy: mcp`. An exact successful retry
is idempotent. A different identity, terminal record, reused identity, missing
marker, or ineligible state fails.

**Annotations:** `readOnlyHint: false`, `destructiveHint: false`,
`openWorldHint: false`.

### `report_state`

**Caller:** executor, or dispatcher only for native creation failure.
**Mutation:** replaces the current state atomically and preserves `turns`.

**Input:**

| Field | Type and rule |
| --- | --- |
| `taskId` | Non-empty string. |
| `threadId` | Matching non-empty ID for a linked task; null only for creation failure. |
| `turnRef` | Stable UUID for this prompt. It may be omitted only by legacy callers that supply a non-null `turnId`, in which case TaskChef derives the same value. |
| `turnId` | Optional Codex metadata. When present it MUST equal `turnRef`; null is valid for fallback UUID refs. Maximum 256 characters. |
| `status` | `working`, `needs_input`, `completed`, or `failed`. |
| `summary` | Omitted or null for `working`; required non-empty string of at most 2,000 characters otherwise. Known GitHub issues and pull requests use canonical URLs. |
| `requestSummary` | Concise current request of at most 1,000 characters for `working`; optional for backward compatibility and omitted for semantic states. A known selected GitHub repository uses its canonical URL. |

**Structured output:** `{ task: Task }`.

For a linked self-linking journey, a new `working` state MUST use a `turnRef`
not already assigned to a different turn. An exact retry of the current
working start MUST return the current task without mutation.
When `turnId` is present, its native-backed `turnRef` MUST also be newer than
every previously stored native-backed ref. Fallback UUID refs are opaque and
MUST NOT be lexically ordered.
A semantic state MUST match the current working turn. Conflicting or stale
state fails. A null-thread record accepts only a fresh executor creation
`failed` state with a retained `turnRef` and null thread/turn IDs. Starting work appends one turn with
its request and a null result. When the previous latest turn is unfinished, the
same locked atomic rewrite MUST first fill it with the fixed TaskChef-generated
`interrupted` outcome. The semantic report fills the active turn's result.
An identical retry for the current settled turn returns success without an
append. Once a newer turn starts, every callback for a historical ref is stale
and fails, even when its content exactly matches the stored callback. A
different request or result for the same turn, a stale turn, or a semantic
result that does not match the active working turn MUST fail.

`interrupted` MUST NOT be accepted as MCP input or projected as task `status`.
It MUST NOT alter `summary`, `results`, or `lastResult`, and its fixed summary
MUST contain no crash output, transcript, user text, or inferred failure cause.

**Annotations:** `readOnlyHint: false`, `destructiveHint: true`,
`openWorldHint: false`.

Reporting tool descriptions MUST identify the configured local `tasks.jsonl`
path and local `.taskchef-usage.json` tracking side effect. GitHub URLs remain
references in that log; reporting does not publish them to GitHub. The
`destructiveHint: true` annotation is retained because reports update existing
state and can recover an unfinished predecessor as interrupted; these tools
are not purely additive. The compatibility alias also changes existing state.
Executors MUST verify accepted terminal reporting before claiming TaskChef was
updated. A platform rejection of an authorized report MUST be described using
only the returned rationale, without inferring causes or retrying around an
explicit denial. Authorization and unit tests do not guarantee platform approval.

### `report_result` (deprecated)

`report_result` retains the prior semantic-only input shape and statuses as a
temporary compatibility alias. It implicitly accepts a fresh supplied turn and
stores its semantic result in a request-unknown turn, including for supported schema-4/5/6 records and
low-level opaque direct records. It does not accept `working`. New executor
instructions MUST use `report_state`. Successful mutation upgrades schema 4-9
to schema 10; unsupported schemas remain rejected. Legacy callers that omit
`turnRef` remain compatible when `turnId` is non-null.

## Copilot and dashboard

A semantic result is cached evidence, not permanent live truth. The dashboard
MUST remain the primary monitoring and browsing UI. Copilot MUST start from the
schema-1 normalized cached brief and explain what finished, what needs
attention, why, and the recommended next action. It MUST NOT need to interpret
historical task schema versions. A working task's current summary MUST remain
null; any prior semantic result MUST be exposed separately as a clearly
historical `lastOutcome`. A link-pending task with no exact thread identity MUST
recommend passive waiting or inspection, never retry or continuation.

Copilot MAY take one bounded native metadata snapshot only when the user
explicitly requests fresh/live verification or a focused task presents a
meaningful contradiction. Active or approval-waiting native state overrides
cache. An inactive task does not prove completion. Copilot MAY read the exact
selected task once for an explicit focused live-verification request or to
resolve a focused contradiction. It MUST NOT poll, wait, perform exhaustive
live audits, or classify assistant prose.

Copilot MAY identify the exact existing executor, explain or draft a
same-assignment follow-up, and continue that executor only with explicit user
authorization. It MUST NOT automatically retry failures, interrupt working
tasks, or redelegate an existing executor. Independent new work MUST route
through delegation. Managed dispatcher routing MUST give same-assignment
answer, follow-up, resume, and continue requests precedence over the blanket
new-work delegation rule. A direct imperative naming the exact existing task
MAY constitute send authorization, but copilot MUST re-read that exact task
immediately before sending.

The published plugin MUST NOT package `taskchef-report` as a discoverable alias.
Its historical explicit name is a documented rename hint handled by copilot,
not a second workflow.

Task lists, summaries, and broad briefs MUST use the final result by default.
The dashboard MUST bind only to loopback, validate the current workspace
snapshot, and avoid sessions or shared client state. `GET /api/health` MUST
return only the bounded service identity, health schema, exact TaskChef and
dashboard-server versions, and canonical workspace. It MUST NOT return task
records, secrets, credentials, environment values, or process-control data.
Identity remains available while an already-started monitor retains its last
valid snapshot after a later invalid task log; an invalid initial log MAY fail
startup safely. Direct thread navigation
MUST require a canonical Codex UUIDv7. Otherwise it MAY open the revalidated
configured project. Project paths from task history MUST be matched against
current configuration before use.
The task-detail dashboard MAY offer manual outcomes from `working` or
`needs_input` to either `completed` or `failed`, from `completed` to `failed`,
and from `failed` to `completed`. It MUST reject same-state terminal
transitions and MUST keep this infrequent administrative action out of list
cards. A keyboard-accessible **More task
actions** disclosure MUST reveal its action list immediately beside it and
change from an ellipsis to an accessible back/hide control while expanded. The
list MUST group **Copy Task ID** and each currently valid **Mark completed** or
**Mark failed** action. **Archive chat** MUST remain hidden while the archive capability
gate is disabled because the bundled CLI does not reliably archive desktop-app
threads. The dormant server endpoint MUST reject requests before discovering or
invoking the CLI. Re-enabling requires a reliable supported app-callable archive
interface or guaranteed CLI compatibility. Opening the
list is the deliberate disclosure step; choosing a terminal outcome submits it
immediately without a second confirmation. Escape MUST close the idle list,
pending controls MUST be
disabled, and failure feedback MUST remain in the dialog. Pending state MUST
focus and announce a stable status inside the dialog. Failed MUST have
destructive styling. The client MUST bound and abort a stalled manual-transition
request, restore the dialog controls, and preserve the action ID so a retry can
resolve idempotently if the server committed before the timeout.

`POST /api/tasks/:id/manual-transition` MUST require the exact loopback origin,
`application/json`, a bounded body, and exactly this versioned shape:
`{schemaVersion: 1, actionId, expected: {status, turnRef, threadId, updatedAt},
targetStatus}`. The server MUST compare every expected field while holding the
workspace lock. A stale request, invalid transition, or reused action ID with a
different operation MUST return a conflict without mutation. Replaying the
same committed action ID and operation MUST return the current task as an
idempotent success. The operation MUST atomically append one new manual turn;
it MUST NOT overwrite executor history. An active unfinished turn MUST first
receive the standard interrupted timeline outcome. The manual turn MUST have a
new server-generated `turnRef`, null `turnId`, one monotonic timestamp for its
start, result, and task update, `updatedBy: dashboard`, and deterministic
request/result summaries. It MUST record provenance
`{kind: dashboard_manual, actionId, fromStatus, toStatus, expectedTurnRef,
expectedThreadId, expectedUpdatedAt}`. No free-form reason is collected or
persisted. A committed write remains successful if the subsequent best-effort
monitor refresh or notification delivery fails.

The task-detail dashboard MAY offer Codex chat archival only when the stored
thread ID is a canonical Codex UUID and the current TaskChef state is not
`working`. It MUST revalidate both conditions for the POST request, require the
exact loopback origin, and obtain explicit user confirmation in the browser.
It MUST invoke the `archive` subcommand by directly executing only a Codex CLI
inside the canonical ChatGPT or Codex desktop application bundle under macOS
`/Applications`, with the thread UUID as a separate argument. It MUST NOT use a shell, a generic `PATH` fallback, a
private desktop endpoint, or direct session-file manipulation. Successful
archival MUST NOT modify dispatcher files or remove the TaskChef task record.
The UI MUST disclose that spawned descendant chats may also be archived and
that TaskChef history remains available.
Snapshot and SSE list payloads MUST omit full `turns` and derived `results`
history and include `latestTurn`. The bounded per-task detail endpoint MAY
return the full validated task so the dialog can render the paired timeline newest first.
Dashboard notifications MUST capture an immutable event-time projection of the
task title, lifecycle state and event, turn ref and optional Codex turn ID, event timestamp,
and relevant concise summary. Rendering MUST NOT resolve historical notice text
from the task's later current state. Notice identity and deduplication MUST use
task ID, turn ref, and lifecycle event; a creation notice without a turn ref MUST
fall back to task ID plus the immutable creation timestamp. Dashboard revision
MUST NOT be the sole identity. Identical snapshots, SSE reconnects, idempotent
reports, schema normalization, and non-semantic rewrites MUST NOT add notices.
Distinct working and semantic-result events for one turn MAY each be retained.
When one compact snapshot first exposes both a latest semantic result and a
newer working turn, the browser MUST reconcile both events. Temporary task
absence MUST NOT discard the prior semantic signature or turn a later
reappearance into another creation event. The first page snapshot MUST establish
a quiet baseline rather than replay existing history. After that baseline, a
new task first observed with a turn or semantic result MUST retain its creation
event and each lifecycle event observable in that compact projection.

Working with no prior result SHOULD be labeled as a task start. Working on a
newer turn while a prior result remains projected SHOULD be labeled as a
follow-up start. Creation, completion, input-needed, and failure labels MUST be
distinct. The Updates panel MUST remain bounded and support individual dismiss
and clear-all without resetting replay protection. A retained notice whose task
is absent from the current list MUST remain readable; selecting it MUST NOT
navigate or mutate data and SHOULD explain that current details are unavailable.
The retained notice list MUST NOT be a live region that re-announces old notices
when it rerenders. A separate polite status region MUST announce only newly
reconciled events. Each notice control's accessible description MUST include
its displayed summary when present, event time, and missing-task state.

## Task-log migration

`workspace migrate` MUST explicitly convert every supported schema-4/5/6/7/8/9 record
under the shared lock. Each legacy semantic result becomes a request-unknown
completed turn; a newer working state becomes a final unfinished turn, and a
schema-7/8 timeline is preserved. Each non-null legacy `turnId` becomes the
same `turnRef`; each null legacy `turnId` receives one durably persisted UUID.
Migration MUST validate the complete source, record/turn counts, turn-ref
invariants, and complete schema-10 candidate before changing the task log,
create and read back an exclusive recovery backup, atomically replace the log,
and validate the installed result. A fully schema-10 log MUST be an idempotent
no-op without another backup. Invalid/unsupported input MUST remain untouched;
failures after backup creation MUST report the backup path and MUST never
partially rewrite individual lines.

## Concurrency and trust

All task and configuration writers MUST share one workspace lock. Duplicate ID,
duplicate thread, link, and result freshness checks MUST occur while holding
that lock. Atomic replacement MUST preserve valid unrelated records.

TaskChef is designed for a local, single-user boundary. Executor self-linking is
a cooperative assertion, not transport-authenticated proof. Task instructions,
stored project snapshots, and dashboard requests are untrusted input.
Implementations MUST validate exact shapes, canonical paths, safe IDs, markers,
loopback origin, and current configuration before acting.

TaskChef MUST NOT use lifecycle hooks, schedules, polling, recent-thread search,
transcript search, title matching, hidden reasoning, or token usage to discover
identity or infer semantic results.

TaskChef MUST NOT install or require daemons, launchd agents, login items,
system services, cron jobs, hooks, privileged components, or elevated/system
permissions for dashboard availability. Availability is best-effort while a
registered Codex session process is alive and is not guaranteed while Codex is
closed.

Releases using the former HMAC dashboard control protocol are legacy listeners.
A verified legacy listener MUST be reported distinctly from an unknown port
conflict and MUST remain running when it refuses simple graceful shutdown. One
final stop through that listener's existing lifecycle is required when crossing
this boundary. Releases implementing simple localhost control replace older
recognized versions automatically.

Installing or replacing plugin files MUST NOT be described as activating the
new MCP code. Release verification MUST install the plugin, activate or reload
the new MCP process, ensure the dashboard, and verify the expected TaskChef
version, dashboard protocol `serverVersion`, reported launcher, canonical workspace,
and canonical URL. Exact-compatible session-listener reuse remains valid; installation MUST NOT be
claimed to reload Codex automatically.
