Goal CRUD over the ACP control plane: implement the five _akh/goal/* handlers that PR #286 wired but never wrote #289
Loading…
Reference in a new issue
No description provided.
Delete branch "claude/wi-019fce4c-goal-crud-over-the-acp-control-plane-imp"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
Anima work item
019fce4c-d90f-7311-aa6c-50dd754f9f29.Part 1 of 2, split from
019e480e-44e8-7213-b072-bdfacd98af68(which is cancelled).Part 2 is
entity schema exposure— a separate work item. They share nothing but theMCP/control-plane registration surface. Do not do Part 2 here.
⚠ Read this first: two traps that have already cost 10 sessions
1. The old work item's auto-generated triage plan pointed at the WRONG REPOSITORY. It
listed
proto/anima/v1/agent.proto,crates/anima-core/…,crates/anima-db/…,crates/anima-server/…,crates/anima-agent/…. Those are Anima paths. This isakh-medu, a separate repo with its own crates, and
anima-agent/anima-aiare retiredin Anima anyway (ADR 0018). That mis-generation is a plausible cause of the repeated
failures. Ignore it; work from the real akhomed surface in this repo.
2. If you hit the same wall twice, STOP and report it rather than burning turns. The
predecessor looped through 9 failed sessions before being parked.
Where the work already is
PR #286 (branch
claude/wi-019e480e-…, headafa6b196) added the dispatch wiring andnothing else. It does not compile.
src/acp/control.rs— the five method names are registered:src/bin/akhomed.rs— five match arms dispatch to handlers that do not exist:Five call sites, zero definitions. Reproducing that wiring is trivial and is written
above; the work is the handlers. Its own comment states the intended shape: "Each handler
resolves the workspace engine, initializes
AgentPredicates, and calls the correspondinggoal operation" backed by
src/agent/goal.rs.What to build
Implement the five handlers against the goal domain in
src/agent/goal.rs:goal/create—{workspace, goal_text, criteria_text, priority, parent_goal_id?},creates a goal with explicit criteria.
goal/update—{workspace, goal_id, goal_text?, criteria_text?, priority?, status?}.goal/cancel—{workspace, goal_id}, a soft-delete (mark cancelled/archived, donot hard-delete).
goal/prioritize—{workspace, goal_ids: [...], priorities: [...]}, batchre-prioritise.
goal/list— extend the existing read to includestatus,created_at,cycles_spent,triples_created, and theparent_goalchain.Lifecycle, exactly as specified — it is deliberately richer than Anima's phase axis and is
unrelated to it, so do not try to align the two:
Verify before you trust the description
It was written 2026-05-21. Check the live tool surface first — what
agent_goalsandlist_workspacesactually expose today may differ. The motivating numbers ("16 activegoals, 53 total, 44 cancelled", observed 2026-05-20) are stale; re-measure if any cleanup
semantics depend on them.
Acceptance
cargo build --features serversucceeds — i.e. every dispatched handler exists.against a real workspace.
goal/cancelsoft-deletes: the goal is still retrievable withstatus = cancelled.Review verdict: BLOCK — the handlers write goals into a store the running agent never reads.
Blocker 1 — handlers bypass the live Agent (
src/bin/akhomed.rs:1237-1500): all five handlers (goal_create/goal_update/goal_cancel/goal_prioritize/goal_list) callself.get_engine(ws)only, neverself.get_agent(ws). Mutations never touch the cachedArc<Mutex<Agent>>'s in-memorygoals: Vec<Goal>(src/agent/agent.rs:167), so a running OODA loop operates on stale goal state indefinitely with no re-sync path.Blocker 2 — two divergent goal stores, last writer wins (
src/agent/agent.rs:773-774+persist_goalsat:3705,:3722): the Agent's restore path prefers a bincode blob only the Agent itself writes, not the KG triples these handlers write. The agent's nextpersist_goalsoverwrites that blob from its stale in-memory list — silently reverting any control-plane create/update/cancel/prioritize after a snapshot or restart. There is no reconciliation.Fix direction for 1+2: route the handlers through the Agent (lock
get_agent(ws), mutate its goal list via the Agent's own goal APIs, let the Agent persist), or add an explicit invalidate/re-sync so Engine-side writes reach the live Agent before its next persist. One authoritative store, not two.Blocker 3 — doesn't build on #286: merge-base
2822e95predates PR #286 (afa6b19, unmerged); this diff independently re-adds identicalCONTROL_METHODSentries and dispatch arms insrc/acp/control.rs. If #286 lands first this conflicts; rebase onto #286 or subsume/close it explicitly.Minor 4 —
goal_prioritize(akhomed.rs:1415-1462):?on the first badgoal_idmid-batch leaves earlier KG writes committed with no rollback — validate all ids first, or report per-item results.Minor 5: handlers flatten
AgentErrordiagnostics intoformat!strings (pre-existingServerControlsignature limitation, but it compounds).Minor 6: no test exercises the Postgres/WAL path (
temp_state()has no PG pool,wire_walno-ops), so the persistence claim is untested.dae0c6ef344950ae4a17Review verdict: BLOCK (head
4950ae4, round 3, 2026-08-06 — human review after gate-needs-human)Round-1 blockers (a) and (b) are both still unaddressed:
(a) Live-Agent bypass — UNFIXED. All five handlers in
src/bin/akhomed.rs(goal_create/update/cancel/prioritize/list) callget_engine(ws)exclusively;get_agentnever appears in any handler body.Agent.goals: Vec<Goal>(src/agent/agent.rs:167) is populated once at construction and never reloaded, so_akh/goal/cancelon a goal a resident Agent is actively working does not stop it — the KG triples change, the live agent's in-memory status doesn't, until process restart.(b) Dual goal stores — UNFIXED and lossy.
persist_goals(src/agent/goal.rs:655) writes the bincode blob thatAgent::new/resume (agent.rs:773) treats as primary:restore_goals_from_store(...)short-circuits the KG-triple fallback whenever the blob exists. The handlers write triples only, never the blob — so once any Agent session has persisted the blob, goals created/edited via_akh/goal/*become invisible on the next resume, and a laterpersist_goalsfrom the resumed (API-blind) agent overwrites and buries them. Round 3's only new code (update_goal_description) adds a third triple-based path without crossing the blob/KG boundary.Fix shape (either):
get_agent(ws)when an agent is resident — mutate liveAgent.goals+ callpersist_goals— falling back to KG-only writes when none is cached; orPlus a regression test that proves a control-plane write is visible to a resident Agent (none of the six new tests construct one via
get_agent).Secondary:
CONTROL_METHODS+ dispatch arms duplicate still-open PR #286's wiring instead of building on it — if #286 lands later this produces duplicate entries; reconcile or close #286 as superseded.GoalStatus::from_labelsilently maps unknown status strings toPending— a bogus"status"value should be a client error.let _ = engine.add_triple(...)swallows write failures inupdate_goal_descriptionand parent-wiring.Tests are decent CRUD coverage of the engine-only path but never exercise the live-Agent scenario that is the crux of this WI.
4950ae4a171bd8ae1f28WIP: Goal CRUD over the ACP control plane: implement the five _akh/goal/* handlers that PR #286 wired but never wroteto Goal CRUD over the ACP control plane: implement the five _akh/goal/* handlers that PR #286 wired but never wrote