Skip to content

Orgabot is in private alpha. Request access, or join our Discord.

← All documentation
Using Orgabot

CLI reference

The orgabot command surface, grouped by what you are trying to do: projects, missions, shipping, organizations, roles, connectors, context, budgets, and recovery.

Every command runs from framework/ as npm run orgabot -- <args> (equivalently npx tsx src/cli/orgabot.ts <args>).

The CLI runs the TypeScript sources directly through `tsx`, not the compiled `dist/`. After merging a change to Orgabot's own framework code, git pull this local repo for the running CLI to pick it up. Rebuilding dist/ is neither enough nor required.

orgabot with no arguments prints the short usage; orgabot help --all prints the full surface, which is much larger than this page.

Global

orgabot [--home <dir>] <command>…    # isolate ALL persistent state under <dir> (blank-slate instance)
orgabot reset [--yes]                # wipe an isolated --home / ORGABOT_HOME; refuses the default ~/.orgabot
orgabot doctor                       # check gh auth, config pointer, registered projects, notifier
orgabot clean [--dry-run]            # release what finished missions still hold
orgabot clean --attribute            # which mission holds which directory, how big, and its live memory
orgabot assistant [status] | assistant set <provider> <model> [--key <api-key>]

Connectors and MCP

orgabot connector mcp-plan <org>                         # read-only CLI-source plan; retain output
orgabot connector mcp-import <org> <server-name> [--source ...] [--fingerprint ...] [--enumerate]  # attach; mutates
orgabot connector mcp-configure <org> <name> (--url <https-url> | --command <executable>)           # manual definition; mutates
orgabot connector mcp-migrate --dry-run                  # read-only legacy binding migration plan
orgabot connector mcp-migrate --apply                    # apply reviewed migration; mutates canonical bindings
orgabot connector mcp-status                             # read-only last-known execution-plane status
orgabot connector mcp-authorize <org> <id> --token-env TOKEN_ENV       # bearer custody; mutates
orgabot connector mcp-authorize-env <org> <id> --bind KEY=SOURCE_ENV   # stdio env custody; mutates

mcp-plan and mcp-migrate --dry-run are stop/go checkpoints. Retain their fingerprint, source, custody, and conflict output; stop on a conflict or an unresolved reconnect requirement before removing the source. Apply only the reviewed plan. Import and migration do not grant roles. Use the CLI for manual transport, bearer, and stdio environment custody. Use the organization Connections card for OAuth; the Capability Registry is read-only. Verify with doctor, then run a granted read-only tool through tool run-as before removing a source configuration. One transport fingerprint maps to one installation-wide server; equivalent unresolved observations in one org share one pending profile. Different orgs or account/credential profiles stay separate. A successful enumerate or reconnect updates the canonical server's validated inventory.

Projects

orgabot project add <repo-path> [--workdir sub/dir] [--name N] [--verify "cmd"] [--unguard '<glob>']
orgabot project list
orgabot project init <project> [--force]        # generate the repo's ORGABOT.md operating reference (ORKA.md still read, and regenerated in place)
orgabot project current [path] [--json]
orgabot project rm <id> [--dry-run] [--yes] [--keep-history] [--with-subprojects] [--force]
orgabot project rename <project> <new-name>     # display name + aliases; the project id never changes
orgabot project set-root <project> <path> [--workdir sub/dir]   # re-point a project at a moved repository root
orgabot project set-issue-owner <root-project>                   # choose among registrations of the same root; never a child
orgabot project review-policy <project> [--add-scan id] [--rm-scan id] [--replace-scan id=cmd] [--add-check id] [--set-missing id=skip|block]
Project retirement is repo-aware: several projects can share one git root, so a repo-wide worktree sweep can reach a sibling project's live checkout. Always --dry-run a removal first.

Missions

orgabot mission <project> "<instruction>" [--background] [--edit-only | --shell] [--mock] [--yes]
                                       [--durable] [--base <branch>] [--ask] [--timeout minutes]
                                       [--shape <workflow>]
                                       [--repository-milestone patch|branch|pull_request|merge|post_merge]
                                       [--no-pr]
