Skip to content

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

← All documentation
Using Orgabot

CLI reference: the command-line surface

The command-line surface, grouped by task: projects, missions, shipping, organizations, roles, connectors, context, budgets, and recovery.

Last updated

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.

Command groupWhat it doesWhere it is documented
Global--home isolation, reset, doctor, clean, and the assistant providerInstall and setup
Connectors and MCPPlan, import, migrate, and authorize MCP servers as connectorsConnectors and tools
ProjectsRegister, list, rename, and re-root projects; set the issue owner and review policyYour first mission
MissionsLaunch, follow up, observe, and cancel missions; epics, objectives, catch-up, resumeMissions in depth, Catching up
orgabot shellThe native interactive REPL: every command below works inside it unchangedThe interactive shell
Objectives and decompositionReshape a running plan with decompose and replan; read planning diagnosticsMissions in depth
ShippingShip, review, review-loop, merge, CI and conflict remediation, approvals, pending workShipping and review
OrganizationsBootstrap and staff an organization, its agents, actors, departments, approvals, and ledgerOrganizations and roles
Roles, capabilities, and connectorsAttach packs, tools, connectors, slots, and duties to roles; the scheduler; tool runsCapability packs, Connectors and tools
Executors, quota, and budgetsRole providers and executors, fallbacks, quota policy, pools, budgets, verify slotsBudgets, quota, and analytics
Context and memoryContext sources, distillation, holds, organization memory, and sinksContext and memory
SkillsConnect, admit, deny, sync, and materialize skills from a governed sourceSkill registry
Dashboard, GitHub, and recoveryThe stack and dashboard, GitHub and Firebase connections, issues, triage, recovery, the operational storeThe dashboard, Troubleshooting

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 doctor --claim-install       # make THIS checkout the installed build (rewrite the config pointer)
orgabot clean [--dry-run]            # release what finished missions still hold
orgabot clean --attribute            # which mission holds which directory and isolated clone, how big, and its live memory
orgabot clean --remove <dir> [--force] [--dry-run]   # remove one kept orka-wt-* root (e.g. an unattributed clone)
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> [--role <slug>]
                 [--instruction "<text>" | --instruction-file <path>]   # fix one work item; the router picks epic vs ordinary
                 [--integration-branch <branch>]                       # every child builds on, PRs into, and merges into this branch
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>" [--idle-timeout <minutes>] [--startup-timeout <minutes>] [--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 settle <id> <nodeId> --reason "..." --evidence "<PR/commit/issue url>"   # settle a failed or held node whose work was delivered
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 shell [--plain]                         # native interactive REPL; every command above works inside it unchanged

