🍊😺 obot2 harness · implementation proposal

Roadmap agent: session orchestration

One interactive session picks 1–2 lanes, spawns a sibling session per lane, tracks their status, and closes the day by writing the diary entry.

2026-07-04 home: safety.agent → upstream: gsm.agent / gsm.roadmap relates: obot.roadmap #17 · #18
Absorbed by requirement #24 (2026-07-11) — the session-lifecycle half of this proposal shipped with obot.agent v0.1.0 (session skills, lead/sibling spawn pattern); the visibility/tracking half is designed in Design #24 — session hub, whose §3 records the disposition of D1–D4 below (D2's needs-input relay moves to #18). Kept unchanged below as the point-in-time record.

The mechanics already exist — the work is writing the procedure down. Sibling sessions (claude --bg), machine-readable per-session status, the result:/needs input:/failed: protocol, enforced worktree isolation, and a per-session diary cadence are all live in the harness today, and the pattern was proven on 2026-07-04 when the safety.viz scaffold ran as a briefed background session. What's missing is the orchestration procedure as durable skills: two new skills in safety.agent (roadmap-session, diary-entry), a formal lane-briefing contract, and one new Requirement on the hub. No new infrastructure.

01 · Concept

What a roadmap-agent session looks like

Jeremy opens a Claude Code session in obot2 and invokes the roadmap agent. It reads the hub, proposes one or two lanes — each a concrete unit of work, typically an open sub-issue with a designed requirement behind it — and, on approval, spawns a sibling session per lane. While lanes run, the roadmap session is the control tower: it reports status on demand, relays questions from lanes to Jeremy, and stays available for hub work (requirement edits, reviews, planning). When lanes land, it collects each one's result and PR links and writes the day's diary entry.

Why siblings, not sub-agents. A sub-agent (Agent tool) runs inside the orchestrator's context and dies with it. A sibling is a full top-level session: its own context window, model, permission mode, transcript, and worktree — visible in the agents view, resumable, and it survives the orchestrator ending. Status lives in the harness, not in the orchestrator's memory, which is what makes orchestration restartable (see §06). This matches the existing preference and the scaffold /spawn pattern already in use.

02 · Inventory

Already in the harness

Everything below exists today and was verified in this session. The orchestrator consumes these; it does not build them.

MechanismWhat it gives the orchestrator
claude --bg "<prompt>" Spawns a sibling session and returns immediately. Takes --model, --permission-mode, --effort. Already allowlisted via the scaffold /spawn command.
claude agents --json [--all] [--cwd <path>] Scriptable session list (no TTY needed): active lanes, or with --all the full history. --cwd scopes to sessions started under the workspace.
~/.claude/jobs/<id>/state.json Per-session state: state, detail, tempo, output.result, children (PR/issue links auto-scanned from the transcript), the original intent briefing, resumeSessionId, timestamps, token count.
working needs input: result: failed: The background-session status protocol. A classifier reads each session's message text and keeps state.json current — lanes that follow the protocol are trackable for free.
bg worktree isolation Background sessions must isolate into a git worktree before editing — enforced by the harness, and aligned with the AGENTS.md worktree convention. Lanes can't collide with each other or with Jeremy's checkout.
diary/ conventions obot.roadmap's diary README already specifies per-working-session cadence (design decision D2): write the day's entry at session end, append a session section if the file exists.
scaffold /spawn briefing The context-briefing pattern (<300 words: paths, decisions, constraints, what's ruled out). The lane briefing below is this, formalized with a status-protocol and conventions section.

03 · Lifecycle

The session, phase by phase

  1. 0

    Situate

    Build the picture before proposing anything: open requirements and sub-issue status from the hub (gh + Project status), the latest diary entry, and — critically — sessions already in flight or recently finished:

    claude agents --json --all --cwd ~/Documents/obot2

    Any stray result: from a previous day that never made a diary entry gets picked up here (see §06).

  2. 1

    Pick lanes

    Propose 1–2 lanes as a multiple-choice prompt per the Decision Prompt convention. A lane is fully specified before spawn: repo · issue · done-condition · model/effort/permission-mode. Cap at two — siblings are full sessions, and the cap keeps cost and Jeremy's review load sane.

  3. 2

    Spawn

    Compose a lane briefing (contract in §04) and launch each sibling from the lane repo's directory, recording the job id → lane mapping in a session ledger:

    cd ~/Documents/obot2/<repo> && \
    claude --bg --model sonnet --permission-mode auto \
      "<lane briefing>\n\n---\n\nTASK: <issue + done-condition>"
  4. 3

    Track

    Default mode is on-demand: when Jeremy asks (or before any decision), render a status board from claude agents --json plus each lane's state.json — state chip, one-line detail, and any children PRs. When the session is just waiting for lanes to land, start a background watcher so the orchestrator is woken instead of polling:

    # run_in_background Bash — exits (and wakes the orchestrator) on any state change
    until [ "$(jq -r .state ~/.claude/jobs/<id>/state.json)" != "working" ]; do sleep 30; done

    On needs input:, surface the lane's question to Jeremy as a multiple-choice prompt, then relay the answer by resuming the lane in place:

    claude --bg --resume <resumeSessionId> "<answer>"  # spike first — see §07
  5. 4

    Wrap up: the diary entry

    For each lane, collect output.result and children links from state.json, fold in the orchestrator's own hub work, and write diary/YYYY-MM-DD.md in the existing format (meta line, work completed, PRs/issues touched, blockers, 🙋 ToDo). Lanes still working at wrap-up are listed as in-flight; tomorrow's entry closes them out. Commit direct to main under the standing autonomy grant, let Pages deploy, and hand Jeremy the deployed URL.

04 · Contract

The lane briefing

The scaffold /spawn briefing, formalized. Three sections, in this order — the third is what makes lanes trackable, and it's the part the current pattern leaves implicit:

CONTEXT   — ~300 words: repo + paths, the spec source (issue link), decisions
            already made (do not re-litigate), constraints, what's been ruled out.

TASK      — the issue to implement + an explicit done-condition
            (usually: draft PR open with `Closes #N`, checks green, never merge).

PROTOCOL  — work in a worktree (../<repo>-worktrees/<branch>); follow
            .github/AGENTS.md conventions + attribution-at-bottom; emit
            `result:` / `needs input:` / `failed:` faithfully — your state.json
            is read by the roadmap session and becomes the diary entry.

This lives as a template in the roadmap-session skill (references/lane-briefing.md) so every spawn — human-initiated or, later, autonomous — produces sessions the tracker can read.

05 · Landing zones

Where the pieces go

safety.agent

primary · new skills
  • skills/roadmap-session/SKILL.md — the §03 procedure, with references/lane-briefing.md and references/status-protocol.md.
  • skills/diary-entry/SKILL.md — the wrap-up procedure, separate so the future nightly-diary lane (#18, lane 2) reuses it unchanged.
  • Both written overlay-shaped per #17: assume gsm.agent conventions, state only the delta.

obot2 workspace

wiring
  • Per-skill symlinks in .claude/skills/ (existing workspace pattern).
  • Allowlist Bash(claude --bg:*) and Bash(claude agents:*) in workspace settings so spawn/track don't prompt.
  • One CLAUDE.md line: roadmap sessions start with the roadmap-session skill.

obot.roadmap

hub · tracking + diary
  • File this proposal as a new Requirement (see D1).
  • Diary README is already compatible — per-session cadence is its design decision D2. Optionally note the orchestrated-session shape (per-lane results + links).
  • No session ledger in the repo: state.json is the runtime source of truth; the diary is the durable record.

gsm.agent / gsm.roadmap

upstream · later
  • gsm.agent: the generic pieces — sibling-session orchestration, lane-briefing contract, status protocol — proposed as a PR per #17 bucket (b); a concrete first artifact for its v1.0 multi-agent-framework direction.
  • gsm.roadmap: the roadmap-agent persona angle. Its v0.1 persona wraps status workflows and plain-English queries; session orchestration is the natural extension once proven here.

06 · Fit

Between #17 and #18 — the attended middle step

#17 · overlay restructure safety.agent becomes a thin overlay on gsm.agent — the home these skills are written into.
This · attended orchestration Jeremy conducts; lanes run as briefed siblings; roadmap agent tracks and writes the diary.
#18 · autonomous lanes Proven lanes graduate to scheduled/event-driven runs under the App identity.

Every #18 candidate lane maps onto a piece built here: the background dev lane is exactly a lane briefing minus the human at the top; the nightly diary is the diary-entry skill on a schedule; the rollup reuses the same status-reading. Running lanes attended first surfaces the failure modes (briefing gaps, protocol drift, needs-input handling) while a human is watching — which is the cheap time to find them.

Restartability falls out of the architecture: because status lives in state.json, not in the orchestrator's context, the roadmap session can end while lanes run. The next roadmap session's Situate phase (--all) finds finished lanes and folds them into that day's diary. No lane is orphaned by a closed laptop.

07 · Decisions

For Jeremy

D1Track as a new hub Requirement, or fold into #18's design?

Recommend: new Requirement. #18 is explicitly unattended operation gated on the App identity (#3); this is attended, needs no new identity, and can start now. Frame it as depending on #17 (overlay home) and feeding #18 (lane graduation).

D2Who answers a lane's needs input:?

Recommend: the orchestrator relays — question surfaces in the roadmap session as a multiple-choice prompt, answer goes back via --resume. Jeremy answering directly in the agents view stays as the escape hatch. Keeps one conversation as the control surface.

D3Standing watcher or on-demand status?

Recommend: on-demand by default, background watcher only when the session is idle-waiting for lanes to land (wrap-up). A standing 30s poll adds noise to a session Jeremy is actively using.

D4One skill or two?

Recommend: two. roadmap-session (orchestrate) + diary-entry (wrap-up), because the diary procedure is reused verbatim by #18's nightly lane and by any substantive non-orchestrated session.

08 · Plan

Implementation order

  1. Spike the two unproven mechanics S — relay-answer via claude --bg --resume, and the watcher-wakes-orchestrator loop. Everything else is verified; these two aren't. Do this before writing skills around them.
  2. File the Requirement on obot.roadmap with this proposal as its Overview S — then Design sign-off per the lifecycle.
  3. Write diary-entry in safety.agent S — smallest piece, immediately useful in every session, no dependency on the spike.
  4. Write roadmap-session + lane-briefing template + status-protocol reference M — incorporating spike findings.
  5. Wire the workspace S — skill symlinks, Bash allowlist entries, CLAUDE.md pointer.
  6. Run one real orchestrated session (two lanes, e.g. safety.viz#2 histogram + gsm.safety#30 widget) and let its diary entry be the acceptance evidence M.
  7. Upstream M — extract the generic orchestration pieces into a gsm.agent PR; open the gsm.roadmap persona conversation. Sequenced with (or after) #17's audit so it rides that classification.

09 · Caveats

Known risks