orgabot fix-epic <epic-reference> --project <id>   # fix one work item; the router picks epic vs ordinary
orgabot mission-all "<instruction>" --only a,b,c | --all [--exclude x,y]
orgabot organization mission <org> <role> <project> "<instruction>"
orgabot follow-up <mission-id> "<instruction>" [--background]
orgabot status | logs <mission-id> | wait <mission-id> [--json] | cancel <mission-id>
orgabot session <mission-id> [--json]           # the authoritative session state
orgabot session list [--limit N] [--json]       # recent mission sessions, newest first
orgabot long-runs | stop <mission-id> | ack-long-run <mission-id>
orgabot inputs | answer <id> "<answer>"
orgabot objective list | show <id> | check [<id>]
orgabot objective graph <id>                    # scheduling state + planning diagnostics
orgabot objective approve <id> <nodeId> | reject <id> <nodeId> [--reason "..."]
orgabot objective run-check <id> <nodeId>       # run a deterministic_check node's gate
orgabot objective decompose <id> <nodeId> --children <json-or-file> [--reason "..."]
orgabot objective replan <id> <nodeId> --nodes <json-or-file> [--reason "..."]
orgabot catchup [--json] [--narrative] [--since <iso>] [--all] [--project <id>] [--limit N] [--ack]
orgabot resume [--ack]                          # the same command under its other name

orgabot catchup is the first thing to run when you sit back down: what landed, what is waiting on you, what is still in flight, what stalled, and what it cost, all since you last marked yourself caught up, most recent first, with the command to continue from printed beside every item. Reading it changes nothing - only --ack moves the watermark - and --since/--all gives the absolute stand-up over the same digest. See Catching up.

orgabot fix-epic is the explicit command surface onto the same primitive the dashboard's Fix button reaches, and the /fix-epic skill is a thin shell over it. <epic-reference> is owner/repo#123, a GitHub issue URL, or provider:externalId for another tracker (linear:ENG-431, jira:ENG-17); it is resolved through the work-item provider configured for the project, and the capability is chosen from the resolved item's normalized type, never from this command. An epic-shaped item launches autonomous epic execution; anything else launches an ordinary role-owned mission. A reference that cannot be resolved, and a project no organization owns, are both refused with the reason rather than guessed past. See An epic runs its children.

Foreground missions prefer Restate durability when the runtime is up; if it is not, they continue in-process with no error. --durable requires Restate and fails when it is unavailable.

Repository-shaped work has an explicit definition of done. pull_request is the compatibility default; patch, branch, merge, and post_merge require their own artifact and evidence chain. --no-pr remains a compatibility alias for the branch milestone. Shell access is derived from the role's toolchest unless you clamp it with --edit-only or request it with --shell; --operate is a deprecated alias for --shell.

--timeout is the long-run alert threshold, never a kill deadline. The idle watchdog stops only a silent worker. A healthy worker that is simply taking a while is alerted on, not killed. Orgabot used to kill workers at 15 minutes and it cost real work.

A long-run notice offers exactly three operator actions: steer it with orgabot follow-up, end it with orgabot stop, or acknowledge it with orgabot ack-long-run. If a wedged mission ignores orgabot stop, orgabot cancel is what ends it.

orgabot stop publishes the request for the process that owns the mission, and it is bound to the round running when you ask. A request the owner never picks up expires after a bounded window instead of staying armed, and one aimed at a round that has since finished is never applied to whatever round starts next: an operator's stop is about the work in front of them. Either way the record is kept, so orgabot status can still answer who asked, when, and what was ultimately done with it.

An objective whose mission is cancelled reports cancelled rather than blocked. It is settled, not delivered: it drops out of blocked work in orgabot status, orgabot objective list, and the dashboard, no automatic pass resumes or re-staffs it, and orgabot objective check on it reports the settlement and changes nothing. Its capability gaps are kept exactly as they were recorded, unresolved, because cancelling a mission configures no capability.

orgabot cancel also ends a queued mission, one admission control has not started yet. There is no worker to signal, so cancelling drops the throttle-queue entry that owns its relaunch and marks the record cancelled in one step. See Queued is its own status.