See The interactive shell for orgabot shell's commands, its /project//attach//detach affordances, Ctrl+C semantics, and its honest limits on session handoff and what survives a crash.

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 launch pipeline the dashboard's Fix button uses (the same normalization, Mission Record, and gates), with one difference in routing described below, 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. Invoking `fix-epic` declares the item an epic: the Mission Record carries the type epic (recorded as operator-declared), and the router selects autonomous epic execution without consulting tracker labels, so a missing epic label or a lone domain label such as seo can neither block nor override the launch. When the tracker classified the item differently, the command prints that on a line of its own. Epic discovery then finds the children at execution time: native sub-issues, the body, comments written by the item's own author, and items that reference it. Body and comment references are read for GitHub (#N), Linear (ENG-123), and Jira (PROJ-17) epics; a comment the tracker marks as a bot's is never read, even on an epic a bot filed. An epic with no children found by any of those routes holds with epic_no_children and names the remedy, and a declared epic whose every discovered child is unreadable holds with epic_children_unreadable rather than running as one unit. For one ordinary piece of work, use orgabot mission or the dashboard's Fix button, where the tracker's type still decides. 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.

--instruction "<text>" (or --instruction-file <path> for longer text) records operator guidance on the epic's own mission record, and every child mission inherits it as a clearly demarcated block of context appended to the child's own instruction. It is context, not scope: the child's work item, acceptance criteria, and every verification, review, and merge gate are unchanged, and the block says so in its own words. The instruction lives on the epic record rather than on the launch, so it survives restarts and a role hold, reaches children that start later after a partial run resumes, shows under orgabot status and on the epic's log, and is carried in the epic's reconciliation so the final report can be read against what you asked for. An epic coordinator has no branch, so orgabot follow-up cannot steer it; to change the guidance mid-run, re-run `orgabot fix-epic` on the same epic with the new `--instruction`: the running epic is not duplicated, its stored instruction is replaced, children that have not started yet pick it up on the epic's next pass, and children already running keep the text they launched with. Steer one child by following up that child's own mission. An empty instruction, one over 20,000 characters, an unreadable file, or both flags at once is refused before anything is resolved or launched.

--integration-branch <branch> declares an integration branch for the epic, for work that must stay off the default branch (for example, when the default branch deploys). Unlike --instruction, which is advice the worker may or may not follow, this is a recorded fact that the delivery code reads: every child the epic launches builds from that branch, opens its pull request against it, merges conflicts from it, and is merged into it. Ship, the review loop, conflict remediation, and merge never fall back to the default branch for such a child. A child PR whose base differs (one opened against the default branch before the declaration existed, say) is retargeted to the integration branch and re-read, or, if GitHub will not retarget it, the merge is refused and the mission holds with the reason; nothing is ever merged into the default branch on the epic's behalf. A branch origin does not have is refused up front (fix-epic checks), and a child whose base cannot be resolved from origin fails rather than building off whatever is checked out. The name must be a plain branch name and not the project's default branch. To apply it to an epic that is already running, re-run orgabot fix-epic on the same epic with the flag: the running epic is not duplicated, the branch is recorded on its coordinator, children that have not started yet build on it, and every child already running resolves it through its coordinator, so its PR is retargeted or refused before any merge. A later re-run without the flag keeps the declaration; to change it, re-run with the new branch (children already launched keep the one they launched with).

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.

--startup-timeout <minutes> sets the startup window (default 5): how long a launched or resumed worker may go without streaming its first turn before that executor is treated as unavailable for the round and the mission continues on the next executor in its fallback chain. See A worker that never starts.

--idle-timeout <minutes> and --startup-timeout <minutes> are accepted by orgabot mission, orgabot organization mission, and orgabot follow-up. The dashboard sets the same two options from the New Mission routing bar and the role card's Give a mission form; see Worker time limits. Both surfaces parse the value the same way, so a dashboard launch and the flag produce the same mission.

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 cancel on a running mission ends the mission's whole process tree, not one pid. It reads the process table before signalling, sends SIGTERM to the tracked host and every descendant (children first), waits a bounded grace period, and escalates to SIGKILL for anything that survived. Its output names the pids it signalled and whether they exited. A cancel that leaves a process running says so and exits non-zero with the pids to kill by hand; it never prints "cancelled" over a host that is still spending CPU. The record flips to cancelled in the same step as the signal, so liveness reconciliation never sees a running record whose host just vanished.

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.

Stop and cancel mean different things to a plan node. orgabot stop (or the dashboard's End) on a node's mission ends that round. The node reads failed, keeps blocking its dependents, and waits for you to follow up, replan, or settle it; it is never dropped from the plan as cancelled. Only an explicit orgabot cancel cancels the node's work. A node whose delivery merged is never treated as cancelled, whichever command ended its mission.

Settling a plan node. Supervision settles an epic node completed from observed facts, and records the evidence on the node and in the objective's settlement trail (orgabot objective show prints both):

  • a merged delivery for the node's work item, by the node's own mission or any other, settles a failed or held node;
  • a source issue closed as completed settles a failed node, citing the merged pull request the issue's timeline closes or cross-references when there is one;
  • a held node (its mission is waiting or queued, with no live worker) settles on a closed issue only when such a merged pull request is observed, so an issue closed without one leaves the node held rather than silently completing it.

An epic child is re-checked before it launches. A child can wait days behind its prerequisites, or across an epic run that resumes after a long gap, while someone closes its issue. So immediately before supervision or the epic run launches a child, or relaunches one whose mission failed, it re-reads the child's source issue:

  • closed as completed: the node settles completed with that evidence and no mission launches;
  • closed as not planned: the node is skipped, with the reason recorded in its settlement trail (orgabot objective show prints it as skipped), no mission launches, and its dependents are released;
  • unreadable (the tracker is down, or returns nothing): the launch is held and the issue is re-read on the next pass. It is never treated as open, and never as closed.

Outside that pre-launch check, an issue closed as not planned settles nothing, and a node whose work is genuinely unfinished keeps blocking its dependents. For anything supervision cannot see, orgabot objective settle <id> <nodeId> --reason "..." --evidence "<url>" settles a failed or held node by hand. Settling a held node also ends its waiting mission, keeping its worktree and branch exactly as orgabot stop does. A settlement is final: no later cancellation or supersession reopens it, and a node an earlier pass wrongly dropped as cancelled, though it carries a settlement or a merged delivery, returns to completed on the next supervision pass.

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".

wait also works on the id orgabot mission ... --background prints when an organization decomposes the mission. That id names an objective, and for the first seconds or minutes (while the launch is routed and decomposed) neither a mission record nor an objective exists for it yet. wait treats the id as still being routed as long as its launch transcript is being written or the durable supervisor still holds its submission, keeps polling (subject to --timeout), and then waits on the objective's nodes. An objective that exists but has no plan nodes yet is also still being routed. The --json result for an objective carries objectiveId, the aggregate ok / verdict / outcome, nodes (each with its mission id, prUrl, and followUps), and top-level nodeMissionIds and prUrls; the human output lists each node's mission id and PR. A launch that stops before creating any record is reported as launched but never became a mission or an objective, never as an unknown id. orgabot logs <objective-id> prints the routing transcript and then names each node mission's orgabot logs command on stderr.

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] [--merge-as-is=<input-id>]   # --merge-as-is is what answering a review-loop decision with "merge" runs
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, and which stranded branches it would recover, without shipping or recording anything.

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 rewrite-missing [--dry-run] [--org <org>]   # write records that were never written; never overwrites, never runs git
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 [--force]]
orgabot stack status
orgabot stack restart [--port N] [--force]   # same as orgabot up --restart
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 issues context-retries [--clear <deliveryId> | --clear-blocked | --clear-all]
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 acceptance reassess <mission-id | owner/repo#N>
orgabot acceptance pending [--all]
orgabot acceptance assessor [show] | set --provider <id> [--model <m>] [--fallback <id[:model]>]... | clear
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 | --markdown]   # every classified state entry: authority, current and target storage, owner, retirement, and whether it is present
orgabot opstore cutover [status | <batch>|all [--dry-run] [--from-fence] | <batch> --revert | <batch> --retire [--close-window --note <why>]]   # move a batch of control documents into the store
orgabot opstore lifecycle [status | advance | import | equivalence | revert | retire [--close-window --note <why>]]   # move the lifecycle journal into the store, one gated phase at a time
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
orgabot opstore organization-layout [status | migrate | rollback --yes]   # where the organization store's storage layout stands, and move it
orgabot app status [--json]              # which state store this host selected, and its process, replica and transaction model
orgabot app matrix [--json]              # which surfaces share the application stack, and which state stays host-local
orgabot app ack <id> --created-at <iso> [--expected-version N] | app unack <id> | app read <id>...   # acknowledgments, through the same service the dashboard uses
orgabot app approvals | app inputs [--status S] [--limit N] [--cursor TOKEN]   # paged reads from the selected store
orgabot app changes [--limit N] [--cursor EPOCH:SEQ]   # the change outbox, paged

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). The replacement view it starts takes over the old view's process record, so the orgabot up that owns the view still owns it afterwards, instead of the view becoming an orphan. The old view process exits, so anything it had started is reaped by the system rather than lingering as an exited process that still looks alive.

