orgabot shell: the native interactive shell
Launch the native shell, map its commands to the CLI, and read its honest limits on session handoff, keyboard controls, and what survives a crash.
Last updated
orgabot shell is a native, terminal-based REPL that sits next to the dashboard's mission terminal as the other place you drive missions interactively. It is not a second implementation of mission launch, follow-up, cancel, or approvals: it reuses mission intake, the planner, the work graph, the scheduler, the capability model, lifecycle, configuration, and credential custody exactly as every other orgabot surface does, and dispatches every recognized command through the same command table orgabot <command> already uses. A typed command inside the shell behaves byte-for-byte like its CLI form.
Starting it
orgabot shell [--plain]--plain renders the startup banner without color/box-drawing, for a terminal or a captured log that cannot render it. --help/-h prints usage and starts nothing.
The shell starts fresh: it does not attach to any mission on launch, and it starts none of the long-running companions (orgabot up, orgabot dashboard, the headless recovery scheduler) on your behalf. It only opens a read-eval-print loop over your terminal's stdin/stdout, sets an "ambient current project" you can point a bare instruction at, and dispatches into the same command table the CLI already has. If you want a mission you launch from the shell to be restart-safe, or you want the recovery/reconciliation passes running, those still come from whatever else you have running (orgabot up, a durable Restate runtime, an installed scheduler job): the shell does not start or supervise them. See Runtime ownership below.
Only one shell REPL can own your terminal's stdin at a time: running shell from inside a shell is refused outright (rather than opening a second readline.Interface on the same stdin), because two readers would both receive every line typed, including a credential meant only for the one that prompted for it.
Commands
Everything is one of three shapes:
| Form | Behavior |
|---|---|
Any orgabot <command> | Dispatched through the identical command table the CLI uses: mission, follow-up, status, session, cancel, stop, interrupt, ship, review-loop, answer, and everything else in the CLI reference work here unchanged. |
A bare line with no recognized command, while a project is set (/project <id>) | Sent as a mission instruction against that project, exactly like orgabot mission <project> "<instruction>". This is the shell's one genuinely new affordance: the CLI has no standing "current project" between invocations. |
A /-prefixed shell-only command | Handled locally, listed below. |
Shell-only commands (/help prints the same table):
| Command | What it does |
|---|---|
/project <id> | Sets the project a bare instruction launches a mission against. |
/attach <mission-id> | Follows an existing mission (for example one launched from the dashboard). See Session handoff. |
/detach | Leaves the shell, printing the session id and the attached mission id (if any) so you have what /attach needs later. The mission keeps running: it runs server-side, not inside the shell process, for a mission you launched with --background; see Runtime ownership for the mission you did not. |
/exit, /quit, or a bare exit/quit | Leaves the shell the same way /detach does, minus the reattachment framing. |
/help | Prints the command summary above. |
cancel <mission-id>, stop <mission-id>, and interrupt <mission-id> are ordinary shared CLI commands available here too, each with distinct meaning: cancel ends the mission (whole process tree, SIGTERM then SIGKILL), stop ends the mission (or every active node of an objective) with an optional reason, and interrupt requests a graceful pause-and-report rather than ending it: the same three-way distinction the mission terminal documents for Escape and End mission.
Session handoff
The shell mints a conversation identity (an InteractiveSession, prefixed ishell_) when it starts, and that identity can attach zero or more mission sessions to itself over the life of the process.
Read this literally, because the honest limit matters: that conversation identity, and everything the shell itself tracked about it: its event timeline, which mission sessions it attached, in what order: lives only for the lifetime of the shell process, the same way a terminal's own scrollback does not outlive the terminal. There is no persisted interactive-session table. Closing the shell and running orgabot shell again does not resume the same conversation identity; it mints a new one.
What does survive is the mission itself. Each mission session's own durable state lives in the mission lifecycle, independent of any shell. /attach <mission-id> (or the plain CLI form, orgabot session <mission-id>) reattaches by mission id, reading the mission's own durable session and giving you the equivalent of orgabot session <mission-id>'s canonical view inline. So:
- "Reattaching to a mission" after restarting the shell works, and is the intended path:
/detachprints exactly the mission id you need, and a freshorgabot shellpicks it back up with/attach <that-id>. - "Reattaching to a conversation": the shell's own sense of what you were doing, independent of any one mission: does not survive a restart. A new shell process is a new conversation identity every time.
This is the current, deliberate scope of the phase-1 contract (interactiveShell/contracts.ts): a conversation can exist with zero attached missions, and gains one each time it resolves an instruction into a mission or you /attach one. It is not a claim that the shell itself is durable: only that the missions it drives are (subject to Runtime ownership below).
Keyboard controls
| Key | What it does |
|---|---|
| Ctrl+C | If a mission is attached (bound by a launch from this shell, or by /attach), requests a graceful interrupt on that mission: the same acknowledged-interrupt model the mission terminal's Escape uses, never a kill. If no mission is attached, Ctrl+C does nothing (it does not exit the shell or kill the process). |
| Enter | Submits the typed line. |
| Up / Down | Recalls prior lines from this shell session's own readline history. |
| Ctrl+D on an empty line | Closes the input stream, which the shell reads the same as /exit. |
Ctrl+C is deliberately not SIGINT-as-kill: the shell installs its own SIGINT handler (on both process and the readline.Interface, since in terminal mode readline intercepts Ctrl+C itself and would otherwise treat it as a request to close the interface) and calls the same requestMissionInterrupt the rest of the CLI uses. That publishes an interrupt request for the process that owns the mission to pick up and acknowledge: it does not signal the mission's process directly, and if no mission is attached, pressing Ctrl+C has no effect at all (it neither interrupts anything nor exits the shell).
Secret entry is protected, not just accepted
Typing a credential, meaning answering a secret prompt (context drive-source credentials, an answer to a secret input request, a multi-line PEM key), is deliberately kept out of three places it would otherwise land:
- Readline's own history. A secret answered through the shell's dedicated secret reader never goes through the ordinary line queue at all, so there is nothing for Up-arrow recall to replay. A credential supplied inline as a flag instead (for example
context drive-source credentials --client-secret abc123) does go through the ordinary queue, so it is redacted (--client-secret=[redacted]) before it is allowed to settle into readline's history. - The interactive session's own event timeline. The same redaction is applied to what gets recorded as a
command_invokedevent: the real, unredacted value still reaches the command being dispatched, only the durable record is scrubbed. - Echo. While the shell is waiting specifically on a secret prompt, input is read without being echoed back to the terminal.
Typing ahead of a prompt is also guarded: a line typed before a fresh credential/approval prompt is held rather than echoed or recorded, because until the prompt actually resolves, the shell cannot yet tell whether that line is about to answer it. A multi-line credential (an RSA/PEM private key) is terminated by an explicit -----END ... PRIVATE KEY----- footer line or Ctrl+D, tested one completed line at a time so trailing text on the same physical line as the footer can never falsely match.
Runtime ownership: what keeps a mission alive
A mission you launch from a bare instruction in the shell (/project foo then fix the failing test) runs foreground, in the shell's own process, exactly like typing orgabot mission foo "fix the failing test" directly at a terminal with no --background flag. This matches the mission terminal's own documented behavior for follow-up rounds: "By default the round runs in the shell that typed it, and dies with that shell." The same is true here, today, for a mission (not only a follow-up) launched from orgabot shell: close the terminal, kill the shell process, or lose the session, and a foreground mission it is running goes with it.
To get durability from the shell, pass --background on the full command form:
orgabot(myproject)> mission myproject "fix the failing test" --background
orgabot(myproject)> follow-up <mission-id> "add the missing test case" --background--background detaches the launch into its own process, exactly as orgabot mission --background and orgabot follow-up --background do outside the shell: the detached process's pid and exit are tracked independently of the shell, and its output is appended to the mission's log (orgabot logs <mission-id>), readable after the shell that launched it is gone. The shell's bare-instruction path (a plain line sent against `/project <id>`, with no explicit flags) does not currently pass `--background`: it always launches foreground. If you want a mission launched from a bare instruction to survive the shell closing, type the full mission <project> "<instruction>" --background form instead of relying on the bare-instruction shortcut.
A follow-up steered into a mission that is already running is delivered live or durably queued regardless of --background: that flag only matters for starting a new detached process; a round already running does not need one relaunched under it.
What the shell itself does not do
orgabot shell is a REPL over your terminal, not a supervisor. It does not start, and its exit does not stop:
- The dashboard, Restate, or the GitHub webhook listener. Those come from
orgabot up/orgabot dashboard, run in their own process(es). See Dashboard, GitHub, and recovery and The dashboard. - The background recovery scheduler: mission-liveness recovery, stale control-request reaping, throttle-queue drains, open-PR resumption, CI failure triage, delivery-merge reconciliation, and the rest of the passes described in ADR 0074. Those run from
orgabot up's headless supervisor, fromorgabot recover sweepas a scheduler-driven one-shot, or from an installed launchd/cron job (orgabot scheduler install): never from anything the shell starts, and never fromGET /api/state.
If none of those are running, closing the shell on a foreground mission loses more than that one mission's process: nothing is left driving liveness recovery, PR resumption, or CI triage for it either, because nothing in this install currently is. That is not a shell-specific gap: it is true any time you run orgabot mission bare at a terminal with nothing else running: but it is worth stating plainly here rather than leaving an operator to assume the shell itself supervises anything beyond the REPL.
Recovery guarantees: crash, close, and host restart
Given the above, here is what actually happens in each case, stated only as far as the code and ADR 0074 document it:
- The shell process itself crashes or is closed, mission launched with `--background`. The mission's own detached process is unaffected: it was never a child of the shell's own liveness, only of the launch. It keeps running, and
orgabot logs <mission-id>/orgabot statusread it back independent of the shell. Reattach a later shell to it with/attach <mission-id>. - The shell process itself crashes or is closed, mission launched without `--background` (the default for a bare instruction). The mission's worker process dies with the shell, the same as any other foreground CLI invocation being killed. What is preserved is whatever the mission's own lifecycle already commits as it runs (branch, worktree, worker session, recorded commits): not the round's continued execution. Nothing about
orgabot shelladds session-loss handling beyond what already exists for a killed foregroundorgabot missionprocess. - Terminal window closes without killing the shell process (for example a detached
tmux/screensession, or a backgrounded shell job): the shell process and anything foreground under it keep running exactly as they were; only the terminal that displayed it went away. A new attachment to that same process (reattaching thetmuxpane, for instance) is what gets you back to it, not/attach, which reattaches a mission, not a terminal. - Host restart. Nothing about
orgabot shellitself is restart-safe: it is a plain foreground process with no daemon and no supervision of its own (ADR 0010's no-daemon stance for a local-first control plane covers this deliberately). A--backgroundmission's detached process also does not survive a host restart: it is a detached OS process, not a durable workflow by itself. What can survive a host restart is the mission's own Restate-durable execution state (if the mission was launched with a live durable runtime) and the recovery scheduler's own reconciliation once something restarts it: an installedorgabot scheduler installjob is what makes the sweep itself come back after a reboot without an operator remembering to. If nothing is installed to relaunchorgabot upor the scheduler after a restart, no autonomous recovery pass runs until an operator starts one by hand.
Known gap, stated plainly rather than glossed over: there is currently no single command that reports "is anything currently watching over the mission I just detached from" from inside the shell itself. orgabot doctor and orgabot status report scheduler/recovery execution evidence (ADR 0074 point 5: a recent live owner reports running, a recently completed one-shot reports recent, and stale/missing evidence never reports running) but you have to think to run them separately: the shell does not surface that state automatically on /detach or /exit.
Related
- The mission terminal: the dashboard's browser-based equivalent, with the same interrupt, follow-up, and
--backgroundsemantics. - CLI reference: every command the shell dispatches verbatim, including
mission,follow-up,--background,--edit-only, and--shell. - The dashboard and Deployment topology: what
orgabot upstarts, and where scheduled duties and durable execution actually run.