--auto session and the goal layerThe 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 place | What 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 |
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.
Three layers, each with a crisp owner:
| Layer | What it is | Who sets it | Lifetime |
|---|---|---|---|
| Goal | A standing direction of work, anchored to hub requirements | @jwildfire (design-level decision) | Weeks–months, cross-session |
| Increment | One 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 |
| Session | A single --auto run: init → select → execute →
wrap up | Triggered by @jwildfire (v1) or a schedule (later) | Hours, bounded |
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.
/goal built-in, and why we need our own layerClaude 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:
| Concern | Owner |
|---|---|
| Standing direction across sessions ("keep adding charts") | Goal files (§2.1) |
| Which issue to work tonight | Increment 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 fact | Wrapup 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.
--auto session lifecycle# 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}"
🦾🤖 {YYYY-MM-DD} {goal-or-increment-slug}, purple in the session hub — so the
dashboard distinguishes them from interactive leads (😺), siblings (👯), and ultracode jobs (⚡️).
Decided 2026-07-22 (O4).--auto
session already running, disk/worktree hygiene. Any failure aborts before a token is spent.--dry-run: run init + selection, print the chosen increment and
planned scope, and stop — the cheap way to sanity-check selection rules early.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:
--increment repo#N was passed → take it (still subject
to the eligibility checks below; refuse and stop with needs input: if it fails them).--auto
run's hand-off that is still eligible → continue it before starting anything new (momentum rule).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.
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 agents — session-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:
tdd skill), draft PRs from obotclaw
with the 5-section obot PR template, Closes #N linkage;headRefOid comparison before trusting or
cleaning anything (runbook rule 4);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:
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.
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).
| Repo | Branch + draft PR | Merge (via obot-merge only) | Issues / board | Releases | Never |
|---|---|---|---|---|---|
| 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. closes | prep ✅ · publish ❌ | master, tag/publish, deletes |
| open.gismo | ✅ draft PR to dev (after obotclaw app install) |
✅ dev |
✅ incl. closes | prep ✅ · 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. closes | prep ✅ · 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. closes | prep ✅ · 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 | ||||
--auto may close any portfolio issue it can
justify except hub Requirement issues. Requirements close as part of release
promotion PRs, which @jwildfire still approves — closing delivered requirements becomes a
release-train step (feeds the coming release skill, oa#36).main
exclude anything touching the autonomy machinery's policy surface
(merge-policy.json, autonomy-grants.json, hooks,
goals/) — closes the loophole where the autonomous session could widen its own
grants.dev integration branch becomes the
default, main is protected and release-only via promotion PRs (approval tier).
Also heals the Status page's DEV STATUS gap for gsm.safety
(hub#44).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.
--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.
| Level | What it means | Status |
|---|---|---|
| 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).
.claude/autonomy-halt in the workspace root. Checked
by the wrapper pre-flight and by the session at every phase boundary (init → select → execute →
wrapup) and between spawned-agent waves; if present, the session parks cleanly: heartbeat line,
digest of state so far, needs input:. Creating the file is the one-command stop
(touch), removable only by Jeremy's say-so.claude attach {job} works on any
--auto run at any time — it is a normal background session.merge-policy.json /
autonomy-grants.json, or set the level back to A0, or set a goal's
status: paused — each takes effect at the next boundary check without killing the
session.git reset --hard,
amending pushed commits, pushes to deploy-named branches, explicit ask rules —
and boundaries stated in the prompt, so the launch briefing's "draft PRs only" line is
itself enforced). If the classifier blocks 3 consecutive or 20 total actions, auto mode pauses
the session back into prompting — an unattended run that starts fighting its guardrails
self-parks into needs input rather than escalating.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:
| Failure | Detection | Behavior |
|---|---|---|
| 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 attempts | Session'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 |
obot-auto (evening launch, morning
review — the proven ultracode rhythm). Scheduling is deliberately deferred to A2; when it
arrives, candidates in order of fit: Claude Code's desktop scheduled tasks or local
cron/launchd on the Mac (lid-open constraint noted), then GHA-hosted headless
(claude -p "/goal …") once the local form is boring. Cloud routines stay out
until the push-identity gap is solved (still research preview; the paused diary routine's
lesson).--auto ships (decided 2026-07-22 —
O6): the requirement stays single-threaded on the centerpiece.@claude PR/issue responder on
portfolio repos — triage, review-comment responses, check runs. Depends only on #3's permission
set; specified enough by the requirement; its detailed design can ride the implementation
sub-issue.Sequenced so each piece is independently reviewable (sub-issues to be filed via
requirement-tasks after design sign-off):
--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.dev from the v1.0.0 main tip, make it the default branch,
protect main as release-only via promotion PRs — mirroring safety.viz.--auto
ships (O6).dev branch + protection (implementation step 2 — gates its standard
lane, not the rest); obotclaw app install on open.gismo (pending — blocks G2 merges, not G1);
/goal built-in availability at the pinned CLI version.--dry-run, and one-increment-per-run. Worst case at
A1 is an unwanted change on an integration branch — revertible, and visible in the morning
digest before it goes anywhere near a release./goal built-in and our layer: we depend on it
only as an in-session stop check — if its semantics change, the session degrades to its own
judgment plus budgets, nothing structural breaks.All six calls decided by @jwildfire in the same-day live review (plus the four grant-matrix clarifications recorded in §5).
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.
goals/registry.json, still inside the §5
carve-out. Selection rules and every other decision here are unchanged.🦾🤖 {date} {slug}, purple in
the session hub — unattended-autonomous is a fourth supervision model alongside 😺/👯/⚡️ and the
dashboard says so at a glance.autonomy-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.--auto ships, not in
parallel — the requirement stays single-threaded on the centerpiece.