# Foresttasks contract stability

What external integrators can rely on. Same document at `/docs/stability`
and the MCP resource `foresttasks://stability`. API reference: `/docs`
(OpenAPI 3.1 at `/api/v1/openapi.json`); agent workflow contract:
`/docs/agent-workflow`.

## Versioning — /api/v1 is additive-only

Within `/api/v1` changes are ADDITIVE only:

- Allowed: new endpoints, new OPTIONAL request fields, new response fields,
  new values on OUTPUT enums.
- Never: removing or renaming endpoints/fields, retyping a field, making an
  optional request field required, narrowing an input enum.

Breaking changes get a new prefix (`/api/v2`) served alongside v1 during a
deprecation window. Deprecated operations are marked `deprecated: true` in
the OpenAPI spec and announced in the changelog at least 30 days before
removal.

## MCP tool stability

The MCP surface versions INDEPENDENTLY of REST `/api/v1` (which stays
additive-only above). MCP is an internal, agent-facing surface, carried on the
server's semantic version (`serverInfo.version` at `initialize`). The
**current major is 10.x**.

- WITHIN a major, tool names + input field names are additive-only (new tools,
  new optional fields); a fresh `initialize` always serves the current
  registry. Descriptions and annotations may be reworded any time — never parse
  them.
- ACROSS a major bump, tools may be consolidated, renamed, or removed with NO
  deprecation aliases (break-and-bump) — the small internal client set is
  cheaper to migrate than a permanent compatibility layer. Such changes are
  announced in the changelog; read `serverInfo.version` and re-initialize.
- Self-healing within a major: unambiguous string primitives coerce at the call
  boundary ("true" → boolean, "5" → number), so a stale cached tools/list
  doesn't strand a client mid-session.

**2.0.0 (2026-06-15)** consolidated the surface 34 → 21 tools: the session /
command / verify clusters became single `{action}` composites; `claim_task`
+ `claim_next_ready` merged into `claim {id?}`; `set_attention` +
`set_snooze` folded into `update_task`; and `record_provenance`,
`get_task_provenance`, `record_session_event`, `resolve_human_request`
were dropped from MCP.

**3.0.0 (2026-06-15)** cull: `set_brief` + `update_brief` unified into
`brief {action: "replace" | "merge"}` (21 → 20 tools). Separately, the unused
REST endpoints `POST /sessions/{id}/events`, `GET /tasks/{id}/provenance`,
and `POST /human-requests/{id}/resolve` were removed (zero consumers — the
dashboard uses tRPC; the equivalent reads/writes remain available there). This
is the one place a non-additive REST change has shipped under v1.

**4.0.0 (2026-06-18)** optimized agent context usage. `get_task` now defaults
to `{guard, sections, readToken}` with `profile`, `include`, and explicit
`since` delta reads; `detail:"full"` still returns the legacy dossier shape.
`queue_state` returns compact repeated-call responses by default, and MCP
tool/resource JSON is minified. REST `/api/v1` is unchanged.

**5.0.0 (2026-06-18)** removed the MCP `session` lifecycle/identity tool
(20 → 19 tools). REST session endpoints remain for compatibility, but agents use
`queue_state`, `claim`, `get_task`, `command`, and the task workflow tools
directly.

**5.1.0 (2026-06-19)** added `work_start` and `work_finish` as additive
workflow packet tools. Existing primitive tools remain available. The new tools
advertise `outputSchema`, return MCP `structuredContent`, and preserve the
minified JSON text fallback in `content[0].text` for older clients.

**6.0.0 (2026-06-21)** removed the due-date and snooze task surface from MCP
tool schemas after the product-level feature removal. `list_tasks` no longer
accepts `overdue`, `snoozed`, `fields:["dueAt"|"snoozedUntil"]`, or sort
field `due`; `update_task` no longer accepts `snoozedUntil`. **Due dates
were reinstated the very next day — snooze was not; see 7.1.0 below.**

**7.0.0 (2026-06-21)** redesigned MCP task/workflow context packets.
`get_task` work-profile reads include current task descriptions and use a
lightweight immediate-parent summary for subtasks; `detail:"full"` keeps the
raw parent dossier. `work_start` gained `parentContext` modes and is the only
tool that may emit curated full parent context. `work_finish` now returns a
finish-specific slim packet and does not fetch or resend parent context, current
task prose, or rendered briefs.

**7.1.0 (2026-06-22)** reinstated the MCP due-date task surface 6.0.0 had
removed the day before (snooze stays removed). `list_tasks` accepts
`overdue` and `dueBefore` again; `create_task`/`update_task` accept
`dueAt` again. Purely additive (new optional fields back), so no major bump.