orgabot stack restart (or its alias orgabot up --restart) is the terminal equivalent, and it covers the whole stack, not just the dashboard. It restarts every stack process Orgabot started that is running code older than the checkout: the mission service (src/restate/server.ts), the dashboard view, the GitHub webhook listener (orgabot github connect), and last the orgabot up supervisor itself, which is where the recovery sweep and runtime health run. --force restarts them even when they are current. It leaves the restate-server binary running, because that runs no Orgabot code and holds the journal; a durable mission that was mid-flight in the old mission service is retried against the new one and replays its journaled steps instead of running them again. After the new mission service starts, the restart re-registers the Mission deployment with force: true, so Restate uses the handler definitions the new service actually serves. Restate keeps the same deployment id, so an invocation that was in flight stays attached to a deployment that exists; it replays against the new code either way, because the old service process is already gone. If Restate answers with a new id instead, the restart says so.

The supervisor restarts itself without stopping the processes it owns: it hands them over by record and exits, and a new supervisor on the checkout's code adopts them. Under the com.orgabot.stack LaunchAgent, stack restart runs launchctl kickstart -k gui/<uid>/com.orgabot.stack and launchd starts the new one. Otherwise (a foreground orgabot up, or the headless one orgabot dashboard starts) the old supervisor starts its replacement itself, with the same arguments. A foreground `orgabot up` does not keep its terminal: the replacement runs detached, both it and the old terminal say so, its output goes to ~/.orgabot/logs/stack/supervisor.log, and orgabot down stops it (Ctrl-C in the old terminal no longer does, because that process has exited). A dashboard view the old supervisor ran is restarted by the new one. Before it hands over, the supervisor checks that the checkout's CLI loads and that the replacement's stop channel is written; if either fails it keeps running unchanged, and stack restart says why and exits 1, so a broken checkout never takes the supervisor down. The restart reports success only once the new supervisor has marked itself ready (its restart channel, runtime health and recovery sweep are running) on the checkout's commit; otherwise it prints supervisor NOT confirmed with the command to run, and exits 1. A supervisor started before this was possible cannot restart itself and is reported as NOT RESTARTED, with the exact command that restarts it (Ctrl-C and orgabot up again for a foreground one, orgabot down and then orgabot up --no-dashboard for the headless one, or the launchctl kickstart line for the LaunchAgent), and stack restart exits 1. A webhook listener owned by such a supervisor is reported the same way.

