WIP: The ACP brief carries the description but never the work item's identity — sessions orient on whichever WI code is most salient in thread history #612

Draft
toasterson wants to merge 1 commit from claude/wi-01a005fc-the-acp-brief-carries-the-description-bu into main
Owner

Anima work item 01a005fc-8c42-78b3-82e5-e6a31d2faa6d.

Moved here from akh-medu WI-256 (019f9f56-52f3), which had the diagnosis right and the repo wrong. This is runner/server work, not engine work.

Symptom

Agent sessions narrate and commit against a different work item than the one they were dispatched for. Both observed 2026-07-26:

  • Session 019f9e7c dispatched on WI-252 closed with "I have successfully implemented WI-250".
  • Session 019f9eea dispatched on WI-254 committed "Add post-condition guard for idle agentic turn completion (WI-249)".

Neither number is random: WI-249, WI-250 and WI-252 are real, adjacent akh-medu items whose threads and branches earlier sessions touched.

The immediate cost is traceability — a commit citing the wrong item breaks the audit trail the whole phase model depends on. The deeper cost is that a session which has mis-identified its task will satisfy the remembered brief rather than the given one.

It is not hypothetical or rare. The two akh-medu sessions that attempted to fix this reported implementing WI-254 and WI-253 while dispatched on WI-256. The bug reproduced itself inside its own fix, twice.

It is not KG leakage

The obvious hypothesis is knowledge-graph recall substituting prior work-item facts. The evidence rules that out for these cases: both sessions ran on the claude-code executor, which has no akh knowledge graph at all.

The actual mechanism — verified in this repo on dc3a5f9a

The dispatch path already does the right thing with the content. assemble_brief (crates/anima-server/src/scheduler.rs:3078) is called with title and description:

let mut brief = assemble_brief(
    &item.title,
    &item.description,
    &prior_lessons,
    &review_feedback,
);

and builds:

let components = anima_ai::prompt::PromptComponents {
    intent: format!("{title}\n\n{description}"),
    
};

which anima_ai::prompt::assemble (crates/anima-ai/src/prompt.rs:52) renders as # Intent\n\n{title}\n\n{description}, plus File references / Review feedback / Prior lessons / Phase prompt. The runner then prepends the worktree preamble and sends the whole thing as first_prompt over ACP (crates/anima-runner/src/slot.rs:794).

So the full description is already delivered via the ACP prompt. That part works and needs no change.

What the assembled brief does not contain, anywhere, is the work item's identity: no WI-nnn code, no UUID. intent is title + description, and a title is prose. item.code exists on the model (crates/anima-db/src/repo/workitem.rs:97, pub code: Option<String>) and is simply never passed to assemble_brief.

