← Swink AgentShore Tech
Dispatch anatomy

What's actually in the envelope when AgentShore dispatches an LLM Agent.

Spawning a coding agent is one line of shell. Spawning one that knows what to do is the whole job.

You can shell out to claude. Or codex. Or gemini. The process starts in whatever directory the calling shell happens to be in. It signs commits with whatever git config it inherits. It reads whatever CLAUDE.md is closest on disk. It picks an issue to work on by — well, no one told it which one, so it picks. Sometimes the same one twice. Sometimes one that doesn't exist anymore. Sometimes it edits .github/workflows/ because no one told it not to.

Most of the work isn't generating code. It's not letting the agent inherit ambient state it didn't earn.

01 / Before pickupAn issue gets groomed through four or five plays before any LLM Agent gets to touch code.

By the time the envelope is sealed, the issue inside it has already been through a pipeline that operates on the BEADS graph — not on source files. None of these earlier plays ship a PR. Skip them, and the implementer ends up burning tokens guessing at scope on an ambient-from-GitHub issue with no acceptance criteria, no parent epic, and no dependency edges.

PIPELINE · no hard ordering — the RL Agent routes via play preconditions 01 · SEED Seed Project audit PRD + docs reconciles GH ⇄ beads creates / repairs epics first-run bootstrap issue: drafted 02 · DESIGN Design Audit scan docs for gaps creates missing scope links to HLD sections capability-gated issue: scoped 03 · GROOM Groom Backlog sync + relink closes stale beads fixes labels, parents 20-play cooldown issue: linked 04 · REFINE Refine Tasks decompose oversized splits into sub-tasks label: needs-refinement adds acceptance bits issue: decomposed 05 · PLAN Write Plan task-level approach implementation notes attached to the task capability: can_implement issue: planned 06 · PICKUP Issue Pickup implementer dispatched opens branch + PR picks highest-priority blocked if no ready task issue: in flight ISSUE STATE PROGRESSION drafted scoped linked decomposed planned ready ⟶ in flight no external_ref epic linked gh-N mirror ≤ 1 day est plan attached deps cleared
Fig. 1 · The pre-pickup pipeline. The RL Agent picks which stage to run next based on play preconditions — no hard sequencer enforces order, but the gates make the order de-facto emergent.

02 / The envelopeEvery dispatch is six pieces, assembled, then handed over.

When the RL Agent picks a play and the parameter resolver picks the LLM Agent and the issue, this is the shape of what gets pushed across the subprocess boundary. Nothing here is typed by hand at dispatch time. Each piece has a source the rest of the system is already maintaining.

SPAWNED SUBPROCESS claude -p --verbose --output-format stream-json --append-system-prompt … "<rendered prompt>" env overlay + cwd applied 01 · INSTRUCTIONS Rendered SKILL.md play-specific template, values interpolated ## Goal Review PR #112 (agentshore-orchestrator). ## Forbidden mutations - .github/workflows/** 02 · TASK GRAPH Beads issue + dep chain canonical epic → story → task slice beads-218 · task · P2 parent: beads-211 (story) epic: beads-94 (Phase 6) blocks: beads-219, beads-220 03 · GITHUB STATE Mirror via external_ref PR diff, review comments, CI status gh-112 · OPEN · mergeable +218 −47 across 9 files checks: ci ✓ ruff ✓ mypy ✓ last review: none 04 · IDENTITY Per-subprocess env overlay git authorship + isolated gh config GIT_AUTHOR_* + GIT_COMMITTER_* GH_TOKEN / GITHUB_TOKEN ← keychain GH_CONFIG_DIR ← isolated path ≠ PR author ⟶ anti-bias 05 · WORKING DIR Project path + branch cwd is set explicitly, not inherited /projects/example-app branch: review/gh-112 worktree: clean cwd ≠ Path.cwd() at spawn time 06 · LIMITS & MEMORY Inflation cap + handoff notes in-prompt rules + post-hoc check + memory issue inflation cap: 2.0× cost band: medium tier scope_drift_log: evidence ledger learnings: 3 prior handoff notes
Fig. 2 · The six pieces of context AgentShore assembles per dispatch. None of them come from the shell environment the LLM Agent happened to be launched in.

03 / Inside the context fileThe structured side-channel the LLM Agent reads before it does anything.

The biggest piece in the envelope — the rendered SKILL.md — is natural language. But the LLM Agent also reads a structured JSON snapshot of the world AgentShore thinks it's stepping into. The path is injected via --append-system-prompt "Context file: …", and the rendered prompt instructs the LLM Agent to open it first. The file is written through a temp-file + os.replace(), so a half-written payload can never be observed by a racing reader.