**8.0.0 (2026-06-22)** removed provenance from the MCP transport surface.
`get_task`, `work_start`, and `work_finish` no longer accept
`include:["provenance"]` and never emit a `provenance` section. Provenance is
still captured server-side and read via REST `/api/v1`, tRPC, and the web UI —
this drops only the redundant MCP echo of the trust spine.

**9.0.0 (2026-06-25)** removed the human pre-request subsystem. The MCP tool
`create_human_request` and the REST `POST /tasks/{id}/human-requests` are
gone; `get_task`/`work_start` no longer emit a `humanRequests` section or an
`openBlockingHumanRequests` guard field, and the status machine no longer gates
on open requests. The HITL **questions** flow (`ask_user`/`answer_question`,
`waiting_for_input`) is unchanged — use it for anything that needs a human.
Separately, this version also covers two consolidation removals that shipped
in the two days before it without their own bump: the standalone `claim` tool
(claim via `work_start {mode:"claim"}` instead — same claim, plus an
orient-on-claim packet) and the operator `command` queue (dropped with the
rest of the `agent_sessions`/`agent_commands` subsystem; humans steer agents
via needs-attention, questions, and comments now, not a polled queue).

**10.0.0 (2026-07-02)** consolidated the surface 28 → 8 tools — the largest
cut yet, the same "one tool, action param" move as 2.0.0/3.0.0 at scale. The
eight: `work` (`work_start` + `work_finish` → `phase:"start"|"finish"`),
`get_task` (unchanged), `find` (`list_tasks`/`search_tasks`/`subtask_tree`/
`queue_state`/`list_projects`/`my_activity`/`suggest_estimate` →
`action:"list"|"search"|"tree"|"queue"|"projects"|"activity"|"estimate"`),
`task` (`create_task`/`update_task`/`set_status`/`cascade_close`/`ask_user`/
`answer_question` → `action`), `annotate` (`add_comment`/`add_link`/
`add_relation` → `type:"comment"|"link"|"relation"`), `bulk`
(`bulk_update`/`batch_update`/`bulk_remove`/`import_checklist` → `action`),
`contract` (`brief` + `verify` → `action:"brief_replace"|"brief_merge"|
"verify_criterion"|"verify_criteria"|"verdict"`), and `kb` (unchanged).
`describe_types` and `workflow_for` were dropped from MCP (REST-only now:
`GET /api/v1/node-types` and `.../node-types/{key}/workflow`). No behavior was
lost — every folded tool's inputs/outputs are byte-identical under the new
discriminator. Break-and-bump, no deprecation aliases: read `serverInfo.version`
and re-initialize. REST `/api/v1` and OpenAPI are UNTOUCHED — every REST
endpoint keeps its 1:1 path (the two surfaces version independently).

## Authentication

`Authorization: Bearer sptk_…` — an agent API key (browser sessions are
also accepted on REST). Scopes `read | write | delete` ∩ an optional
per-project allow-list. Secrets are shown exactly once at mint; rotation
preserves the agent's identity and history.

## One error envelope

Every REST error body is `{"error": {"code", "message", "details?"}}`:

| HTTP | code | meaning |
|------|------|---------|
| 400 | VALIDATION | Schema validation failed (details = issues). |
| 401 | UNAUTHENTICATED | Missing or invalid credentials. |
| 403 | FORBIDDEN | Authenticated but lacking the capability/scope. |
| 404 | NOT_FOUND | Missing, not visible to your scopes, or malformed id (no probing). |
| 405 | METHOD_NOT_ALLOWED | Path exists, method doesn't. |
| 409 | CONFLICT | Duplicate or lost race (e.g. claim). |
| 413 | PAYLOAD_TOO_LARGE | Body over 256 KB. |
| 422 | RULE_BLOCKED | Status-machine / closure rule (details = the violated rule). |
| 429 | RATE_LIMITED | Over the rate limit — Retry-After (seconds) always set. |
| 500 | INTERNAL | Unhandled server error (opaque by design). |

MCP tool failures carry the SAME codes in-band: an `isError` result whose
text is `CODE: message` (RULE_BLOCKED appends ` rule={…}`). Transport
denials (401/405/429) are JSON-RPC error bodies with the same HTTP statuses
and headers.

## Rate limits

60-request burst, 1 request/second sustained, per principal (API key or
user), SHARED across REST + MCP. Every 429 carries `Retry-After` seconds —
back off, never retry-storm.