That is the whole bug. The session is handed a prose title, while the thread history and any inherited PR it can see are saturated with explicit WI-nnn codes belonging to other items — because these work items routinely reuse pre-existing PRs from earlier attempts (#256 and #240 were reused rather than created). When the model needs to name its task, the most salient work-item identifier in its context belongs to someone else, and nothing in the brief contradicts it.

The same omission is in the reviewer path: assemble_reviewer_brief (scheduler.rs:2910) writes **Title:** {title} with no code and no id either.

Required change

  1. Pass the identity into both briefs. assemble_brief and assemble_reviewer_brief take the code and the work item UUID alongside title and description. Both are already in scope at their call sites.
  2. Render it unambiguously and first. The intent section should lead with the identity — e.g. # Intent\n\n**WI-256** (019f9f56-52f3-7872-8025-f785dd2ccf1e) — {title} — so it is the first work-item identifier the model reads, not the twentieth.
  3. Repeat it late. Per Till's direction on the original item: the identity belongs at a position that survives context compaction, not only in a preamble a long turn pushes out of the window. Emitting it once at the top is necessary but not sufficient; consider a short trailing restatement after the phase prompt.
  4. Name the failure mode in the brief. State explicitly that thread history, review feedback and existing branches may reference other work items, and that those are not this session's task. This costs a sentence and directly addresses the observed confusion.

Why not fix it on the akh-medu side

akh-medu WI-256 tried, and it was the wrong repo. Its round-2 attempt wired akhomed to detect an Anima session via ANIMA_RUNNER_TOKEN and then call the mcp__anima__get_work_item MCP tool at init to fetch its own identity. The reviewer rejected it — correctly on the mechanics (it called agent.call_tool(), which does not exist on Agent, so it did not compile) and correctly in principle:

"the work item context should instead be provided by the Anima runner through the session dispatch mechanism (environment variables, command-line arguments, or initialization protocol)"

What that reviewer could not know, reviewing inside akh-medu, is that the runner already does exactly that for title and description — the transport is the ACP prompt itself, and it has been all along. The missing piece was never a delivery mechanism. It was two fields.

An MCP round-trip at akhomed init would also be strictly worse: it needs auth before the session is usable, and it introduces a failure mode where the lookup fails and the akh boots with no identity at all.

Acceptance

  • The assembled implementer brief and reviewer brief both contain the work item's code and UUID.
  • Unit tests on assemble_brief and assemble_reviewer_brief assert the identity is present (the existing assemble_reviewer_brief_* tests in scheduler.rs:3626+ are the pattern to follow).
  • The identity appears both early and late enough to survive compaction of a long turn.
  • Spot-check after deploy: a session on a work item with an inherited thread or a pre-existing PR reports its own code. akh-medu WI-256's own history is the natural regression fixture — it has both.
  • akh-medu WI-256 (019f9f56-52f3) — origin; cancelled in favour of this item
  • akh-medu WI-220 — no structured tool-call telemetry from akh sessions, which is why the tecton-lane KG hypothesis remains untestable. Not a blocker here: this item fixes the demonstrated non-KG path.
  • akh-medu WI-221 — per-session SessionContext; unrelated but frequently confused with this in the threads.
Anima work item `01a005fc-8c42-78b3-82e5-e6a31d2faa6d`. Moved here from akh-medu **WI-256** (`019f9f56-52f3`), which had the diagnosis right and the repo wrong. This is runner/server work, not engine work. ## Symptom Agent sessions narrate and commit against a **different work item than the one they were dispatched for**. Both observed 2026-07-26: - Session `019f9e7c` dispatched on **WI-252** closed with *"I have successfully implemented WI-250"*. - Session `019f9eea` dispatched on **WI-254** committed *"Add post-condition guard for idle agentic turn completion (WI-249)"*. Neither number is random: WI-249, WI-250 and WI-252 are real, adjacent akh-medu items whose threads and branches earlier sessions touched. The immediate cost is traceability — a commit citing the wrong item breaks the audit trail the whole phase model depends on. The deeper cost is that a session which has mis-identified its task will satisfy the *remembered* brief rather than the *given* one. It is not hypothetical or rare. The two akh-medu sessions that attempted to *fix* this reported implementing **WI-254** and **WI-253** while dispatched on **WI-256**. The bug reproduced itself inside its own fix, twice. ## It is not KG leakage The obvious hypothesis is knowledge-graph recall substituting prior work-item facts. **The evidence rules that out for these cases:** both sessions ran on the `claude-code` executor, which has no akh knowledge graph at all. ## The actual mechanism — verified in this repo on `dc3a5f9a` The dispatch path already does the right thing with the *content*. `assemble_brief` (`crates/anima-server/src/scheduler.rs:3078`) is called with title and description: ```rust let mut brief = assemble_brief( &item.title, &item.description, &prior_lessons, &review_feedback, ); ``` and builds: ```rust let components = anima_ai::prompt::PromptComponents { intent: format!("{title}\n\n{description}"), … }; ``` which `anima_ai::prompt::assemble` (`crates/anima-ai/src/prompt.rs:52`) renders as `# Intent\n\n{title}\n\n{description}`, plus File references / Review feedback / Prior lessons / Phase prompt. The runner then prepends the worktree preamble and sends the whole thing as `first_prompt` over ACP (`crates/anima-runner/src/slot.rs:794`). **So the full description is already delivered via the ACP prompt.** That part works and needs no change. What the assembled brief does **not** contain, anywhere, is the work item's **identity**: no `WI-nnn` code, no UUID. `intent` is title + description, and a title is prose. `item.code` exists on the model (`crates/anima-db/src/repo/workitem.rs:97`, `pub code: Option<String>`) and is simply never passed to `assemble_brief`. That is the whole bug. The session is handed a prose title, while the thread history and any inherited PR it can see are **saturated with explicit `WI-nnn` codes belonging to other items** — because these work items routinely reuse pre-existing PRs from earlier attempts (#256 and #240 were reused rather than created). When the model needs to name its task, the most salient work-item identifier in its context belongs to someone else, and nothing in the brief contradicts it. The same omission is in the reviewer path: `assemble_reviewer_brief` (`scheduler.rs:2910`) writes `**Title:** {title}` with no code and no id either. ## Required change 1. **Pass the identity into both briefs.** `assemble_brief` and `assemble_reviewer_brief` take the code and the work item UUID alongside title and description. Both are already in scope at their call sites. 2. **Render it unambiguously and first.** The intent section should lead with the identity — e.g. `# Intent\n\n**WI-256** (`019f9f56-52f3-7872-8025-f785dd2ccf1e`) — {title}` — so it is the first work-item identifier the model reads, not the twentieth. 3. **Repeat it late.** Per Till's direction on the original item: the identity belongs at a position that **survives context compaction**, not only in a preamble a long turn pushes out of the window. Emitting it once at the top is necessary but not sufficient; consider a short trailing restatement after the phase prompt. 4. **Name the failure mode in the brief.** State explicitly that thread history, review feedback and existing branches may reference *other* work items, and that those are not this session's task. This costs a sentence and directly addresses the observed confusion. ## Why not fix it on the akh-medu side akh-medu WI-256 tried, and it was the wrong repo. Its round-2 attempt wired `akhomed` to detect an Anima session via `ANIMA_RUNNER_TOKEN` and then call the `mcp__anima__get_work_item` MCP tool at init to fetch its own identity. The reviewer rejected it — correctly on the mechanics (it called `agent.call_tool()`, which does not exist on `Agent`, so it did not compile) and correctly in principle: > *"the work item context should instead be provided by the Anima runner through the session dispatch mechanism (environment variables, command-line arguments, or initialization protocol)"* What that reviewer could not know, reviewing inside akh-medu, is that **the runner already does exactly that** for title and description — the transport is the ACP prompt itself, and it has been all along. The missing piece was never a delivery mechanism. It was two fields. An MCP round-trip at akhomed init would also be strictly worse: it needs auth before the session is usable, and it introduces a failure mode where the lookup fails and the akh boots with no identity at all. ## Acceptance - The assembled implementer brief and reviewer brief both contain the work item's code and UUID. - Unit tests on `assemble_brief` and `assemble_reviewer_brief` assert the identity is present (the existing `assemble_reviewer_brief_*` tests in `scheduler.rs:3626+` are the pattern to follow). - The identity appears both early and late enough to survive compaction of a long turn. - Spot-check after deploy: a session on a work item with an inherited thread or a pre-existing PR reports **its own** code. akh-medu WI-256's own history is the natural regression fixture — it has both. ## Related - akh-medu **WI-256** (`019f9f56-52f3`) — origin; cancelled in favour of this item - akh-medu **WI-220** — no structured tool-call telemetry from akh sessions, which is why the tecton-lane KG hypothesis remains untestable. Not a blocker here: this item fixes the demonstrated non-KG path. - akh-medu **WI-221** — per-session `SessionContext`; unrelated but frequently confused with this in the threads.
This pull request has changes conflicting with the target branch.
  • crates/anima-server/src/mcp/handlers.rs
  • crates/anima-server/src/scheduler.rs
View command line instructions

Manual merge helper

Use this merge commit message when completing the merge manually.

Checkout

From your project repository, check out a new branch and test the changes.
git fetch -u origin claude/wi-01a005fc-the-acp-brief-carries-the-description-bu:claude/wi-01a005fc-the-acp-brief-carries-the-description-bu
git switch claude/wi-01a005fc-the-acp-brief-carries-the-description-bu

Merge

Merge the changes and update on Forgejo.

Warning: The "Autodetect manual merge" setting is not enabled for this repository, you will have to mark this pull request as manually merged afterwards.

git switch main
git merge --no-ff claude/wi-01a005fc-the-acp-brief-carries-the-description-bu
git switch claude/wi-01a005fc-the-acp-brief-carries-the-description-bu
git rebase main
git switch main
git merge --ff-only claude/wi-01a005fc-the-acp-brief-carries-the-description-bu
git switch claude/wi-01a005fc-the-acp-brief-carries-the-description-bu
git rebase main
git switch main
git merge --no-ff claude/wi-01a005fc-the-acp-brief-carries-the-description-bu
git switch main
git merge --squash claude/wi-01a005fc-the-acp-brief-carries-the-description-bu
git switch main
git merge --ff-only claude/wi-01a005fc-the-acp-brief-carries-the-description-bu
git switch main
git merge claude/wi-01a005fc-the-acp-brief-carries-the-description-bu
git push origin main
Sign in to join this conversation.
No reviewers
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set

Reference
toasterson/Anima!612
No description provided.