orgabot wait, orgabot stop, and orgabot cancel also accept an objective (parent) id, not only a single mission id. Point one at a decomposed plan and it resolves to its plan nodes. wait re-reads the whole plan every interval and returns only when the objective is settled or nothing in it can advance without you: a node that has not launched yet is pending (never success), a node launched or relaunched after the command started is picked up, and the reported outcome is the worst-off node's (a failed node outranks a pending or running one, which outranks a completed one), so ok: true / exit 0 means every plan node completed. stop first persists the objective's own cancellation (ending the parent mission when the objective was fanned out from one) and then ends every node still queued, running, or waiting, including a queued node's admission entry, so no later supervision or admission pass can launch a pending node; cancel does the same, signalling a running node's process and ending a queued or waiting one. An objective id with no launched node missions yet is reported as such rather than as "no such mission".

For an epic, the objective id is its coordinator's mission id, and the coordinator counts as a way the plan can still advance. wait keeps polling while that controller is running, even when every child currently reads waiting - an epic whose children are held on acceptance is being driven toward their next round, not parked on you. It returns once the controller itself parks on a human (a hold, an approval, a typed answer) or ends, and says which: the human-readable output adds a controller ... is <state>; nothing is driving the remaining nodes. line, and --json carries a controller field ({"driving": true} while it is still working, otherwise that mission's own wait payload). Before this, an epic with held children returned after a single sample, which silently ended your supervision of a perfectly healthy run.

Stopping or cancelling an objective also revokes the launches the token throttle parked for that plan. The throttle queues a launch before any mission record exists, so a node waiting on bandwidth or host capacity has no mission id of its own and is named by nothing the node list reports; each such entry is cancelled with the plan and listed by its queue id (throttle-queued launch <id> cancelled (never started)). Nothing was started, so there is nothing to stop - the point is that orgabot throttle resume-due will not launch that work when capacity returns. A resume of a queued launch also re-reads its objective's cancellation first-hand before relaunching anything, so an objective cancelled while a drain is running launches nothing either.

Objectives and decomposition

A decomposed objective's node missions inherit the parent objective's already-resolved workflow shape - they do not re-classify their own (frequently rewritten) sub-goal text. Before this, a node whose rewritten instruction happened to read as troubleshooting or review_audit could hold on workflow_contract_invalid while its parent objective kept showing running, because the node's own launch re-read a shape out of its instruction instead of trusting the one already decided for the objective. orgabot status/orgabot objective check on a decomposed plan now reflect a node that could not launch as the objective's own attention state, never as silent progress.

A one-node objective whose sole node would classify to a genuinely different delivery family than the parent (a troubleshooting-reading node under a repository_change parent, for example) is refused at decomposition time instead: nothing is persisted, and the operator either rewrites the sub-goal or relaunches with an explicit --shape. A multi-node delegation is not affected by this check - an intermediate step in a real DAG routinely reads diagnostically ("diagnose and repair the failed verification") under a repository-change parent, and that is expected, not a mismatch.

Reshaping a plan while it runs

orgabot objective decompose <id> <nodeId> expands a still-pending work node into child nodes wired into the same plan; every dependent that named the parent is rewired onto every child, so nothing downstream starts early. orgabot objective replan <id> <nodeId> replaces a node and its still-pending dependents with a fresh set. Both take a JSON array, inline or as a file path, and both refuse cleanly, changing nothing, past the depth and per-call budget limits, on an invalid node reference, or on a plan that would end up with a dependency cycle.

Two things a replan will never do: it never supersedes an unresolved human_approval or deterministic_check gate (a policy gate is not replanned away), and a replacement never inherits the superseded node's evidence. Superseded nodes are retained and marked, never deleted.

Planning diagnostics

orgabot objective graph <id> prints each node's scheduling state (runnable, blocked with the reason, running, completed, failed, superseded), its decomposition lineage, and a planning-diagnostics summary: discovered vs admitted parallelism (what the planner found could run right now, against what the scheduler actually started), blocked-reason, role-assignment and executor-selection counts, replanning frequency, and quality metrics usable for comparing planners across models and configurations.

Each node may also carry structured decomposition provenance: why the node exists, why that role was chosen, whether it was believed parallelizable, per-dependency reasons, and the constraints that shaped it. This is a fixed set of short named fields on purpose. Orgabot does not store or expose private model reasoning, and a write carrying a reasoning transcript, an unrecognized field, raw log content, or secret-shaped text is refused, never silently trimmed.

The dashboard is the equivalent surface: GET /api/objectives returns the same per-node provenance and scheduling state plus the same diagnostics and metrics the CLI prints. Both are read-only projections and neither starts work.