A stopped dashboard can take a few seconds to release its port, so the dashboard step waits up to 30 seconds for the port before it reports not replaced. When the supervisor hands over, the dashboard's final verdict is what is observed on its port afterwards, not what the step reported before the handoff: if the new supervisor started the dashboard itself, stack restart prints the listening pid, its start time and that it serves the checkout's code, and reports success. A listener counts only when it is verified as this checkout's dashboard: the port's dashboard record names it (or the process group it runs in), it started after the restart began, it runs this checkout's orgabot dashboard, and it loaded the checkout's commit. Any other process on the port, such as a dashboard from another checkout, fails the restart and the line says why. When something is still not on the checkout's code, the last line names each such component with its pid and start time, for example the stack is NOT fully on the checkout's code; not on it: dashboard :4317 (pid 3039, started Mon Oct 5 11:40:02 2026).

Restarting the webhook listener also restarts its ngrok tunnel: the listener owns the tunnel, and the new listener points the GitHub App webhook at whatever URL the new tunnel gets. Deliveries GitHub sends while no listener answers are not received, so the restart prints that window (start and end time) and whether the URL changed. GitHub does not retry App webhook deliveries on its own; they appear as failed in the App's Advanced settings, under Recent Deliveries, where you can redeliver them, and Orgabot's reconciliation poll picks up the same state regardless. If the new tunnel reports no URL, the restart says webhook deliveries are not being received and names orgabot github connect.

Mission processes (follow-up rounds and so on) are never restarted; they finish on the code they started with, and orgabot stack status names any still running old code. A mission process records its own start time and the commit it loaded when its round starts, so that verdict is exact, and a pid the system has since reused is not mistaken for the mission. Rounds recorded before that fall back to comparing the process start time with when the checkout last moved.

The restart finds processes only through the identity record Orgabot wrote when it started each one, under ~/.orgabot/stack/processes/: the pid, the start time the process table reported for it, the commit it loaded, and the orgabot up that owns it. It never searches for processes by name, and a pid that now belongs to a different process (the start time no longer matches) is never signalled. Before any signal, the process's command line must also match the recorded component (this checkout's src/restate/server.ts, its dashboard, its github connect, or restate-server), so a record that names some unrelated process is refused. A stop is reported only once the whole process group has exited and the port is free. stack restart, the bring-up in orgabot up, and orgabot down take a lock under ~/.orgabot/stack/, so they never run over one another; the second one waits, then plans against what the first one left. The wait is bounded at 120 seconds: after that the command gives up without changing anything and says another stack operation is running (<operation> (pid N)), naming the holder, so retry once that operation finishes. A lock left behind by a process that has exited (or whose pid now belongs to another process) is cleared automatically, as is an unreadable lock file more than a few seconds old. orgabot reset refuses a home whose recorded stack processes are still running. When the owning orgabot up is still running, the restart is handed to it and reported only once it acknowledges; when that orgabot up has exited (an orphaned stack), the restart stops the recorded processes itself and starts replacements. A dashboard you started by hand with orgabot dashboard has no record, so for that one the command falls back to asking the dashboard to restart itself. It reads that dashboard's per-process token the same way the dashboard page does, then sends the same request as the System tab's Restart button; the token is never printed. If the token cannot be read, the dashboard refuses the request, or the dashboard cannot be reached, the command says which one happened and what to do next.

