diff options
| author | Claude Sonnet 5 <noreply@anthropic.com> | 2026-07-04 05:05:36 +0000 |
|---|---|---|
| committer | Claude Sonnet 5 <noreply@anthropic.com> | 2026-07-04 05:05:36 +0000 |
| commit | 8cb291ad7cdb317ff80947278ee055b1a4925b41 (patch) | |
| tree | d9719ae8982a9d7cc3482a71a87d003cdb7e0dfd /CLAUDE.md | |
| parent | 04b6e7eef473cb6eb69e345a4ea08243a8713077 (diff) | |
feat(story,role): add retro ceremony -- closes the self-improvement loop (Phase 8)
The final mechanism the versioned role-config model (Phase 5) was built for:
when a story reaches DONE, StoryOrchestrator spawns a retro-role task that
reflects on the story's full history and proposes draft role_configs
versions for a human to review and activate via the existing (unchanged)
POST /api/roles/{role}/activate.
- AgentChannel gains a 6th method, ProposeRoleConfig(ctx, role.RoleConfig)
(version, err), following ProposeEpic's precedent (Phase 7c): a structured
tool call, not summary-parsing. storeChannel.ProposeRoleConfig calls the
same Store.CreateRoleConfig the human-facing POST /api/roles/{role}/versions
endpoint already uses (proposed_by: "retro"), landing a new draft row
without touching whatever's currently active. Wired through both
transports exactly like ProposeEpic: internal/agentloop/tools.go (native
loop) and internal/executor/agentmcp.go (MCP).
- StoryOrchestrator.Tick now routes a story at status DONE to a new
processRetro stage instead of processStory -- a sibling stage, not a
continuation, since the Builder->Evaluators->Arbitration chain is long
settled by then. processRetro only *reads* that settled pipeline
(read-only findEvaluators/findArbitration counterparts to
ensureEvaluators/ensureArbitration -- it never spawns/mutates
Builder-pipeline tasks) to locate the Arbitration task the retro task
depends on, then spawns (idempotently -- checks for an existing
retro-role dependent first) one retro-role task with instructions
assembled from the story's spec/acceptance-criteria, full task tree, per-
task cost/escalation history, active role_configs per role encountered,
and the story's own event stream (evaluator verdicts, arbitration
decision).
- event.KindRetroCaptured (attached to the story's ID, matching
KindEvalVerdict/KindArbitrationDecided's convention) fires once the retro
task completes (auto-accepted like every other pipeline task), aggregating
every event.KindRoleConfigProposed the retro task recorded (one per
propose_role_config call) into {task_id, proposals: [{role, version}],
summary} -- the summary is the "capturing lessons" half of this ceremony,
the proposals are the versioned-config half.
- Human activation is completely untouched: drafts land through the
identical CreateRoleConfig/config_json path Phase 5's endpoints already
handle, confirmed via existing role-endpoint tests passing unmodified.
go build/vet/test -race -count=1 all pass, full suite (20 packages) -- one
run hit a known, pre-existing, intermittent flake under full-suite load
(unrelated to this phase's files) that did not reproduce on two immediate
reruns, both in isolation and full-suite.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01V1moSNCJRcP6kykA4tyUSs
Diffstat (limited to 'CLAUDE.md')
| -rw-r--r-- | CLAUDE.md | 63 |
1 files changed, 62 insertions, 1 deletions
@@ -78,7 +78,7 @@ Config defaults to `~/.claudomator/config.toml`. Data is stored in `~/.claudomat | `internal/agentloop` | Provider-neutral tool-use control-flow loop shared by all `NativeRunner`s | | `internal/sandbox` | `Sandbox` interface (`HostSandbox`/`DockerSandbox`) + pre-tool-use guardrail hooks | | `internal/role` | `RoleConfig`/`Tier`/`Rung` — per-role system prompt + provider/model escalation ladder | -| `internal/scheduler` | `Scheduler` — polls role-typed FAILED tasks and retries/escalates them per their role's ladder, and (Phase 7c) role-typed BLOCKED tasks whose ask_user question has timed out; `StoryOrchestrator` (Phase 7b) — polls stories and drives Builder → Evaluators → Arbitration → REVIEW_READY | +| `internal/scheduler` | `Scheduler` — polls role-typed FAILED tasks and retries/escalates them per their role's ladder, and (Phase 7c) role-typed BLOCKED tasks whose ask_user question has timed out; `StoryOrchestrator` (Phase 7b) — polls stories and drives Builder → Evaluators → Arbitration → REVIEW_READY, and (Phase 8) a `DONE` story through a retro stage proposing `role_configs` drafts | | `internal/story` | `Epic`/`Story` — planning layer above the flat task tree; data model + CRUD only (see `internal/scheduler.StoryOrchestrator` for the orchestration that drives it) | | `web` | Embedded static UI (`embed.go`) | @@ -518,6 +518,67 @@ distinguishable in the event stream. `GET /api/tasks?needs_review=true` surfaces flagged tasks for a human to double-check the system's stand-in answer later. +### Retro & the self-improvement loop (Phase 8) + +Closes the loop the whole role-versioning system (Phase 5) was built for: +when a story reaches `DONE`, `internal/scheduler.StoryOrchestrator` spawns a +`retro`-role task that reflects on the story and proposes improved +`role_configs` drafts for a human to review and activate. + +- **Trigger**: `StoryOrchestrator.Tick` now routes a story at status `DONE` + to `processRetro` instead of `processStory` (`CANCELLED` stories are still + skipped entirely, unchanged). `processRetro` never spawns or mutates a + Builder/Evaluator/Arbitration task itself — it only *looks up* the + already-settled pipeline via read-only `findEvaluators`/`findArbitration` + (structurally identical to `ensureEvaluators`/`ensureArbitration` but + without their spawn side effects) to find the Arbitration task the retro + task should depend on. If the pipeline isn't fully settled yet (shouldn't + happen for a real `DONE` story), it simply has nothing to do this tick. +- **Idempotency**: structural, like the rest of this file — it looks for an + existing `retro`-role dependent of the Arbitration task before spawning + one, the same "does a role-matched dependent already exist" check + `ensureArbitration` uses. +- **Instructions**: `buildRetroInstructions` assembles the story's spec/ + acceptance criteria, its full task tree (`taskTree`, the same + parent/dependents BFS `GET /api/stories/{id}/task-tree` performs, walked + directly against `StoryStore` rather than over HTTP), each task's + accumulated cost and highest escalation rung (`ListExecutions`), the + currently active `role_configs` for every role encountered + (`GetActiveRoleConfig`), and the story's own event stream — evaluator + verdicts, arbitration decision (`ListEvents`) — then instructs the retro + agent to call `propose_role_config` for any role worth revising and + `report_summary` with the qualitative lessons either way. +- **Reporting mechanism**: a 6th `AgentChannel` method, + `ProposeRoleConfig(ctx, role.RoleConfig) (version int, err error)`, added + following `ProposeEpic`'s precedent (structured tool call over + summary-parsing). `storeChannel.ProposeRoleConfig` + (`internal/executor/channel.go`) calls the same `Store.CreateRoleConfig` + `POST /api/roles/{role}/versions` already uses, hardcoding + `proposed_by: "retro"`, and — never reading or touching whatever version + is currently `active` for that role. It also records + `event.KindRoleConfigProposed` (payload `{role, version}`) attached to the + *calling task's own ID* (roles have no first-class row of their own the + way epics/stories do). Exposed as a `propose_role_config` tool on both + transports — `internal/agentloop/tools.go` and + `internal/executor/agentmcp.go` — decoding directly into + `role.RoleConfig`'s own JSON shape (no parallel input type). +- **`event.KindRetroCaptured`**: once the retro task reaches `COMPLETED` + (auto-accepted from `READY` exactly like every other task in this + pipeline), `finalizeRetro` reads the retro task's own event stream for + every `KindRoleConfigProposed` it recorded and aggregates them into a + single event attached to the **story's** ID: payload `{task_id, + proposals: [{role, version}, ...], summary}` — `summary` is the retro + task's own reported summary, satisfying "capturing lessons" even when zero + config changes are proposed. Guarded by an in-memory `handledRetro` set + (keyed by retro task ID) so a story sitting at `DONE` forever doesn't + re-emit the event every tick — the same accepted per-process-only + simplification as `Scheduler.handled`/`handledVerdicts` above. +- **Human activation, unchanged**: `POST /api/roles/{role}/activate` (Phase + 5) is untouched — draft rows land via the exact same `CreateRoleConfig` + path the human-facing `POST /api/roles/{role}/versions` endpoint uses, so + they're structurally identical and immediately visible via the existing + `GET /api/roles/{role}/versions` with no changes required. + --- |