Shipping

orgabot ship [project] [--branch B] [--all] [--no-review] [--no-ci] [--no-merge] [--review-max N] [--deliver-as <roleSlug>] [--dry-run]
orgabot review <project> <pr#> [--post]
orgabot review deep-scan <project> [--disclose-output]   # whole-repo scan; the issue carries metadata only unless you authorize the raw output
orgabot review-loop <project> <pr#> [--max N] [--edit-only | --shell] [--no-merge]
orgabot merge <project> <pr#> [--method squash|merge|rebase]
orgabot ci-remediate <project> <pr#>
orgabot conflict-remediate <project> <pr#>
orgabot approvals | approve <id> | deny <id>
orgabot pending [list] | pending reap [--dry-run]

Every launch-capable subcommand here honors --help/-h (prints usage, launches nothing, exit 0) and refuses an unknown flag (usage + exit 2) rather than silently ignoring it. orgabot ship with no arguments and exactly one built-but-unshipped branch ships it immediately; pass --dry-run to see what it would ship without shipping it.

Organizations

orgabot organization bootstrap [--name N] [--owner N]
orgabot organization staff <org> | template list | template install <org> <template-id>
orgabot organization agent create <org> <role-slug> [name]
orgabot organization actor create <org> <type> "<name>" | assign <org> <role-slug> <actor-id>
orgabot organization project add <org> <project>... [--scope internal|client] [--client slug]
orgabot organization list | show <org> | archive <org> [--reason text]
orgabot organization department list <org> [--json]
orgabot organization department membership add|remove <org> <role-slug> <department>
orgabot organization approvals <org> [--status] | approve <org> <id> | deny <org> <id>
orgabot organization ledger verify [org]
orgabot organization ledger repair-fork <org> --reason <text> [--yes]
orgabot organization home <org> | attention <org> | health <org> | risks <org> | milestones <org>

ledger repair-fork is a narrowly guarded recovery for a historical two-head fork caused by concurrent appenders. Without --yes it only previews the fork entry and both heads. With --yes it first persists a readable immutable approval event, then appends the reconciliation entry; it never rewrites either historical branch. It refuses entry-hash corruption, duplicate entries, unknown parents, and more complex fork shapes.

The executive read models (home, attention, health, figures, autonomy, risks, proposals, milestones) answer business questions (what was asked, what was delivered, what needs you, what is at risk) rather than engineering ones.

Roles, capabilities, and connectors

orgabot role pack add|rm <org> <role> <pack-ref> [--provenance builtin|user|imported]
orgabot role tool grant|revoke <org> <role> <tool> [--config c] [--scope s]
orgabot role connector grant <org> <role> <connector> [--scope read] [--repos a,b]
                                                   [--permissions k=v] [--expires ISO] [--security-review]
orgabot role slot fill <org> <role> <category> <connector-id>
orgabot role duty add <org> <role> [<project>] "<objective>" --cron "<expr>" | --interval <dur>
orgabot duty list <org> | duty run-due [<org>] [--now ISO] [--dry-run]   # no <org> runs every active organization

orgabot scheduler install [--cadence "<expr>"] [--target local|cloud]
orgabot scheduler reinstall [--cadence "<expr>"]   # remove any entry left under the old spelling, then reinstall
orgabot scheduler uninstall | scheduler status | scheduler list

orgabot connector add <org> <provider> [--scope read] [--base-url u] [--category c]
orgabot connector connect github-app <org> --app-id <id> --installation <id> [--repos a,b]
orgabot connector mcp-plan <org> | connector mcp-import <org> <server-name> [--enumerate]
orgabot connector mcp-configure <org> <name> (--url <https-url> | --command <executable>)
orgabot connector mcp-authorize <org> <connector-id> --token-env <ENV_NAME>
orgabot connector mcp-authorize-env <org> <connector-id> --bind TARGET=SOURCE_ENV
orgabot connector logs <kind> [--instance <id>] [--event <event>] [--limit <n>]
orgabot tool run <tool> [args...] [--org <org> --role <role>]
orgabot compliance add <org> <name> --applies-to <role,role> [<pack-ref>]

orgabot connector logs reads the per-connector log the dashboard's Connectors → Available page renders: connection attempts, token mints, provisioner steps and errors, already scrubbed of secrets and capped at 500 entries (100 per kind). It is an observation surface and not an audit log, and it grants nothing.