orgabot stack status lists each recorded process with its pid, whether a live orgabot up owns it or it is ORPHANED, and whether it is running the checkout's code or is STALE, followed by the command that fixes what it found. A stale supervisor gets its own line, because its recovery sweep is on old code too: orgabot stack restart when it can restart itself, or the exact command that restarts it when it cannot. orgabot down stops an orphaned stack the same way, by recorded identity, including its restate-server; a stack whose orgabot up is still running is stopped from that terminal with Ctrl-C, as before. A new orgabot up that finds an orphaned Restate or webhook component still answering reuses it and adopts its record, so that orgabot up owns it from then on.

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. --markdown prints the same table with each entry's owning module and where its retirement stands.

The remaining control documents move in batches: the project registry (projects), mission queues, holds and requests (mission_control), recovery queues (recovery), sink cursors (sinks), context and source registries (context), memory distillation and playbooks (memory), and verification and quota caches (verification). A new install keeps them in the store from the start. On an existing install the files stay the authority until you run orgabot opstore cutover <batch> (or all), with the dashboard, the scheduler and any running mission stopped: the command refuses while another process has the store open. It reads every file first, imports them, reads every row back and compares it with its file before committing, then moves the files into ~/.orgabot/state-imported/ instead of deleting them. Running it again is safe. --dry-run checks the files without switching anything. While a batch's rollback window is open (30 days for projects and context, 14 for the rest), --revert writes the store's current documents back to their files. --retire closes the window; it is refused early unless you pass --close-window --note "<why>". After that, prune-compat <batch> deletes the fenced files.

orgabot opstore lifecycle moves the mission lifecycle journal the same way, one phase per command: advance goes from file_only to dual_write, where every append is mirrored into the store while the files stay the journal; import backfills older missions; equivalence compares every event; the next advance switches to store_authoritative only if that full comparison is equal. After the switch the files are kept current as the path back for 60 days (revert returns to them), until retire stops writing them.

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.

The organization store is kept one row per record for the collections that grow (roles, grants, connectors, requests, missions, the audit log), so a change to one role or one request reads that organization's rows and writes only what changed. Two edits to different records at the same moment both land; two edits to the same value of the same record are refused rather than one silently winning. The first organization change after upgrading moves an older install to this layout by itself, in one transaction that checks the result reads back identical and changes nothing if it does not. orgabot opstore organization-layout shows which layout the store is in and what the last move recorded. orgabot opstore organization-layout rollback --yes moves it back to the previous layout, the same organization data read back identical, and keeps it there until orgabot opstore organization-layout migrate.

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.

orgabot app runs against the state store deployment.json selects (see Deployment topology): local SQLite by default, or managed PostgreSQL when the topology places state_store there. The local dashboard's /api/v1 routes, the hosted control plane and orgabot app all use the same application services, so an acknowledgment, a page of approvals, or a page of changes behaves the same way on either backend. A stale --expected-version is refused with a version conflict instead of overwriting. On a host configured for PostgreSQL, orgabot opstore (other than matrix) and every command that would open ~/.orgabot/operational.db refuse with a message naming deployment.json and the tenant. They do not create the file and they do not read old JSON records.

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.

A mission whose record write cannot reach SQLite (store busy, unavailable, or refusing secret-shaped content) writes missions/<id>.json instead and logs mission record <id> written to its JSON fallback [<kind>] (a busy or failed write logs operational mission record write failed [<code>]). The next write that reaches SQLite removes a file older than that write (a process rechecks a mission at most every 10 minutes), so a file that stays well past that is either a refused record (its only copy) or a sign the store is still failing. A write that waits for or holds the store's write lock for over a second logs operational store write lock slow with its process and caller.