Skip to content

feat(conductor): isolate the work-ledger flow in a kirocrew-ledger-conductor agent - #9277

Merged
bolichen97 merged 1 commit into
mainfrom
feat/ledger-conductor-agent
Sep 8, 2026
Merged

feat(conductor): isolate the work-ledger flow in a kirocrew-ledger-conductor agent#9277
bolichen97 merged 1 commit into
mainfrom
feat/ledger-conductor-agent

Conversation

@iamwhatever

@iamwhatever iamwhatever commented Sep 7, 2026

Copy link
Copy Markdown
Collaborator

Problem / Motivation

#9152 (Phase 2 of the work-ledger RFC) mounted kirocrew-work on kirocrew-conductor and kirocrew-pipeline-conductor and auto-approved the two conductor verbs on both, exactly as the RFC's §Agent spec changes specifies.

The effect is that every existing conductor user is opted into a procedure nobody asked them about. The two grants read as additive, but the flow they enable is not:

goal-conductor (shipped) the ledger flow
dispatch order session_createsession_send seed → record the item createsession_createbind → seed
patrol cycle session_read_message per item, with a stored since cursor one work_ledger_read
item state lives in session_ledger artifacts, encoded by ledger_entry.py under a 32-entry cap the ledger's own record
acceptance batch assembled by hand, unknown pr omitted manually returned as accept_batch

So the two tools arrive on an agent whose prompt and bundled skill describe the other column. An agent's tool surface is part of its charter, and this one landed on the agent most likely to be mid-goal when it upgrades.

Why it matters

A conductor that upgrades mid-goal gets tools its own skill never mentions. Best case it ignores them and nothing changes; worst case it half-adopts the flow — records items in the ledger and in session_ledger artifacts, or binds after seeding — and ends with two records that can disagree about what is running. Neither outcome was chosen by the user, and neither is visible to them.

The flow itself is fine. What is wrong is who is holding it.

What changed (motivation → approach → change)

Approach. Retract the mount from the two shipped conductors, and put the flow on a third spec that is otherwise a copy of the goal conductor. Isolation over a config flag: a flag would still need goal-conductor/SKILL.md to describe both procedures at once, and the two disagree on the load-bearing steps.

A — retracted

_install_conductor_agent and _install_pipeline_conductor_agent each drop the @kirocrew-work tools entry, the mcpServers entry and the two per-tool grants. _CONDUCTOR_WORK_GRANTS is renamed _LEDGER_CONDUCTOR_WORK_GRANTS and retargeted; the tuple and its reasoning are kept for the new agent.

Verified byte-identical to pre-#9152, not just to "no work entries": both specs were generated at 40ff1482d~1 and on this branch under the same stubs the installer tests use, dumped with sorted keys, and diffed. Identical.

B — added

kirocrew-ledger-conductor / kirocrew-ledger-conductor.json, installed eagerly beside its siblings.

