PR #1022 — Agent-Roles-Charters Phase 1 Follow-Ups: Closing the Footguns
The Goal at the End of the Previous PR
PR #1003 (post #117) shipped Phase 1 of agent-roles-charters: a CLI (synlynk agent init/list/show/edit/disable) and agent_id-driven dispatch integration, giving synlynk a
persistent workspace-agent identity distinct from the swappable execution harness. Its
non-authoring review (Grok, via synlynk pr check) approved and merged the PR but flagged five
non-blocking follow-up items — real footguns for the capability-grants work planned in Phase 2, not
blockers for Phase 1 itself. PR #1003's own retrospective named them explicitly: capability_grants
still gets wiped on every agent edit, and a dispatched job's originating agent identity is lost
the moment dispatch returns because daemon_jobs has no agent_id column.
Strategic Shift in This PR
None. This PR is exactly the scope docs/superpowers/specs/2026-08-16-agent-roles-phase1-followups-design.md
laid out: five small, same-file-neighborhood fixes, explicitly scoped apart from the larger Phase 2
(memory + gated learning) work so that cleanup didn't get tangled with new design risk. One
deliberate, flagged deviation from the spec's literal text: §3 said to add agent_id to the
quota-deferred INSERT INTO daemon_jobs branch "mirroring session_id exactly" — but that branch
doesn't actually carry session_id today. The plan documented this up front and every dispatch
prompt for that task explicitly said to leave the branch untouched rather than force a mirror that
would introduce an inconsistency the spec didn't intend.
What This PR Shipped
Executed as eight TDD tasks via Subagent-Driven Development, each dispatched individually to Codex
(synlynk dispatch codex --task "..." --force-agent --context-mode full), independently re-verified
by re-running the affected test files myself after every merge rather than trusting each job's
self-reported pass/fail — one job's self-report (3 failures) turned out to be a sandbox-only
PermissionError writing to ~/.synlynk/workspaces, confirmed harmless only once re-run outside
that job's sandbox:
daemon_jobs.agent_idpersistence — new schema migration (db.py, guardedALTER TABLE) and a new_ensure_daemon_job_agent_id_column()helper, threaded through both live write paths indispatch_agent()(theUPDATE ... SET agent_id=COALESCE(agent_id, ?)branch and theINSERT OR REPLACEbranch). The value written is theagent_idUUID parameter, not the resolved role string — it identifies which workspace agent dispatched the job.capability_grantsmerge-not-replace —regenerate_agent_projection()now reads the existing.synlynk/agents/<id>.yamlprojection'soverridesblock before writing, merging newrepo_overrideson top instead of replacing the dict wholesale.cmd_agent_editdrops its hardcodedrepo_overrides={"capability_grants": {}}entirely — existing grants now survive a charter edit. New-agent init is unaffected: with no existing file, the merge still starts from{}.resolve_dispatch_harness()extraction — the inline role→harness resolution logic that lived only insidedispatch_agent()is now a standalone, side-effect-free function (no subprocess spawn, no DB write), called from bothdispatch_agent()andcli.py's--dry-runpreview branch. Before this,synlynk dispatch --as-agent dev --dry-run(no explicit harness) printedagent: Nonebecause the preview never ran the same auto-selection the live path did._harness_for_org_rolerestricted toCORE_FLEET— the function iteratedsorted(AGENT_CAPABILITY_BASELINES), the full baseline key set including experimental/non-fleet entries, sorted alphabetically. A future baseline (e.g."local") could win a role's harness pick purely by sorting beforeagy/claude. Iteration now filters tosynlynk._constants.CORE_FLEETfirst.- Regression coverage — a new test proves
story_id-based routing still wins overagent_idrole-based fallback when both are present (confirming Task 5's extraction preserved the original precedence order), plus a test locking in theCORE_FLEETfilter using a fake non-fleet baseline that would otherwise win by alphabetical sort.
Full suite: 2026 passed, 2 skipped, 0 failed (python3 -m pytest -q, run independently after every
merge, not just at the end).
Brainstorm Visuals Used
None — this was pattern-following cleanup work in an already-mapped file neighborhood
(dispatch.py, agent_cli.py, agent_store.py, db.py, cli.py); no new visual brainstorming was
needed.
What This Achieved on the Path to Autonomy
The two persistence gaps this closes are exactly the ones the next phase depends on. Phase 2 wants
capability_grants to become load-bearing — actually read and enforced during dispatch/harness
selection — which only makes sense if grants survive a charter edit instead of being silently reset
every time. And any future "jobs by agent" view, cost attribution, or trust-scoring mechanism needs
daemon_jobs.agent_id to actually exist in the table; before this PR, the identity was resolved in
memory and then thrown away the moment dispatch_agent() returned.
The Goal at the End of This PR
All five PR #1003 follow-up items are closed. Phase 1 of agent-roles-charters, plus its own
follow-up debt, is now fully settled — nothing deferred, nothing silently dropped. The next goalpost
is Phase 2: making capability_grants mean something (read and enforced, not just stored) and
building the gated-learning/memory layer on top of the now-solid identity and persistence
foundation this PR finished laying.