.agentshore/contexts/s-2026-05-21-a3f7/play-218.json
// written atomically before subprocess spawn · session-scoped path · per-play file
{
  "schema_version": 1,
  "session_id": "s-2026-05-21-a3f7",
  "mode": "agent",               // solo · agent
  "current_play": "code_review",
  "skill_name": "agentshore-code-review",
  "play_id": 218,                 // int; per-play file is play-218.json
  "assigned_github_identity": "reviewer-bot",
  "target_branch": "review/gh-112",
  "context_file": ".agentshore/contexts/s-…/play-218.json",

  "params": {                          // resolved by the param resolver, not by the model
    "agent_id": "claude-reviewer-2",
    "issue_number": 112,
    "pr_number": 112,
    "branch": "feat/dispatch-onepager",
    "num_commits": 4,
    "url": "https://github.com/swink/agentshore/pull/112"
  },

  "open_issues": [                     // snapshot · so the model can sequence its work
    { "issue_number": 113, "title": "…", "state": "open", "priority": "P2" },
    … 18 more
  ],

  "pull_requests": [
    {
      "pr_number": 112,
      "github_author": "implementer-bot",   // ← differs from assigned_github_identity
      "head_sha": "a3f9d1e…",
      "mergeable": true,
      "review_decision": null,
      "last_reviewed_sha": null,
      "status_check_summary": "ci ✓ · ruff ✓ · mypy ✓"
    }
  ],

  "budget": {                          // session-scoped, not global
    "enabled": true,
    "total": 25.00,
    "spent": 17.83,
    "remaining": 7.17
  },

  "learnings_count": 3,
  "learnings": [                       // pulled from session_learnings + agent_handoffs
    { "play": "code_review", "note": "watch for CI-workflow edits" },
    … 2 more
  ],

  "project_path": "/projects/example-app"
}
Fig. 3 · A representative context payload. Top-level keys are stable; the params sub-dict varies by play.

04 / Where the pieces come fromFour systems, already maintaining themselves, fold into one envelope.

The trick isn't building any one of these pieces. It's keeping their sources separated so each one can be authoritative for its own slice. BEADS owns the task graph. GitHub owns the human conversation. agentshore.yaml owns identity. AgentShore's SQLite owns the RL state and the per-session memory.

BEADS Project graph epics · stories · tasks · deps canonical via Dolt GITHUB Human surface issues, PRs, reviews, CI linked via external_ref="gh-N" AGENTSHORE.YAML Config agents, identities, tiers frozen dataclass, atomic reload AGENTSHORE SQLITE RL state & memory plays, rewards, handoffs, learnings WAL mode, schema v3 DISPATCH ASSEMBLER render_skill_prompt() + resolve_identity_env() + write_context_file() 01 SKILL.md rendered prompt ← config + beads 02 Task graph issue + deps ← beads 03 GH state PR / CI mirror ← github 04 Identity env git + GH_TOKEN ← config + keychain 05 Working dir cwd + branch ← config + git 06 Limits cap + memory ← sqlite
Fig. 4 · The assembler doesn't author context. It indexes systems that already track it — and reassembles the envelope every dispatch.

05 / What the LLM Agent doesn't get to inheritThe shortest path to a broken loop is letting the subprocess pick its own state.

NAKED DISPATCH $ claude inherits everything from the calling shell Inherited (and unverified): cwd → wherever the shell was git author → global ~/.gitconfig GH_TOKEN → ambient env or gh-cli prompt → closest CLAUDE.md found issue → picked by the model scope → none memory → none cost cap → none AGENTSHORE DISPATCH spawn(cmd, env=overlay, cwd=…) every field is set explicitly per play Assigned (and logged): cwd ← _active_project_path(state) git author ← identities[agent].git_user_* GH_TOKEN ← keychain service · scoped prompt ← render_skill_prompt(skill, params) issue ← param resolver, deduped scope ← inflation cap + prompt-baked rules memory ← session_learnings + agent_handoffs cost cap ← tier band · enforced post-hoc
Fig. 5 · The failure modes of unattended runs live on the left. The boring discipline on the right is what makes the loop self-driving.

06 / After the envelope is openedThe dispatch isn't done when the subprocess exits.

Every play ends with a post-hoc issue-inflation check via validate_scope(), a reward computed against the BEADS alignment delta, and a row appended to dispatch_replay so the dispatch can be reconstructed later. The same machinery that built the envelope reads it back — which is the only reason the next dispatch knows anything about this one.

Persistence you don't need beats lost context. So everything gets written down, even when it feels like overkill.

The architecture is mostly about not letting one LLM Agent's ambient state become the next LLM Agent's bug. Naming the working directory. Naming the identity. Naming the issue. Naming the scope. Writing it all down so the next loop iteration can read it back.