kirocrew-conductor kirocrew-ledger-conductor
tools execute_bash, fs_read, web_fetch, session, report, tool_search, @kirocrew-core, @kirocrew-dashboard the same + @kirocrew-work
mcpServers kirocrew-core, kirocrew-dashboard + kirocrew-work (no autoApprove)
allowedTools session, report, tool_search, 14 core verbs, 4 dashboard verbs the same + work_ledger_read, work_ledger_record, work_brief
worker verbs not mounted work_brief granted (reads the caller's OWN bound item, or not_bound); work_report mounted, not granted
file-writing tool none none
prompt _CONDUCTOR_SYSTEM_PROMPT (unchanged) _LEDGER_CONDUCTOR_SYSTEM_PROMPT
skill goal-conductor (unchanged) goal-ledger-conductor
bundled scripts accept_eval.py, ledger_entry.py accept_eval.py only

Everything _install_conductor_agent's docstring argues for is kept and re-asserted, because a copied installer is where those properties drift: no fs_write and no code (governance classes it under filesystem.write), no whole-server auto-approve, every grant filtered through the governance ceiling with a SEL record naming this installer for each withhold, and permissions derived from the filtered list rather than restated.

The worker half is mounted because kirocrew-work is one server. Of its two verbs, work_brief is granted: it only reads the caller's own bound item (or answers not_bound, which is how a root conductor learns it has no parent), and it is a second-level conductor's mandated first call in a session nobody opened — gated, that call is an approval stall before any planning. work_report is not granted: it writes into the parent's record across a dispatch relationship, and one prompt per round boundary is the accepted cost. A nested conductor's reporting contract (work_brief before planning, work_report at round boundaries) is spelled out in both the prompt and the skill.

Two mechanical helpers, _filter_auto_approve and _conductor_mcp_servers, so the third installer is not a third copy of the same forty lines. Full extraction of the installer body is deliberately not done: agent.py documents "one standalone installer per generated agent" as its pattern, and rewriting two shipped installers inside a retraction is the wrong risk to take in this PR.

_LEDGER_CONDUCTOR_SYSTEM_PROMPT fixes the dispatch order, tells a second-level conductor to report its own item upward with work_brief / work_report (its parent reads its own ledger, not the child's), states bind before seed and why (an unbound worker's first work_brief cannot tell an early call from a broken one), carries the per-item agent table with the never-leave-agent-unset warning, makes done a claim the evaluator settles, keeps a claimed pr out of the acceptance bar until an explicit action=accept, and notes that patrol is still on a timer until monitor_start accepts watch: "work-ledger".

New builtin skill goal-ledger-conductor, matching the prompt. accept_eval.py is carried as a real file — the builtin-skill scope gate refuses a symlink before any read, and a wheel carries one poorly — and pinned byte-identical to the shipped copy by a test, because two copies that drift are two acceptance bars. The copy is excluded from mypy in pyproject.toml: two top-level modules named accept_eval make mypy stop with "Duplicate module named" before checking anything, and the original stays checked. The skill feeds the evaluator through a quoted heredoc (<<'ACCEPT_BATCH'), not a printf '%s' '<json>' argument: the acceptance document is built from ingested text, and a single quote inside a file path would end the string and hand the rest to the shell, which execute_bash runs after one approval. (The shipped goal-conductor skill carries the same printf shape at two sites; tracked in #9309, out of this PR's scope.) ledger_entry.py is not carried: it exists to squeeze an item into a 2000-character artifacts value under an entry cap, and this flow has a store.

goal-conductor/SKILL.md, its scripts and _CONDUCTOR_SYSTEM_PROMPT are untouched, byte for byte, and a test asserts it. They are what the un-migrated conductor runs on. Phase 4's rewrite of that skill and its deletion of ledger_entry.py are consequently out of scope here.

C — docs

  • docs/feature-map/README.md — the Work ledger row now names who mounts it and who deliberately does not.
  • src/kiro_crew/docs/agents.md — the owned-spec roster gains kirocrew-ledger-conductor and kirocrew-worker (the latter was left out by feat(work-ledger): kirocrew-work tools and kirocrew-worker agent (phase 2) #9152), with a sentence on why the ledger conductor is separate.
  • docs/system-specs/modules/pipeline-conductor.md — its mcpServers claim needed no edit (it still says core + dashboard, which the retraction restores), but its "a work-ledger tool family … is unbuilt" paragraph was stale and is corrected.
  • The RFC goes to revision: v2.1 with a Rollout note recording the retraction, what isolation costs, and the two merge criteria: one goal run end to end on the new agent (items created, bound, seeded, reported, verified, closed, including at least one blocked/question and one second-level conductor), and Phase 3's watch: "work-ledger" gate merged so patrol stops paying a turn per quiet interval. It also states what retiring the kirocrew-ledger-conductor name means: an alias spec identical to the merged conductor for at least one minor release, a release-notes line and a kirocrew doctor notice — never a same-change deletion.

Tests

New test/test_ledger_conductor_agent.py (31 tests) in three groups:

  • the skill is safe to copy from — the evaluator example uses a quoted heredoc and the vulnerable printf forms are asserted absent; and both the skill and the prompt tell the reader to filter accept_batch to status: done items before piping it (accept_batch is status-blind by design, so an unfiltered batch would let a progress worker's early-satisfied file condition be recorded as a pass and closed under it).
  • it is a conductor — no fs_write/code; no web_search/grep/glob; execute_bash mounted and never granted; no mutating session verb granted (session_send, session_stop, chat_folder_move*, task_run, workflow_run, spawn_*); grants pass the governance ceiling and a stripped grant loses its KAS rule with it; a withhold is audited naming this installer; the audit failing does not break the install; {{VERBOSITY_BLOCK}} present; owned but not required; the boot installer calls it.
  • the ledger surface is here — server mounted with no autoApprove; only the conductor half granted, with no whole-server ref; KAS resources exact rather than kirocrew-work/*.
  • the procedure the prompt must carry — the four ledger keywords; the dispatch order asserted as an order by string position (create < session_create < bind < session_send), not merely as presence; the agent rule; done as a claim plus accept_eval/accept_batch/action=verdict; action=accept; action=close with no ledger_entry; the watch: note; that it points at its own skill and not goal-conductor. Plus: the skill ships only the evaluator, as a regular file, byte-identical to the shipped one; the skill body and the prompt agree on the four load-bearing rules; and goal-conductor is untouched.

Inverted rather than deleted in test_conductor_agent.py, test_pipeline_conductor_agent.py and test_worker_agent.py: every #9152 assertion becomes a negative one, on all four surfaces a mount can survive ontools, mcpServers, allowedTools, and the derived KAS permissions block. The last matters most: nothing reads allowedTools on that backend, so a rule left behind there would re-grant the flow silently. Two new prompt-side assertions pin that neither shipped conductor's prompt names a tool it cannot reach. A deletion would have left nothing stopping the mount from coming back; a negative assertion makes a future re-mount delete a test that says why it was withheld.

test_worker_agent.py's parametrized "a conductor mounts it" test moves to the one conductor that does; its former subjects get the negative form; the no-write check now covers all three conductors.

Manual verification

  • Byte-identity of the two shipped specs — generated at 40ff1482d~1 and on this branch under identical stubs, diff -r clean. This is the claim the whole retraction rests on, and no unit test can make it (it is a comparison across commits).
  • Gates run locally with their base refs exported (without them these are report-mode and always exit 0): brand-name, focus-cue, changelog-history, harness-parity, builtin-skill-scope, docs-lint, feature-map — all pass.
  • Lintblack --target-version py310 --check, isort --check-only, flake8, mypy on the touched Python: clean.
  • Tests — 447 pass across the conductor/worker/work-ledger/mcp-work files. Full backend suite: 89177 pass, 295 fail; the 295 are the same set on a pristine origin/main worktree on this host (host-ownership, AF_UNIX path length, missing binaries — test_host_isolation_floor, test_session_storage, test_file_explorer_app and siblings), differing by one port-contention flake in test_app_backend.py that flips direction between runs.
  • Local review lanes — three rounds. Round 1: GPT 5.6 Sol raised one BLOCKING (the printf interpolation above), fixed and verified closed. Round 2 (after the server's accept_batch filter finding): both clean. Round 3 (after the nested-conductor reporting finding): both clean. Round 4 (after Design Review's work_brief grant suggestion): both clean. Opus 4.8 returned no findings in any round.

Not verified: an end-to-end run of the new agent against a live goal. That is the first of the two merge criteria in the rollout note, and it needs a real multi-item goal rather than a test fixture.

Related Issues

Follows #9152 (Phase 2) and #8855 (Phase 1). Partially retracts #9152's agent-spec change; the tools, routes, store and kirocrew-worker spec it added are untouched.

Pattern harvest

Defect class: a spec change that adds tools to a shipped agent whose bundled procedure the tools contradict. The grant diff looks additive and reviews as additive; what actually ships is a different operating procedure under an unchanged agent name.

Rule candidate: when a change adds an MCP server to an existing agent spec, the review has to ask whether that agent's prompt and skill describe the procedure the new tools imply. If they do not, the tools belong on a new spec, or the procedure ships in the same commit.

A grep-shaped gate cannot judge that. The tractable half is a test-level convention, which this PR establishes: a negative mount assertion per shipped agent, so a later addition has to delete a test that states why the mount was withheld rather than just adding a line to a list.

no linked issue: this retracts part of an already-merged PR (#9152) and adds an agent the RFC did not name; no tracking issue exists for it.

@iamwhatever
iamwhatever requested a review from a team as a code owner September 7, 2026 18:10
@github-actions github-actions Bot added the readiness: checking Automated validation is still running label Sep 7, 2026
@github-actions

github-actions Bot commented Sep 7, 2026

Copy link
Copy Markdown
Contributor

Design Review (Fable 5) — ✅ PASS

Design-level review of 584cfab944b47afc210102df418cdc196c0bbd18 — updated in place on each push. A BLOCK verdict blocks PR readiness; PASS/CONCERNS are advisory.

All the diff hunks are accounted for and match the description; the depth cap is enforced server-side in work_ledger.py, the one-way door (a new public agent name) carries an explicit retirement/alias story in the RFC's rollout note, and the acknowledged duplication cost is pinned by byte-identity and negative-mount tests. No design-level risk survives the kill-filter.

Design-Verdict: PASS

Root-cause retraction with a named alternative rejected for cause, a reversibility story for the new public name, and regression pins on every re-mount surface.

[DESIGN-REVIEWED] 584cfab

@github-actions

github-actions Bot commented Sep 7, 2026

Copy link
Copy Markdown
Contributor

GPT 5.6 Review — ✅ no blocking findings

GPT 5.6 completed its review of 584cfab944b47afc210102df418cdc196c0bbd18 and found no blocking issues.

This comment is updated in place on each push.

Review details

No findings.
[GPT-REVIEWED] 584cfab

False positive or not applicable? A repository writer can comment:
/ai-review override gpt 584cfab944b47afc210102df418cdc196c0bbd18: <one-sentence reason>

@github-actions

github-actions Bot commented Sep 7, 2026

Copy link
Copy Markdown
Contributor

First Principles Review (Fable 5) — ✅ PASS

Premise-level review of 584cfab944b47afc210102df418cdc196c0bbd18 — why this exists and whether the shipped surface is the smallest honest version. Updated in place on each push. A BLOCK verdict blocks PR readiness; PASS/CONCERNS are advisory.

All verification is done: the rename is complete, nothing dangles, the author's counts check out. Final review:

First-Principles-Verdict: PASS

Retracting an unchosen charter change at its cause — the RFC's own mount decision — and re-homing the flow where opting in is explicit.

What this change ships

Intent: stop every existing conductor user from being silently moved onto the work-ledger procedure #9152 mounted on their agents, while keeping that procedure reachable for people who choose it. A FIX (of a rollout decision), carried by one declared addition.

  1. kirocrew-conductor loses the work-ledger tools; spec back to pre-feat(work-ledger): kirocrew-work tools and kirocrew-worker agent (phase 2) #9152 — justified, this is the fix
  2. kirocrew-pipeline-conductor, same retraction on all four surfaces — justified, this is the fix
  3. New selectable agent kirocrew-ledger-conductor, installed at boot — justified: after the retraction it is the ONLY mount of work_ledger_read/work_ledger_record (grep: no other spec mounts the conductor half), so its zero option strands Phase 2's shipped tools
  4. New goal-ledger-conductor skill carrying the inverted procedure — justified; no existing skill describes bind-before-seed
  5. work_brief now auto-approved on a conductor (previously withheld everywhere) — declared; derived from an unattended nested-conductor's mandated first call, and server-side identity resolution (mcp_work.py:304 _strict_caller) bounds it to the caller's own item
  6. Second byte-identical copy of accept_eval.py + mypy exclusion — declared duplicate-by-design; no script-sharing mechanism exists (checked all 27 skills: every scripts/ is per-skill; skills.py:372 rejects cross-tree symlinks), and a drift-pin test holds it
  7. Two installer helpers replacing the duplicated forty-line bodies — rides along in the retraction, but net-subtractive with 3 consumers each and output pinned byte-equal by tests
  8. Heredoc replaces printf for the evaluator feed, in the new skill only — point patch with 2 counted unfixed siblings (goal-conductor/SKILL.md:152,296), author counted them first and deferred to goal-conductor/SKILL.md: feed accept_eval.py and ledger_entry.py through a quoted heredoc, not printf '%s' '<json>' #9309
  9. Docs: feature-map row, agents roster (+kirocrew-worker, omitted by feat(work-ledger): kirocrew-work tools and kirocrew-worker agent (phase 2) #9152), stale pipeline-conductor paragraph, RFC bumped to v2.1 with the retraction recorded — mandated by the same-commit spec invariant

Watch

The RFC's own rollout note says this agent exists to be retired, yet shipping it mints a permanent public name (alias spec through deprecation, kirocrew doctor notice). The author prices this honestly ("What isolation costs, stated plainly"), and the fold-back criteria are concrete — but the fold-back is now a second migration someone must actually run, or the "temporary" third conductor becomes the ninth permanent spec.

[FIRST-PRINCIPLES-REVIEWED] 584cfab

@github-actions

github-actions Bot commented Sep 7, 2026

Copy link
Copy Markdown
Contributor

Opus 4.8 Review — ✅ no blocking findings

Reviewed 584cfab944b47afc210102df418cdc196c0bbd18 — this comment is updated in place on each push.

Review details

No findings.

[OPUS-REVIEWED] 584cfab

Verdict parsed from the review's SHA-scoped output markers for commit 584cfab944b47afc210102df418cdc196c0bbd18.

False positive or not applicable? A repository writer can comment:
/ai-review override fable 584cfab944b47afc210102df418cdc196c0bbd18: <one-sentence reason>

@iamwhatever
iamwhatever force-pushed the feat/ledger-conductor-agent branch from a33764a to 39c3d7e Compare September 7, 2026 18:49
@iamwhatever

Copy link
Copy Markdown
Collaborator Author

Intent: Take the work-ledger flow off the two shipped conductors and put it on a separate kirocrew-ledger-conductor agent with its own skill, so no existing conductor user is moved onto the bind-before-seed dispatch order and the ledger-read patrol cycle without choosing it. The two shipped conductor specs must come out byte-identical to their pre-#9152 form.
Not a goal: Changing the work-ledger store, tools, routes or the kirocrew-worker spec; rewriting goal-conductor/SKILL.md or deleting ledger_entry.py (Phase 4); the watch: "work-ledger" monitor gate (Phase 3); a general refactor of the conductor installers beyond the two mechanical helpers.

@github-actions github-actions Bot added readiness: action required A blocking check or review needs attention and removed readiness: checking Automated validation is still running labels Sep 7, 2026
@iamwhatever
iamwhatever force-pushed the feat/ledger-conductor-agent branch from 39c3d7e to bc13846 Compare September 7, 2026 19:44
@github-actions github-actions Bot added readiness: checking Automated validation is still running readiness: action required A blocking check or review needs attention and removed readiness: action required A blocking check or review needs attention readiness: checking Automated validation is still running labels Sep 7, 2026
@iamwhatever

Copy link
Copy Markdown
Collaborator Author

self-added: yes
mechanism: none added — the fix is a filter step in the skill's patrol procedure and the prompt's done bullet, not new code or a store change

  • The evaluator receives non-done items span=b063b0003de1 — fixed in bc13846

accept_batch (work_ledger.py accept_batch()) is status-blind by design: it is the two-phase promotion seam, composed from every open item with a concrete acceptance. The procedure, not the store, is where status is known, so the filter lives in the procedure.
goal-ledger-conductor/SKILL.md step 3 now says: keep only the entries whose item is currently status: done, then pipe that filtered document; "Never pipe the unfiltered document." The cost-discipline bullet and the "every ready item" wording are changed to done items only.
_LEDGER_CONDUCTOR_SYSTEM_PROMPT (agent.py) done bullet now says the same filter, and why: a stub that already exists is a genuine pass on unfinished work.
Pinned by test_prompt_and_skill_filter_the_batch_to_done_items in test/test_ledger_conductor_agent.py, which asserts both texts carry the filter and the old "every ready item" wording is gone.
This ruling covers the class: any finding that the conductor evaluates an item whose worker has not reported done is answered by this filter, wherever the instruction text moves.

@github-actions github-actions Bot added readiness: checking Automated validation is still running and removed readiness: action required A blocking check or review needs attention labels Sep 7, 2026
@github-actions github-actions Bot added readiness: action required A blocking check or review needs attention readiness: checking Automated validation is still running readiness: passed Eligible automated validation passed for the current revision and removed readiness: checking Automated validation is still running readiness: action required A blocking check or review needs attention labels Sep 7, 2026
@iamwhatever

Copy link
Copy Markdown
Collaborator Author

self-added: no
mechanism: none

  • Watch: the duplicated evaluator is time-bounded only by the RFC's merge criteriarebutted (holds, and is the accepted cost; not disproportional to leave as is)

The copy is the cost of the isolation this PR exists to buy. The alternatives were each verified worse: a symlink is refused by the builtin-skill scope gate before any read; a shared scripts/ outside either skill breaks <this skill's dir> resolution, which both skills rely on so a KIROCREW_HOME override still finds the script; importing the original by path from the copy would make the new skill depend on the old skill being installed, which is the coupling the rollout note says must not exist while the two are separate.
The three artifacts named (copy, byte-identity pin, mypy exclude) are one unit and disappear together on the merge criteria; the pin is what stops them from becoming two acceptance bars in the meantime. That is the tracking: a drift is a red test, not a note nobody reads.
Whether Phase 3 stalls is a planning fact this PR cannot change. If it does, the rollout note's criteria still hold and the duplication stays visible on every build of this file. The PR does not claim the duplication is free, only that it is smaller than every alternative available now.

@iamwhatever

Copy link
Copy Markdown
Collaborator Author

self-added: no
mechanism: none

Correct on both counts: the two printf '%s' '<json>' sites in goal-conductor/SKILL.md carry the same defect this PR fixed in the new skill, and the PR body's sentence was not a tracking artifact.
Now it has one. #9309 (label deferred-finding, assigned, due 2026-09-14) names both sites, the heredoc form to use, and the negative test to add in test/test_conductor_agent.py.
It stays out of this PR because the PR's contract for the goal-conductor tree is "untouched", asserted by test_the_shipped_conductor_skill_is_untouched_by_this_flow: that contract is what makes the retraction claim checkable, and a two-line prose fix in the same commit would weaken it more than it is worth. The follow-up is small and lands independently.

@github-actions github-actions Bot added readiness: action required A blocking check or review needs attention and removed readiness: checking Automated validation is still running labels Sep 7, 2026
@iamwhatever
iamwhatever force-pushed the feat/ledger-conductor-agent branch from bc13846 to bc0cd37 Compare September 7, 2026 21:37
@github-actions github-actions Bot added readiness: checking Automated validation is still running and removed readiness: action required A blocking check or review needs attention labels Sep 7, 2026
@iamwhatever

Copy link
Copy Markdown
Collaborator Author

self-added: yes
mechanism: none added — a reporting section in the ledger conductor's prompt and skill, no new code

  • Selecting kirocrew-ledger-conductor creates a bound child whose procedure never calls work_report span=e79227337a44 — fixed in bc0cd37

The finding holds: the worker half is mounted on this spec precisely so a second-level conductor can reach it, but neither the prompt nor the skill told a nested conductor to use it, so its parent's item would sit bound with no status — the shape the parent reads as a stall.
_LEDGER_CONDUCTOR_SYSTEM_PROMPT gains "If a conductor dispatched you": work_brief before planning (title + acceptance are the definition of done, decision is the parent's instruction), work_report progress at round boundaries, question / blocked / done with the same semantics a worker uses, and not_bound from work_brief as the root conductor's signal that it has no parent.
goal-ledger-conductor/SKILL.md gains "When a conductor dispatched you" with the same contract and the depth-2 note; the Known-limits bullet on the two worker tools now points there.
Pinned by test_prompt_and_skill_make_a_nested_conductor_report_upward in test/test_ledger_conductor_agent.py.
This ruling covers the class: any finding that a nested ledger conductor fails to report its own item's status upward is answered by these two sections, wherever the text moves.

@iamwhatever
iamwhatever force-pushed the feat/ledger-conductor-agent branch from bc0cd37 to 91fa497 Compare September 7, 2026 22:05
@iamwhatever

Copy link
Copy Markdown
Collaborator Author

self-added: yes
mechanism: one grant added — @kirocrew-work/work_brief in _LEDGER_CONDUCTOR_WORK_GRANTS; no new code

  • Watch: the second-level conductor's mandated first call (work_brief) was gated, so an unattended nested goal stalls on a human approvalfixed in 91fa497 (took the Suggestion)

Holds. The nested-conductor section added in the prior round made work_brief the first mandatory call, and the same spec left it gated, in a child session nobody has open. That is an approval stall before any planning.
Fix per the Suggestion: work_brief is now auto-approved on kirocrew-ledger-conductor. It satisfies the tuple's own rule — it only READS the caller's own bound item, or answers not_bound — the same rule the two granted ledger verbs rest on. work_report stays gated: it writes into the parent's record across a dispatch relationship, and one prompt per round boundary is the accepted cost.
The root-conductor case is the one cost named in the Suggestion: it gets an unprompted not_bound. The prompt and skill now say that answer is how a root conductor learns it has no parent, so it is a signal, not a failure.
Pinned: test_the_conductor_half_plus_the_read_only_worker_verb_are_granted and the KAS-resource test in test/test_ledger_conductor_agent.py; test_the_grant_tuples_cover_the_server_and_share_only_the_read in test/test_worker_agent.py asserts work_brief is the ONLY overlap between the conductor and worker grant tuples.
This ruling covers the class: a granted work-ledger verb on a conductor spec must read only the caller's own record or answer a refusal; any write across a dispatch relationship stays gated.

@iamwhatever

Copy link
Copy Markdown
Collaborator Author

self-added: no
mechanism: none

  • Watch: kirocrew-ledger-conductor is public surface the rollout note plans to retire, with no alias/migration storyfixed in 91fa497 (rollout note amended)

Holds: session_create's agent, cron jobs and crew bindings can name the agent the moment it ships, and "retired" without a story is a Mode not found for every one of them.
The RFC rollout note now states what retirement means: at fold-back, kirocrew-ledger-conductor.json stays installed as an alias spec whose prompt and grants are identical to the merged kirocrew-conductor, for at least one minor release, with a release-notes deprecation line and a kirocrew doctor notice for any config still naming it. Deleting the alias is a separate later change with its own notes. A dispatch onto the old name during the window gets the merged agent.
This ruling covers the class: any user-referenceable agent name this design introduces retires through an alias window, never by deletion in the same change.

@iamwhatever

Copy link
Copy Markdown
Collaborator Author

self-added: no
mechanism: none

  • Watch: the description says the worker verbs are not granted while the diff grants work_brieffixed (description corrected; the grant stays)

Holds against the body the lane read: the grant landed in 91fa497 as Design Review's suggested fix for the nested conductor's gated first call, and the body update describing it was posted a few minutes after the lane started.
The current body's grants table lists work_ledger_read, work_ledger_record, work_brief, its worker-verbs row reads "work_brief granted (reads the caller's OWN bound item, or not_bound); work_report mounted, not granted", and the paragraph on the worker half states the in-code rationale verbatim — the stall at a nested conductor's mandated first call in a session nobody opened. The old "deliberately not granted" sentence is gone.
The grant is kept rather than dropped: the stall is not conjectural — work_brief is the first mandatory step of the nested-conductor section, and every call on a gated verb prompts, so the stall is a property of the spec rather than something to be observed in a run.
This ruling covers the class: the PR body's grants table is the auto-approve surface a human approves; any later change to _LEDGER_CONDUCTOR_WORK_GRANTS in this PR must update that table in the same push.

@iamwhatever

Copy link
Copy Markdown
Collaborator Author

self-added: no
mechanism: none

  • Watch: kirocrew-ledger-conductor is one-way-door public surfaceaccepted-and-deferred (as the lane itself classifies it)

Agreed, and the deferral is already recorded in the artifact that owns it: the RFC rollout note's alias paragraph states that retirement keeps kirocrew-ledger-conductor.json installed as an alias identical to the merged conductor for at least one minor release, with a release-notes line and a kirocrew doctor notice, and that deleting the alias is a separate later change.
No issue is filed for it because the RFC's merge criteria are the tracking artifact for the fold-back itself; the alias plan rides with them and is actionable only when they are met.

@github-actions github-actions Bot added readiness: action required A blocking check or review needs attention and removed readiness: checking Automated validation is still running labels Sep 7, 2026
…nductor agent

#9152 mounted `kirocrew-work` on `kirocrew-conductor` and
`kirocrew-pipeline-conductor`, per the RFC's §Agent spec changes. That part is
retracted here, and the flow moves to a new agent instead.

The two grants alone do not describe the change they came with. The ledger flow
**inverts the dispatch order** (`create` -> `session_create` -> `bind` -> seed,
where `goal-conductor/SKILL.md` seeds before it records) and **replaces the
patrol cycle** (one `work_ledger_read` instead of a per-item transcript read with
a stored cursor). Mounting the tools on a shipped agent therefore hands its users
a different procedure under the same name, on the agent most likely to be
mid-goal when it upgrades. An agent's tool surface is part of its charter.

Retracted (A). Both installers drop the `@kirocrew-work` tools entry, the
`mcpServers` entry and the two per-tool grants. Verified byte-identical to their
pre-#9152 form by generating both specs at 40ff148~1 under the same stubs and
diffing. Every #9152 assertion becomes a negative assertion rather than a
deletion, on all four surfaces a mount can survive on — `tools`, `mcpServers`,
`allowedTools` and the derived KAS `permissions` block, the last silently, since
nothing reads `allowedTools` on that backend.

Added (B). `kirocrew-ledger-conductor`, a copy of `_install_conductor_agent` with
the work mount, `_LEDGER_CONDUCTOR_WORK_GRANTS` (renamed from
`_CONDUCTOR_WORK_GRANTS`) and its own prompt. Every property that installer's
docstring argues for is kept and re-asserted, because a copy is where those
drift: no `fs_write` and no `code`, no whole-server auto-approve, grants filtered
through the governance ceiling with a SEL record for each withhold, and
`permissions` derived from the filtered list. The worker half is mounted (it is
one server) and deliberately not granted — to a root conductor it answers only
`not_bound`, and a second-level conductor reaches it through the gate.

Two mechanical helpers, `_filter_auto_approve` and `_conductor_mcp_servers`, so
the third installer is not a third copy of the same forty lines. Full extraction
of the installer body is deliberately not done: the file documents "one
standalone installer per generated agent" as its pattern, and rewriting two
shipped installers inside a retraction is the wrong risk.

New builtin skill `goal-ledger-conductor`, carrying `accept_eval.py` as a real
file (the skill-scope gate refuses a symlink) and NOT `ledger_entry.py` — that
codec exists to squeeze an item into a 2000-character `artifacts` value, and this
flow has a store. `goal-conductor` and `_CONDUCTOR_SYSTEM_PROMPT` are untouched:
they are what the un-migrated conductor runs on.

Docs. The feature-map row and `agents.md` name who mounts the ledger and who
does not; `pipeline-conductor.md`'s "unbuilt" paragraph is corrected. The RFC
goes to v2.1 with a rollout note recording why the flow is isolated and the two
criteria for folding the agents back together — one goal run end to end,
including a `blocked` or `question` and a second-level conductor, and Phase 3's
`watch: "work-ledger"` gate merged so patrol stops paying a turn per quiet
interval.

## Pattern harvest

Defect class: a spec change that adds tools to a shipped agent whose bundled
procedure the tools contradict. The grant diff looks additive and reviews as
additive; what actually ships is a different operating procedure under an
unchanged agent name.

Rule candidate: when a change adds an MCP server to an existing agent spec, the
review has to ask whether that agent's prompt and skill describe the procedure
the new tools imply. If they do not, the tools belong on a new spec or the
procedure ships in the same commit. A grep-shaped gate cannot judge this; the
tractable half is a test-level convention, which this commit establishes — a
negative mount assertion per shipped agent, so a later addition has to delete a
test that says why the mount was withheld.
@iamwhatever
iamwhatever force-pushed the feat/ledger-conductor-agent branch from 91fa497 to 584cfab Compare September 7, 2026 23:40
@iamwhatever

Copy link
Copy Markdown
Collaborator Author

Rebased onto main at 12ecd4512 (carries the #9317 fix for test_empty_session_stats, which was the only red on the previous head).

One line-level delta from the reviewed head 91fa49783: goal-ledger-conductor/scripts/accept_eval.py is resynced to goal-conductor/scripts/accept_eval.py, which #9311 changed on main (non-boolean exists is now an error instead of being bool()-coerced). The byte-identity pin test caught the drift on rebase, exactly as intended; the copy is once again identical to the shipped original. #9311 also makes #9294 redundant, so that issue is closed.

Everything else in the payload is unchanged: identical added/removed lines, same 13 files.

@github-actions github-actions Bot added readiness: checking Automated validation is still running readiness: passed Eligible automated validation passed for the current revision and removed readiness: action required A blocking check or review needs attention readiness: checking Automated validation is still running labels Sep 7, 2026

@bolichen97 bolichen97 left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Approving after independent Tech Lead verification.

Phase-3 relationship to #9152 — correct, and the load-bearing claim independently reproduced. I generated both shipped conductor specs under identical stubs at 40ff1482d~1 (pre-#9152) and at this head, dumped with sorted keys: diff -r is clean. Negative control at origin/main shows the real delta the retraction removes — @kirocrew-work in tools, the kirocrew-work mcpServers entry, and the two grants on both allowedTools and the derived KAS permissions block. All four surfaces are clean here, which is the surface set that matters since nothing reads allowedTools on the KAS backend.

Not a new builtin app. No app manifest, no app tree, nothing under the App Kit surface: this is a new agent SPEC (kirocrew-ledger-conductor.json, added to OWNED_KIRO_AGENT_FILES, owned-but-not-required) plus a builtin skill under src/kiro_crew/builtin_skills/, which is exactly where AGENTS.md's LLM-facing-capabilities rule requires a skill any shipped feature references to live. No new CLI command and no new MCP tool — the kirocrew-work server, its four verbs, the store, the routes and the kirocrew-worker spec from #9152 are untouched, so the statelessness rule is not re-engaged.

No regression on #9152's merged work. mcp_work.py, work_ledger.py, the dashboard handlers, validation.py, sandbox.py and security/paths.py are all absent from the diff. goal-conductor/SKILL.md, its scripts and _CONDUCTOR_SYSTEM_PROMPT are byte-for-byte untouched. The _CONDUCTOR_WORK_GRANTS_LEDGER_CONDUCTOR_WORK_GRANTS rename leaves no dangling reference.

Security properties of the copied installer hold. No fs_write and no code; execute_bash mounted and never granted; no whole-server auto-approve; grants filtered through _may_auto_approve with a SEL record naming this installer per withhold, and the audit failing does not break the install; permissions derived from the filtered list rather than restated. work_brief granted (reads the caller's own bound item or answers not_bound) and work_report deliberately left gated is the right split for a write that crosses a dispatch relationship. The skill feeds the evaluator through a quoted heredoc and names the printf '%s' '<json>' shape as the thing not to do.

Verification run locally: 206 tests pass across test_ledger_conductor_agent.py, test_conductor_agent.py, test_pipeline_conductor_agent.py, test_worker_agent.py. accept_eval.py copy confirmed byte-identical to the shipped original and a regular file, not a symlink. The mypy exclusion regex is anchored to goal-ledger-conductor/scripts/accept_eval.py$, so the original stays type-checked.

CI: 64 check-runs, 56 success / 8 skipped / 0 failure on this head. All five AI lanes clean (Design PASS, First Principles PASS, GPT no blocking, Opus no blocking, UX Review success), plus PR Hygiene, PR Scope, Builtin Skill Scope Gate, CodeQL and Semgrep. Every AI finding across four rounds carries a disposition comment; the two deferrals are recorded in the artifacts that own them (#9309 for goal-conductor's remaining printf sites, the RFC rollout note for the agent-name alias story).

One residual, accepted rather than waved: the end-to-end run of the new agent against a live multi-item goal is not done, and it is correctly named as the first of the two merge criteria in the RFC rollout note rather than claimed. Since the new spec is additive and both shipped conductors are provably back to their pre-#9152 form, that gap cannot regress an existing user.

@bolichen97
bolichen97 merged commit e2efb9d into main Sep 8, 2026
65 checks passed
@bolichen97
bolichen97 deleted the feat/ledger-conductor-agent branch September 8, 2026 00:44
@github-actions github-actions Bot removed the readiness: passed Eligible automated validation passed for the current revision label Sep 8, 2026
iamwhatever pushed a commit that referenced this pull request Sep 8, 2026
Adds the third conductor spec: an agent that owns ONE security audit on ONE
target, dispatching one auditor session per attack surface, an independent
verifier per finding, and (behind a human yes) a fixer. Design of record is
the RFC in #9195, sections "The agent" and "Verified facts".

Registration only. The `security-conductor` skill and its bundled scripts
land separately; this spec references them by name so the agent has somewhere
to read its procedure from, and the prompt already binds the two decisions it
must not make by judgment to those scripts' verdicts.

- `subagent.py`: the name joins `UNADVERTISED_AGENTS`, so it never appears in
  a rendered roster. A conductor is dispatched by name by an operator;
  advertising it invites a caller to hand it work it cannot do.
- `agent_files.py`: `SECURITY_CONDUCTOR_AGENT_FILENAME` beside its pipeline
  sibling, and in `OWNED_KIRO_AGENT_FILES` so the Playwright convergence
  sweep rewrites it rather than letting it rot.
- `agent.py`: `_install_security_conductor_agent()` cloned from
  `_install_pipeline_conductor_agent()`, called eagerly beside it with the
  same try/except+debug-log shape, plus
  `_SECURITY_CONDUCTOR_SYSTEM_PROMPT`.

Every security property the sibling installers argue for is kept, and each
one carries more weight here because this agent's children probe a security
fence, so what it ingests on an unattended cycle is hostile by assumption:
no file-writing tool (neither `fs_write` nor `code`), both servers mounted
whole but auto-approved verb by verb, `execute_bash` mounted and never
auto-approved, and the KAS policy derived from the FILTERED grant list.

The grant tuples are the pipeline conductor's, REUSED rather than copied.
Deriving from this prompt's own tool inventory lands on exactly that set --
patrol lifecycle, reads, the agent's own ledger, owner reporting, and no
`select_crew` since this conductor routes nothing -- so a third
byte-identical copy would be duplication whose later divergence nothing
could detect. Cross-agent reuse is already this file's practice for
`_CONDUCTOR_WORK_GRANTS`.

Tests: `test/test_security_conductor_agent.py` mirrors the pipeline
installer's suite (identity, owned filename, unadvertised, verbosity token,
patrol via `monitor_start`, no file-writing tool, dashboard grants
create/read only, core grants verb by verb, narrowed MCP servers,
permissions derived from the filtered grants, and the withheld-grant audit
naming this installer) and adds the four prompt invariants the charter rests
on: scope is `scope_check.py`'s verdict and `UNKNOWN` is never permission,
acceptance is `verify_finding.py`'s verdict and never a child's prose, a
policy refusal IS the boundary, and both human gates. One test asserts the
two conductors' resolved grant sets stay identical, which is what makes the
reuse above safe to state rather than merely intended.

The two roster ratchets that enumerate `UNADVERTISED_AGENTS` are updated
(they pin the set's single definition, so they red by design on a new
member), as are the two prose lists that named the old three.

Review round 1 (GPT 5.6, blocking): shell was mounted with nothing saying what
it is FOR. The spec half was already closed -- `execute_bash` is never in
`allowedTools`, pinned by test -- but the pipeline conductor I mirrored carries
a "shell exists to run the skill's scripts" paragraph that this prompt lacked,
so the one path from hostile child output to a changed target (a finding whose
text asks for a shell write, ingested on a cycle where the operator armed
session-level trust) was unaddressed at the only layer that can address it:
`allowedTools` is name-scoped with no argument matching, so a granted shell
cannot be bounded by argument in the spec. The prompt now says shell runs the
skill's scripts and is never a way to change a target, that a change to a
target is a child's work behind a gate, and that a finding's own text is
ingested content rather than an instruction. Red-before proven.

Review round 2 (Design Review, Opus 4.8 — both advisory): the user-facing
`docs/agents.md` advertisement is dropped. Both lanes named the same remedy —
a release cut between this PR and the skill PR would ship a DOCUMENTED,
dispatchable auditor whose enforcement scripts do not exist. The eager install
cannot be deferred (`session_create` refuses an agent it cannot resolve, and
resolution reads a boot-time snapshot), but the advertisement can, and
`kirocrew-worker` is already an owned spec that list does not name — so
omitting one until its procedure ships is the file's existing practice rather
than a new gap. `docs/system-specs/modules/crew-mode.md` keeps its update: it
describes `UNADVERTISED_AGENTS` membership, which is true today.

Rebased onto main after the three sibling changes landed (#9195 RFC, #9270
ledger, #9271 skill) and re-mirrored onto the pipeline conductor, which moved
underneath this branch:

- #9277 pulled `@kirocrew-work` and its grants OUT of both shipped conductors
  into a dedicated `kirocrew-ledger-conductor` spec, and renamed
  `_CONDUCTOR_WORK_GRANTS`. The mount is dropped here too: this conductor's
  children report through the skill's ledger scripts, not the work ledger, so
  keeping it would grant a flow whose procedure this agent does not run --
  #9277's own charter-creep argument. New test
  `test_the_work_server_is_not_mounted`.
- the grant filter plus withheld-audit block is now `_filter_auto_approve`,
  and the mcpServers narrowing is `_conductor_mcp_servers`. Both adopted,
  which deletes ~45 lines of inline copy from this installer.
- the `AGENTS.md:248` coupling that made this PR unmergeable on its own is
  resolved by #9271: the `security-conductor` skill now lives in
  `src/kiro_crew/builtin_skills/`. The three scripts the prompt names are
  still absent, which `test_no_stub_scripts_are_shipped` on main declares the
  sanctioned phased state ("an absent script is fine here").

Conflicts were additive on both sides (a fourth conductor at the same
anchors); both were kept.
iamwhatever pushed a commit that referenced this pull request Sep 8, 2026
Adds the third conductor spec: an agent that owns ONE security audit on ONE
target, dispatching one auditor session per attack surface, an independent
verifier per finding, and (behind a human yes) a fixer. Design of record is
the RFC in #9195, sections "The agent" and "Verified facts".

Registration only. The `security-conductor` skill and its bundled scripts
land separately; this spec references them by name so the agent has somewhere
to read its procedure from, and the prompt already binds the two decisions it
must not make by judgment to those scripts' verdicts.

- `subagent.py`: the name joins `UNADVERTISED_AGENTS`, so it never appears in
  a rendered roster. A conductor is dispatched by name by an operator;
  advertising it invites a caller to hand it work it cannot do.
- `agent_files.py`: `SECURITY_CONDUCTOR_AGENT_FILENAME` beside its pipeline
  sibling, and in `OWNED_KIRO_AGENT_FILES` so the Playwright convergence
  sweep rewrites it rather than letting it rot.
- `agent.py`: `_install_security_conductor_agent()` cloned from
  `_install_pipeline_conductor_agent()`, called eagerly beside it with the
  same try/except+debug-log shape, plus
  `_SECURITY_CONDUCTOR_SYSTEM_PROMPT`.

Every security property the sibling installers argue for is kept, and each
one carries more weight here because this agent's children probe a security
fence, so what it ingests on an unattended cycle is hostile by assumption:
no file-writing tool (neither `fs_write` nor `code`), both servers mounted
whole but auto-approved verb by verb, `execute_bash` mounted and never
auto-approved, and the KAS policy derived from the FILTERED grant list.

The grant tuples are the pipeline conductor's, REUSED rather than copied.
Deriving from this prompt's own tool inventory lands on exactly that set --
patrol lifecycle, reads, the agent's own ledger, owner reporting, and no
`select_crew` since this conductor routes nothing -- so a third
byte-identical copy would be duplication whose later divergence nothing
could detect. Cross-agent reuse is already this file's practice for
`_CONDUCTOR_WORK_GRANTS`.

Tests: `test/test_security_conductor_agent.py` mirrors the pipeline
installer's suite (identity, owned filename, unadvertised, verbosity token,
patrol via `monitor_start`, no file-writing tool, dashboard grants
create/read only, core grants verb by verb, narrowed MCP servers,
permissions derived from the filtered grants, and the withheld-grant audit
naming this installer) and adds the four prompt invariants the charter rests
on: scope is `scope_check.py`'s verdict and `UNKNOWN` is never permission,
acceptance is `verify_finding.py`'s verdict and never a child's prose, a
policy refusal IS the boundary, and both human gates. One test asserts the
two conductors' resolved grant sets stay identical, which is what makes the
reuse above safe to state rather than merely intended.

The two roster ratchets that enumerate `UNADVERTISED_AGENTS` are updated
(they pin the set's single definition, so they red by design on a new
member), as are the two prose lists that named the old three.

Review round 1 (GPT 5.6, blocking): shell was mounted with nothing saying what
it is FOR. The spec half was already closed -- `execute_bash` is never in
`allowedTools`, pinned by test -- but the pipeline conductor I mirrored carries
a "shell exists to run the skill's scripts" paragraph that this prompt lacked,
so the one path from hostile child output to a changed target (a finding whose
text asks for a shell write, ingested on a cycle where the operator armed
session-level trust) was unaddressed at the only layer that can address it:
`allowedTools` is name-scoped with no argument matching, so a granted shell
cannot be bounded by argument in the spec. The prompt now says shell runs the
skill's scripts and is never a way to change a target, that a change to a
target is a child's work behind a gate, and that a finding's own text is
ingested content rather than an instruction. Red-before proven.

Review round 2 (Design Review, Opus 4.8 — both advisory): the user-facing
`docs/agents.md` advertisement is dropped. Both lanes named the same remedy —
a release cut between this PR and the skill PR would ship a DOCUMENTED,
dispatchable auditor whose enforcement scripts do not exist. The eager install
cannot be deferred (`session_create` refuses an agent it cannot resolve, and
resolution reads a boot-time snapshot), but the advertisement can, and
`kirocrew-worker` is already an owned spec that list does not name — so
omitting one until its procedure ships is the file's existing practice rather
than a new gap. `docs/system-specs/modules/crew-mode.md` keeps its update: it
describes `UNADVERTISED_AGENTS` membership, which is true today.

Rebased onto main after the three sibling changes landed (#9195 RFC, #9270
ledger, #9271 skill) and re-mirrored onto the pipeline conductor, which moved
underneath this branch:

- #9277 pulled `@kirocrew-work` and its grants OUT of both shipped conductors
  into a dedicated `kirocrew-ledger-conductor` spec, and renamed
  `_CONDUCTOR_WORK_GRANTS`. The mount is dropped here too: this conductor's
  children report through the skill's ledger scripts, not the work ledger, so
  keeping it would grant a flow whose procedure this agent does not run --
  #9277's own charter-creep argument. New test
  `test_the_work_server_is_not_mounted`.
- the grant filter plus withheld-audit block is now `_filter_auto_approve`,
  and the mcpServers narrowing is `_conductor_mcp_servers`. Both adopted,
  which deletes ~45 lines of inline copy from this installer.
- the `AGENTS.md:248` coupling that made this PR unmergeable on its own is
  resolved by #9271: the `security-conductor` skill now lives in
  `src/kiro_crew/builtin_skills/`. The three scripts the prompt names are
  still absent, which `test_no_stub_scripts_are_shipped` on main declares the
  sanctioned phased state ("an absent script is fine here").

Conflicts were additive on both sides (a fourth conductor at the same
anchors); both were kept.
iamwhatever added a commit that referenced this pull request Sep 8, 2026
Adds the third conductor spec: an agent that owns ONE security audit on ONE
target, dispatching one auditor session per attack surface, an independent
verifier per finding, and (behind a human yes) a fixer. Design of record is
the RFC in #9195, sections "The agent" and "Verified facts".

Registration only. The `security-conductor` skill and its bundled scripts
land separately; this spec references them by name so the agent has somewhere
to read its procedure from, and the prompt already binds the two decisions it
must not make by judgment to those scripts' verdicts.

- `subagent.py`: the name joins `UNADVERTISED_AGENTS`, so it never appears in
  a rendered roster. A conductor is dispatched by name by an operator;
  advertising it invites a caller to hand it work it cannot do.
- `agent_files.py`: `SECURITY_CONDUCTOR_AGENT_FILENAME` beside its pipeline
  sibling, and in `OWNED_KIRO_AGENT_FILES` so the Playwright convergence
  sweep rewrites it rather than letting it rot.
- `agent.py`: `_install_security_conductor_agent()` cloned from
  `_install_pipeline_conductor_agent()`, called eagerly beside it with the
  same try/except+debug-log shape, plus
  `_SECURITY_CONDUCTOR_SYSTEM_PROMPT`.

Every security property the sibling installers argue for is kept, and each
one carries more weight here because this agent's children probe a security
fence, so what it ingests on an unattended cycle is hostile by assumption:
no file-writing tool (neither `fs_write` nor `code`), both servers mounted
whole but auto-approved verb by verb, `execute_bash` mounted and never
auto-approved, and the KAS policy derived from the FILTERED grant list.

The grant tuples are the pipeline conductor's, REUSED rather than copied.
Deriving from this prompt's own tool inventory lands on exactly that set --
patrol lifecycle, reads, the agent's own ledger, owner reporting, and no
`select_crew` since this conductor routes nothing -- so a third
byte-identical copy would be duplication whose later divergence nothing
could detect. Cross-agent reuse is already this file's practice for
`_CONDUCTOR_WORK_GRANTS`.

Tests: `test/test_security_conductor_agent.py` mirrors the pipeline
installer's suite (identity, owned filename, unadvertised, verbosity token,
patrol via `monitor_start`, no file-writing tool, dashboard grants
create/read only, core grants verb by verb, narrowed MCP servers,
permissions derived from the filtered grants, and the withheld-grant audit
naming this installer) and adds the four prompt invariants the charter rests
on: scope is `scope_check.py`'s verdict and `UNKNOWN` is never permission,
acceptance is `verify_finding.py`'s verdict and never a child's prose, a
policy refusal IS the boundary, and both human gates. One test asserts the
two conductors' resolved grant sets stay identical, which is what makes the
reuse above safe to state rather than merely intended.

The two roster ratchets that enumerate `UNADVERTISED_AGENTS` are updated
(they pin the set's single definition, so they red by design on a new
member), as are the two prose lists that named the old three.

Review round 1 (GPT 5.6, blocking): shell was mounted with nothing saying what
it is FOR. The spec half was already closed -- `execute_bash` is never in
`allowedTools`, pinned by test -- but the pipeline conductor I mirrored carries
a "shell exists to run the skill's scripts" paragraph that this prompt lacked,
so the one path from hostile child output to a changed target (a finding whose
text asks for a shell write, ingested on a cycle where the operator armed
session-level trust) was unaddressed at the only layer that can address it:
`allowedTools` is name-scoped with no argument matching, so a granted shell
cannot be bounded by argument in the spec. The prompt now says shell runs the
skill's scripts and is never a way to change a target, that a change to a
target is a child's work behind a gate, and that a finding's own text is
ingested content rather than an instruction. Red-before proven.

Review round 2 (Design Review, Opus 4.8 — both advisory): the user-facing
`docs/agents.md` advertisement is dropped. Both lanes named the same remedy —
a release cut between this PR and the skill PR would ship a DOCUMENTED,
dispatchable auditor whose enforcement scripts do not exist. The eager install
cannot be deferred (`session_create` refuses an agent it cannot resolve, and
resolution reads a boot-time snapshot), but the advertisement can, and
`kirocrew-worker` is already an owned spec that list does not name — so
omitting one until its procedure ships is the file's existing practice rather
than a new gap. `docs/system-specs/modules/crew-mode.md` keeps its update: it
describes `UNADVERTISED_AGENTS` membership, which is true today.

Rebased onto main after the three sibling changes landed (#9195 RFC, #9270
ledger, #9271 skill) and re-mirrored onto the pipeline conductor, which moved
underneath this branch:

- #9277 pulled `@kirocrew-work` and its grants OUT of both shipped conductors
  into a dedicated `kirocrew-ledger-conductor` spec, and renamed
  `_CONDUCTOR_WORK_GRANTS`. The mount is dropped here too: this conductor's
  children report through the skill's ledger scripts, not the work ledger, so
  keeping it would grant a flow whose procedure this agent does not run --
  #9277's own charter-creep argument. New test
  `test_the_work_server_is_not_mounted`.
- the grant filter plus withheld-audit block is now `_filter_auto_approve`,
  and the mcpServers narrowing is `_conductor_mcp_servers`. Both adopted,
  which deletes ~45 lines of inline copy from this installer.
- the `AGENTS.md:248` coupling that made this PR unmergeable on its own is
  resolved by #9271: the `security-conductor` skill now lives in
  `src/kiro_crew/builtin_skills/`. The three scripts the prompt names are
  still absent, which `test_no_stub_scripts_are_shipped` on main declares the
  sanctioned phased state ("an absent script is fine here").

Conflicts were additive on both sides (a fourth conductor at the same
anchors); both were kept.

Co-authored-by: Joe Guo <zejiangg@amazon.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants