Design #18 — autonomous obot operations: the --auto session and the goal layer

APPROVED 2026-07-22 · Requirement obot.roadmap #18 · Milestone 2026q3 · Stage-3 autonomy experiments (stage model, 2026-07-21) · Drafted 2026-07-22 · Reviewed live by @jwildfire same day — all judgement calls resolved (§11), grant matrix clarified (§5)

1. Context and goals

The July 2026 stage model makes autonomy the next act: Stage 2 ("safety.viz released") closed with gsm.safety v1.0.0 shipped on 2026-07-22; Stage 3 is "autonomy experiments" — obot picks up and completes roadmap work with @jwildfire reviewing outcomes rather than driving every session. Requirement #18 scoped this as a small set of named autonomous lanes under the obotclaw App identity, each with a defined human gate.

Everything below builds on machinery that already exists and has survived production use:

In placeWhat it gives the autonomous session
Session framework (obot.agent v0.2.0): session-init / session-spawn / session-todo / session-wrapup, scratchpad heartbeat + Stop-hook nudge, session hub dashboard (hub #24) The bookends and observability: hand-off in, digest out, live progress visible all night
Merge lane (2026-07-22): obot-merge + merge-policy.json two-tier policy; raw merges hook-denied workspace-wide The only write-path to protected integration branches — policy is enforced, not promised
obotclaw GitHub App (hub #3): obot-app-token helper, bot-attributed commits/PRs Identity: unattended work is visibly bot work, auditable per the operating contract
Overnight ultracode runbook (proven 2026-07-12): Monitor watch on job state, 40-min stall detection, morning digest, pre-flight checklist The supervision pattern — reused as-is for --auto runs
Requirement lifecycle + roadmap-first gate The work queue: autonomous sessions only ever work from filed hub requirements
Decided 2026-07-19 (K3 session): the v1 lane slate is requirement-status rollup + event-driven responder + background dev lane, on GitHub Actions under obotclaw. The nightly-diary lane is out (the diary stays a session-wrapup product).

This design covers the whole requirement but its centerpiece is the background dev lane's local form: a --auto flag on /session-init that turns the standard session opening into a fully autonomous dev session — init, pick the next increment, execute it through spawned agents, wrap up, and leave a reviewable morning surface. The GHA-hosted lanes (rollup, responder, and eventually the dev lane itself) share the same identity and grant model and are specified in §8.

2. The autonomy model: goals → increments → sessions

Three layers, each with a crisp owner:

LayerWhat it isWho sets itLifetime
GoalA standing direction of work, anchored to hub requirements @jwildfire (design-level decision)Weeks–months, cross-session
IncrementOne session's worth of work advancing a goal: a specific requirement or sub-issue in an eligible stage Selected by the --auto session under fixed rules (§4.2)One session
SessionA single --auto run: init → select → execute → wrap upTriggered by @jwildfire (v1) or a schedule (later)Hours, bounded
Decided 2026-07-21 (stage model): two standing goals for Stage 3 — G1 “keep adding charts” (static + interactive: nepExplorer #35, QT Phase 2 #37, FDA static charts #9, backlog #38–#40, plus the safety.viz improvement backlog) and G2 “build the app” (the safetyGraphics-replacement app — the open.gismo app-first arc, #34 — bringing core gsm tools into the gsm.safety framework).

2.1 Goal definitions — a new, small artifact

Goals live as one file per goal in obot.agent/goals/{slug}.md — YAML frontmatter over prose, the same shape as skills, so they are agent-parsable, human-reviewable, and versioned next to the machinery that consumes them (precedent: scripts/merge-policy.json):

---
name: charts
title: "G1 — keep adding charts (static + interactive)"
status: active            # active | paused — paused goals are never selected
anchors:                  # hub requirements that feed this goal
  - jwildfire/obot.roadmap#35   # nepExplorer
  - jwildfire/obot.roadmap#37   # QT Explorer Phase 2
  - jwildfire/obot.roadmap#9    # FDA static charts (phased)
backlog:                  # secondary feed, worked only when no anchor is eligible
  - jwildfire/safety.viz        # improvement backlog (e.g. #83–#89)
grant_profile: standard   # row set in the grant matrix (§5)
---
Prose: intent, boundaries, what "done for the summer" looks like,
anything a selecting session should weigh that the issues don't say.

The hub stays the source of truth for the work (requirements, stages, sub-issues); the goal file only records the standing direction plus pointers. Pausing a goal is a one-line edit; retiring one is deleting the file. Placement decided 2026-07-22 (O2): obot.agent.

3. The /goal built-in, and why we need our own layer

Claude Code ships a /goal command (v2.1.139+; facts verified against current docs 2026-07-22): a session-scoped completion condition, implemented as a prompt-based Stop hook and evaluated after each turn by a small fast model (Haiku by default). It clears when the condition is met (or via /goal clear), survives --resume, and pairs cleanly with auto permission mode and background runs; headless one-shots (claude -p "/goal …") run the loop to completion in a single invocation — the likely shape of the eventual GHA-hosted dev lane. It answers "is this session done yet?" — it does not persist across sessions, select work, or hold portfolio intent.

So the division of labor is:

ConcernOwner
Standing direction across sessions ("keep adding charts")Goal files (§2.1)
Which issue to work tonightIncrement selection (§4.2)
"Is tonight's increment complete?" checked every turn/goal, set at session start from the increment's exit criteria
"Did the run behave?" after the factWrapup digest + morning review (§4.4)

The --auto init sets the in-session goal to the selected increment's exit criteria — e.g. "a draft PR exists on safety.viz implementing sv#85 with CI green, the heartbeat has a close-out line, and the wrapup draft is written" — so the built-in provides a per-turn doneness check and a natural stop, independent of the model's own judgment.

4. The --auto session lifecycle

4.1 Launch

# v1: Jeremy-triggered, one command
obot.agent/scripts/obot-auto [--goal charts|app] [--increment owner/repo#N] [--dry-run]

# which expands to roughly:
claude --bg --permission-mode auto -n "🦾🤖 {date} {goal-slug}" \
  "/session-init --auto {args}"

4.2 Init and increment selection

The init phase is the existing lean init unchanged: read the hand-off (scratchpad Overview, diary loose ends, next-session memory), reconcile with one GitHub delta agent, write the prioritized list to the scratchpad. The flag changes what happens next: instead of presenting the list and stopping, the session selects the top eligible increment and proceeds. Selection rules, in order:

  1. Directed: --increment repo#N was passed → take it (still subject to the eligibility checks below; refuse and stop with needs input: if it fails them).
  2. Resume: an unchecked agent-actionable item from a previous --auto run's hand-off that is still eligible → continue it before starting anything new (momentum rule).
  3. Implementation-ready: highest-priority hand-off item that (a) belongs to an active goal, (b) traces to a hub requirement whose Design is signed off, (c) has its repo work scoped (sub-issue or equivalent), and (d) touches only repos the goal's grant profile allows.
  4. Pipeline-advancement: if nothing is implementation-ready, take the goal's next requirement stuck earliest in the lifecycle and advance the artifact: draft the requirement/design doc, publish it, and end at the review gate. The session still produces a deliverable; the deliverable is the draft.

Hard skips, never selected: anything gated on @jwildfire (reviews, decisions, approval-tier merges); anything without a filed hub requirement (roadmap-first — the 07-12 lesson); anything touching a repo outside the grant matrix; release publishing (release prep — notes draft + staged promotion PR — is allowed, §5); ultracode launches (⚡️ scale stays an explicit human opt-in).

v1 works one increment per run. Multi-increment nights come later, if ever — a bounded, reviewable unit per run is the point of the experiment.

4.3 Execution

Per the orchestration vision (2026-07-12), the --auto lead stays thin: roadmap hygiene, session management, and scaffold work in its own hands; product work goes to spawned agentssession-spawn siblings carrying the standard briefing (heartbeat contract, merge-lane rules, deliverables-to-disk), with model/effort chosen per the allocation grant. The existing conventions apply unchanged, notably:

4.4 Autonomous wrapup and the morning surface

The wrapup runs its normal collect + verify phases (fold the scratchpad, one GitHub delta agent), then hits the interactivity boundary: the checkpoint is a draft-diary review, and the standing rule for unattended sessions is explicit — stop with needs input: and the full draft; never post without review. So a --auto session ends by leaving, in order:

  1. standing-grant mechanical applies done (board stage moves, issue comments, scratchpad fold);
  2. the diary draft committed as a draft file (not posted) + rendered for morning review;
  3. a morning digest in the scratchpad: increment attempted, what shipped (links), CI state, token/cost note per the allocation grant, anything skipped or failed, and a numbered morning-actions queue;
  4. the session's own job state ending in needs input: with the digest headline.

Morning review is deliberately the same surface Jeremy already uses: draft PRs on GitHub, the session hub, the diary draft, the deployed hub site.

5. Guardrails and the K4 per-repo grant matrix

K4 (2026-07-19) calls for wider standing per-repo autonomy grants for Aug–Sept, with review reserved for designs and releases. This is the first concrete draft of those grants, scoped to what a --auto session may do unattended (interactive sessions keep today's rules — this matrix never widens what requires approval, it narrows what autonomy may touch):

Matrix as clarified with @jwildfire live, 2026-07-22 (four decisions: issue-close rule, policy-file carve-out, gsm.safety standard tier, release prep-only).

RepoBranch + draft PRMerge (via obot-merge only)Issues / boardReleasesNever
safety.viz✅ worktree + draft PR to dev dev (standard tier) ✅ file/comment/label/board; closes ✅prep ✅ · publish ❌ main merge without approval, tag/publish, deletes
safety-histogram✅ draft PR to dev dev ✅ incl. closesprep ✅ · publish ❌master, tag/publish, deletes
open.gismo✅ draft PR to dev (after obotclaw app install) dev ✅ incl. closesprep ✅ · publish ❌main, tag/publish, deletes
gsm.safety✅ worktree + draft PR to dev (dev branch to be created — see below) dev (standard tier, once added to merge-policy.json) ✅ incl. closesprep ✅ · publish ❌ main promotion without approval, tag/publish, deletes
obot.agent✅ (scaffold lane) main (standard tier), except PRs touching scripts/merge-policy.json, scripts/autonomy-grants.json, workspace hooks, or goals/ — those always wait for @jwildfire (the self-widening carve-out) ✅ incl. closesprep ✅ · publish ❌deletes
obot.roadmap✅ direct commits to main (existing standing grant) ✅ own-PR merges per existing grant ✅ full standard updates incl. non-Requirement closes; Requirement issues are never closed directly — they close via release promotion PRs that @jwildfire approves deletes of any kind (files, issues, releases, history)
Anything outside the jwildfire org❌ hard rule — read-only, no exceptions, drafts handed to Jeremy
Decided 2026-07-22 (grant clarifications, live review):

Enforcement is layered, mostly already built: the merge-gate-guard hook + two-tier merge-policy.json (merges), auto permission mode's classifier (destructive ops), the no-delete rule, and obotclaw attribution. New: the matrix above ships as obot.agent/scripts/autonomy-grants.json next to the merge policy, read by the --auto init at selection time and citable in refusals. Per O5 (decided 2026-07-22) it ships soft — consulted by the session, not hook-enforced — since the risky writes (merges) are already hard-gated; hardening is revisited after the first A1 runs.

6. Autonomy levels: A0 → A1 → A2

Decided 2026-07-22 (O3, overriding the draft's recommendation): --auto starts at A1. @jwildfire's call: A0 draft-only operation is already proven — the supervised overnight ⚡️ runs since 2026-07-12 (hep-explorer, open.gismo Phase-0, abnbl-DILI) all delivered clean draft PRs unattended. A0 remains defined below only as the demotion target.
LevelWhat it meansStatus
A0 — observed runs Everything in §4, but draft-PR-only: no merges anywhere except the existing obot.roadmap standing grant. Skipped as an entry level (proven by the ⚡️ overnight record); kept as the instant demotion target if A1 misbehaves.
A1 — routine merges (the K4 target — start here) Standard-tier obot-merge allowed unattended (dev branches + oa main, minus the policy-file carve-out), so an increment can land end-to-end overnight. Approval-tier still never. Entry level from the first run. Jeremy triggers each run.
A2 — scheduled Nightly trigger without a human launch (desktop scheduled tasks / local cron first; GHA-hosted headless as the lane matures). Same session, same rules — only the trigger changes. Gate: sustained clean A1 operation + Jeremy's comfort.

The level is a single field in autonomy-grants.json (shipping value: A1); promotion is a reviewed one-line change, demotion is instant (§7).

7. Kill switches and failure modes

7.1 Stopping it

7.2 Failure modes → one behavior

Every failure ends the same way: park, log, digest, needs input: — never silent, never retry-forever, never widen scope to route around a block. Specifically:

FailureDetectionBehavior
Login/token expiry mid-run (seen 07-12)Pre-flight + API errors Park + PushNotification (the one case that pings the phone: the run cannot even report otherwise)
Stall (no progress)Monitor watch on job state, 40-min rule (runbook) Monitor emits; morning digest flags the stall window
CI red on the increment's PR after honest attemptsSession's own checks Park with the failure output in the digest; PR stays draft; no force-anything
Guardrail refusal (hook/permission/policy)Denied tool call Treat as a wall, not a puzzle: park and report — a denial in --auto is never "find another route"
Selection finds nothing eligible§4.2 exhausts Report "no eligible increment" with why per candidate — itself useful roadmap signal

8. Scheduling and the other v1 lanes

9. Implementation plan

Sequenced so each piece is independently reviewable (sub-issues to be filed via requirement-tasks after design sign-off):

  1. obot.agent — the --auto core (one PR): session-init SKILL.md gains the --auto mode section (§4); session-wrapup SKILL.md gains the unattended variant (§4.4, codifying the existing needs-input rule); goals/charts.md + goals/app.md; scripts/obot-auto (pre-flight + launch); scripts/autonomy-grants.json (§5, level=A1); merge-policy.json gains gsm.safety dev as a standard lane and main in the approval tier; halt-file checks documented in both skills. This PR touches policy files, so by its own carve-out it merges only with @jwildfire's approval.
  2. gsm.safety branch-model adoption (repo ops, per the 2026-07-22 decision): create dev from the v1.0.0 main tip, make it the default branch, protect main as release-only via promotion PRs — mirroring safety.viz.
  3. Maiden A1 run (no code): evening launch on sv#85 (decided — O1); digest reviewed next morning; grant-matrix and selection-rule fixes fed back as small PRs.
  4. Rollup lane (obot.roadmap workflow + small script) — after --auto ships (O6).
  5. Responder lane (workflow per target repo) — after the rollup proves the GHA + App identity path; scheduling (A2) as a separate later sub-issue.

10. Dependencies and risks

11. Judgement calls — resolved 2026-07-22

All six calls decided by @jwildfire in the same-day live review (plus the four grant-matrix clarifications recorded in §5).

O1 — Maiden run: sv#85 (axis-limit inputs pre-filled with data defaults) — well-scoped, single-repo, no design gate; the first unattended select→build→merge-to-dev→digest cycle runs on it.
O2 — Goal definitions live in obot.agent goals/ — versioned with the machinery (merge-policy precedent); goal edits ride the scaffold-lane review path, and per the §5 carve-out they always wait for @jwildfire.
Superseded 2026-07-24 by design #53 (with #71): goals are hub issues (direction + membership); only the policy binding — the selectable set, active/paused, grant profile — remains in obot.agent as goals/registry.json, still inside the §5 carve-out. Selection rules and every other decision here are unchanged.
O3 — Start at A1, overriding the draft's A0-first recommendation: draft-only operation is considered proven by the supervised overnight ⚡️ runs (2026-07-12 onward). A0 remains the demotion target only. See §6.
O4 — Identity tag 🦾🤖 {date} {slug}, purple in the session hub — unattended-autonomous is a fourth supervision model alongside 😺/👯/⚡️ and the dashboard says so at a glance.
O5 — Grants ship soft at v1autonomy-grants.json is consulted by the session, not hook-enforced; the risky writes (merges) are already hard-gated. Hardening revisited after the first A1 runs, reusing the merge-gate-guard pattern.
O6 — Rollup lane after --auto ships, not in parallel — the requirement stays single-threaded on the centerpiece.