Connector secrets and bring-your-own App private keys go through ORGABOT_CONNECTOR_SECRET / ORGABOT_GITHUB_APP_KEY or stdin, and land in this platform's protected credential store (macOS keychain, Linux Secret Service, Windows DPAPI, or your own cloud secret manager), never in a config file. The published App key is held only by the isolated shared broker; a tenant receives neither that key nor its installation token. orgabot secrets status reports the selected custody provider and why. orgabot secrets migrate moves a legacy connector-secrets.json into it, and orgabot secrets migrate --from <kind> --to <kind> <handle>... moves named handles between backends.

Executors, quota, and budgets

orgabot organization role-provider <org> <role> <claude|codex|grok|default> [--model m]
orgabot organization role-executor set|show|clear <org> <role> <provider> [--model M] [--effort E] [--key K]
orgabot organization default-worker set|show|clear <org> <provider> [--model M]
orgabot organization role-fallback set|show|clear <org> <role> "claude:opus,codex"
orgabot organization quota-policy show|set <org> [<role>] wait-then-escalate|escalate
orgabot pools list | clear <provider> [model] | set <provider> [model] (--reset-at <ISO> | --entitlement) | pools holds | resume-due | resume
orgabot budget set <org> --level org|role|actor --period daily|weekly|monthly|mission --limit <usd>
orgabot budget list <org> | remove <org> <id> | status <org>
orgabot analytics backfill
orgabot verify slots                        # host admission budget: full/light/scan verify AND worker/loop sessions
orgabot verify preset [--set laptop|workstation|ci_offloaded] [--suggest]

Context and memory

orgabot context sources list | bind <source-id> … | sync <source-id> | --all
orgabot context distill [--org <org>] [--project <project>] "<requirement>"
orgabot context canonical set|show|clear
orgabot context issue-source set <project> <owner>/<repo>       # project must resolve to the registered repository root
orgabot context drive-source set|connect|credentials|folders|show|clear|list
orgabot context holds | resume
orgabot memory repo set | reindex | sync-approvals
orgabot memory list | findings | resolve | approve | reject
orgabot sink list | sink deliver <sink> …

Skills

orgabot skills connect <org> <owner/repo> [--path .claude/skills] [--ref main] [--cadence <ms>]
orgabot skills admit <source-id>         # requires an approval minted for this exact content
orgabot skills deny <source-id>          # clear a proposal the owner refused
orgabot skills list | show <source-id> | disconnect <source-id>
orgabot skills sync [--source <id>] [--check] [--due]
orgabot skills materialize | verify
orgabot skills cadence <source-id> <ms|off>

Connecting admits nothing: skill content is instruction authority injected into workers, so it reaches a worker only at a commit an owner approved. sync serves the pinned commit and nothing else; --check PROPOSES a bump and never applies one. See Skill registry.

Dashboard, GitHub, and recovery

orgabot up [--port N] [--no-restate] [--no-github] [--no-dashboard] [--restart]
orgabot stack status
orgabot down
orgabot dashboard [--port N] [--no-durable] [--no-github-connect]
orgabot github connect | disconnect
orgabot firebase connect --project <firebase-project-id> [--orka-project <project>]
                      [--backend <location>/<backend>]… [--credentials <sa.json>] [--poll-seconds N]
orgabot firebase status | check | disconnect
orgabot issues [--dry-run] [--yes]
orgabot triage <project> [<project2> ...] [--dry-run] [--title "text"] [--label <name>] [--epic-project <project>]
orgabot recover [--dry-run]
orgabot pr-recovery [list]
orgabot pr-recovery resume [<owner/repo#number>] [--dry-run]
orgabot pr-recovery opt-out <owner/repo#number> [--reason "why"] | opt-in <owner/repo#number>
orgabot ci-triage [list]
orgabot ci-triage run [<owner/repo#runId>] [--dry-run]
orgabot ci-triage opt-out <owner/repo#runId> [--reason "why"] | opt-in <owner/repo#runId>
orgabot remediate run-due | <mission-id>
orgabot finding remediate <org> <role> "<finding>" [--project <project>]
orgabot proxy [--project | --allow host1,host2 --network restricted|open|none]
orgabot opstore [status]                 # create or migrate the local operational store, and report it
orgabot opstore verify                   # integrity-check it and prove its change feed is readable
orgabot opstore prune-compat <domain> [--yes] [--dry-run]   # delete the leftover JSON files an older install wrote for a store-backed domain
orgabot opstore matrix [--json]          # every classified state entry, its authority and placement, and whether it is present
orgabot opstore backup <dest-path>       # consistent copy, safe to take while Orgabot is running
orgabot opstore restore <source-path>    # validate a backup, then make it live; refuses a damaged one
orgabot opstore rollback [<n>]           # list the databases restores replaced, or put one back

Every subcommand above that can launch missions with no positional argument (issues, duty run-due, recover, pending reap, ship with no arguments) honors --help/-h (prints usage, launches nothing, exits 0) and refuses an unknown flag (usage + exit 2) instead of silently ignoring it - orgabot issues --help used to run a full auto-issues sweep and launch ten missions for exactly this reason. A bare orgabot issues sweep prints the plan it would run and asks for confirmation before launching anything, unless --yes is passed or --dry-run already stops it there; a non-interactive caller (a script, a cron entrypoint) that cannot be asked refuses rather than defaulting to "yes".

Stale code, and how to restart

orgabot up and orgabot dashboard are long-running processes that keep running the TypeScript sources they loaded at start, so a merged fix takes effect only once they restart. The dashboard checks the checkout's HEAD against what it loaded on its own timer; once it falls behind, it raises a visible "Dashboard code is stale" incident naming both commits and, once the timer has computed it, how many commits behind it is. That card carries a one-click Restart dashboard button (drains in-flight requests, tears down the Restate components it owns, and re-execs against the current checkout so the mission service comes back up on fresh code too).

orgabot up --restart does the same thing from the terminal against an already-running dashboard on the resolved port, rather than starting a new stack. Both orgabot up (before it reclaims the port) and orgabot review-loop's post-merge step print the same staleness notice when they find one, so a just-merged fix that has not taken effect yet is never a silent mystery. The System tab's health grid also shows the running commit next to Restate readiness at all times, stale or not.

The orgabot duty run-due LaunchAgent/cron entrypoint execs the current checkout on every fire (it runs through tsx, which reads sources fresh each invocation), so a scheduled duty never runs stale code the way the two long-running processes above can.

orgabot dashboard also auto-starts the orgabot github connect webhook listener and tunnel when GitHub ingestion is already configured (a webhook secret exists and a GitHub App is connected), so live GitHub state does not need a second terminal. A running tunnel is reused, and a missing ngrok or missing configuration is a stated skip on the dashboard console, never a startup failure. Opt out per session with --no-github-connect, or globally with ORGABOT_AUTO_GITHUB_CONNECT=off. orgabot up manages the GitHub component itself (--no-github), so it is unaffected.

orgabot dashboard also brings Restate up first, by default (issue #1888), the same components orgabot up starts: without it, every mission launched from the dashboard is silently "Not restart-safe". A Restate already running is reused, never re-bound, so a second orgabot dashboard is safe. Opt out per session with --no-durable (or --no-restate, orgabot up's spelling); the saved durability preference (orgabot durability) still decides what an individual mission's launch does with the runtime once it is there.

opstore manages ~/.orgabot/operational.db, the local SQLite operational store and change outbox. Your mission lifecycle journal is still the authority for mission state. Approvals, mid-mission questions, built-but-unshipped records, organization state (the org chart, its audit ledger, and its memory index), the observation stores, the four acknowledgment stores, and mission records read and write this store and nothing else: there is no JSON copy of any of them and no fallback to one. It also exists so dashboard work can push changes to the browser instead of having the browser ask for everything every three seconds. orgabot opstore matrix [--json] lists every classified state entry under your home directory with its authority, its intended placement, the store domain that owns it, and whether the entry is present, so "what still reads a JSON file for truth here" is answered by the running installation rather than a document.

Two things follow, and both are deliberate. The JSON files an older install wrote for these domains stay on disk, unread - switching storage is not permission to delete your data - and orgabot opstore prune-compat <domain> deletes them when you ask. And if the store is unavailable, Orgabot refuses rather than quietly reading a leftover file. A fallback that fires only when the store is unreadable would put you back on two disagreeing copies at the exact moment you can least afford it.

restore validates before it replaces anything. The source is staged, opened, migrated to the current schema, integrity-checked, and read from its own retention watermark; if it is truncated, not a database, damaged, or was written by a newer Orgabot than you are running, the restore is refused and your live store is left exactly as it was. Your backup file is never modified.

Close the dashboard before restoring. If another process still has the store open, the restore refuses and names it: replacing the file underneath a running process corrupts its connection and loses whatever it writes next.

The database a restore replaces is not deleted. It moves into a rollback window (~/.orgabot/operational-rollback/, the newest five kept), so restoring the wrong backup is recoverable: orgabot opstore rollback lists them newest first and orgabot opstore rollback 1 puts the most recent one back. A rollback point is complete: the replaced database is checkpointed and its WAL sidecar travels with it, so rolling back returns the changes that were committed right up to the restore, not a truncated database that merely looks intact.

If Orgabot was killed rather than shut down, the next open runs a bounded structural check and orgabot opstore status reports what it found. A process that still has the store open is not a crash and does not trigger it, and one process closing never cancels the check another process's death earned. On a large database the check is skipped and reported as skipped rather than assumed clean; run orgabot opstore verify for the full check.

Raw mission logs, diffs, and other artifacts stay as files and are not moved into this database. Only small structured records that something needs to query belong in it.

The database file, its WAL sidecars, and anything orgabot opstore backup writes are held owner-only (0600), and an open that cannot set that is refused rather than left permissive. Once mid-mission questions moved in, this file holds answers you typed, which is the same reason ~/.orgabot/inputs/*.json has always been owner-only. Restoring a backup written elsewhere makes it owner-only too, so a permissive source cannot widen your live store.

Store-backed domains

Approvals, mid-mission questions (inputs), the built-but-unshipped records (pending), organizations, the two observations stores, and acknowledgments live in the operational store. They were moved there behind ADR 0063's bounded dual-write phase; that phase is complete, the machinery is gone, and every one of these domains now reads and writes SQLite only.

acknowledgments is four small documents: the failed-mission acknowledgments under the mission bar, the inbox acknowledgments, the Alert Bell read marks, and your catch-up watermarks. They are one domain because they are one kind of fact, "you have seen this". Nothing you click changes.

pending changes nothing you type: orgabot pending list and orgabot pending reap behave exactly as they did, and "actionable" is still derived from an observed pull request and an observed branch rather than from the record. Underneath, a record is a row keyed by its (project, branch) pair, so a delete is scoped to the repository it names, and a reap and a mission saving its record land in one transaction.

organizations is three documents at once - the organization store, the audit ledger, and the memory index - because they are one picture. Nothing you type changes, and neither does any authority rule. A role grant is still created only through the existing approval and authority checks; a connector permission is still the clamp of the App's own grant, the connector's provisioned ceiling, and the per-role grant, computed on every resolution rather than stored; and connector secrets are not in the store at all - it holds the same credential handle the JSON file always held, and the secret stays where it is. Role routing, capability lookup and grant resolution are indexed lookups of one role instead of a load of the whole document.

An organization store Orgabot cannot read is reported as unknown, never as empty. "This install has no organizations" is the one condition that lets a mission run as the generic, unattributed worker, so an unavailable operational store holds the mission and asks you rather than quietly running it as you.

observations is both observation stores - the Issues tab's record of what GitHub last said, and the Firebase App Hosting rollout watch. GitHub is still the authority on an issue and Google still on a rollout, the Firebase connector still only observes and never deploys or rolls back, and only a terminal failure is an incident - a CANCELLED rollout is your decision and an in-flight one has not failed.

The rule both stores are built on holds by construction. A failed or partial poll keeps the previous observation and reports the error, because replacing the list with an empty one is a false all-clear. One observation is one row - the whole issue list, or the whole set of rollouts, builds and incidents - written in one transaction, and no observation write ever deletes a row first. So an interrupted write leaves the previous observation standing. The only erase is orgabot firebase disconnect.

Mission records also live in SQLite (the lifecycle journal stays as files). orgabot opstore prune-compat <domain> --yes deletes the leftover JSON files an older install wrote for one of the domains above, and orgabot opstore prune-compat missions --yes deletes leftover missions/*.json records (never the .log files). Neither touches the SQLite store.