The mission terminal
How the interactive terminal works: launching by typing or by dictating, streaming output, the one-row head and metadata, structured mission outcomes and their lifecycle, steering a running mission, acknowledged Escape interrupts, edit-only vs operate, and what a follow-up round actually re-runs.
The dashboard's mission terminal is meant to feel like an interactive coding terminal you already know: you launch work in it, it stays attached, output streams in, and you can redirect or interrupt without losing the thread. The org chart adds routing, authority, and capabilities around that familiar interaction. This is the governing near-term product decision (ADR 0055).
The session contract
- You type an instruction into the terminal and it launches a mission.
- The same terminal stays attached while the mission runs.
- Lifecycle events and worker output stream into it, pushed as they are committed rather than fetched on a timer. Your place in the session is a durable position, so a dropped connection, a refresh, or a restarted dashboard resumes exactly where you were. See the live terminal.
- Escape requests an acknowledged interrupt (see below).
- You can supply a new instruction in the same session.
- Orgabot resumes the same mission lineage through verification, review, and delivery.
The prompt
The prompt behaves like a shell prompt, and the same keys work in the New Mission launcher, in a running session, and in the mission drill-down (all three share one history per mission):
| Key | What it does |
|---|---|
| Enter | Sends. |
| Shift+Enter | Starts a new line. |
| Up | Recalls the previous message you sent. Pressing it again walks further back. From inside a multi-line draft it only recalls when the caret is on the first line; otherwise it moves the caret. |
| Down | Comes back through the recalled messages to the one you were writing, which is returned to you unchanged. |
| Escape | Stops dictation if the microphone is live; otherwise clears a non-empty prompt, closes the launcher on an empty prompt, and requests an interrupt in a running session. |
The field grows as you type up to about a third of the pane, then scrolls, so a long message is never clipped to the two rows the prompt starts at.
Dictating instead of typing
Every terminal prompt has a microphone beside it: the New Mission launcher, and the steering prompt of a mission that is already running. Press it, say what you want done, and press it again (or press Escape) to stop. What you said is written into the prompt as ordinary text.
- Dictation never sends anything. The transcript lands in the prompt as an editable draft, exactly as if you had typed it. Launch is still the only thing that starts a mission, and Send is still the only thing that steers one. A misheard word is yours to fix before it becomes work.
- It inserts at the caret. Type half a sentence, dictate the rest, and you get both. Dictation never replaces what is already in the prompt.
- It corrects itself as you speak. The words appear while you are still talking and are revised as the recognizer settles on them, so the prompt shows the current best transcript rather than a growing pile of guesses.
- The recording state is stated, not just coloured. The mic pulses, and a
listening…line appears while the microphone is live. - Escape stops the microphone, and only that. In a running session Escape otherwise requests an interrupt, so while you are dictating it is taken as "stop talking" and nothing else; press it again for its usual meaning.
- Everything you said is kept when you stop. Stopping folds the last, still-forming phrase into the prompt rather than discarding it. Sending stops the microphone and drops whatever came after the message you sent, so the emptied prompt does not open pre-filled with the tail of the previous one.
- One microphone at a time. Dictation belongs to the prompt you started it on. Closing that pane, or switching to another mission, ends the recording and leaves what was already dictated on that prompt's own draft.
Your browser does the transcribing, not Orgabot. Dictation uses the browser's built-in speech recognition (Apple Speech on Safari for macOS; the browser's own service in Chrome and Edge). Orgabot adds no transcription credential, never receives the audio, and sees only the text you finally send. The first press asks for microphone permission, which is granted per site in your browser's settings.
If your browser has no speech recognition, the microphone is shown disabled and says so on hover; typing is unaffected. It is disabled the same way while a message is in flight, because a prompt you cannot type into is not one you can dictate into either.
Inserting a skill with `/` or `$`
Typing `/` (or `$`, if that is the character your other tools use) at the start of the prompt, or after a space, opens an inline list of the skills installed on this machine: the ones under ~/.claude/skills and in your Claude Code plugin marketplaces, including anything the skill registry has materialized there.
- Keep typing to narrow it. The characters after the trigger filter the list as you go, matching the skill's name first and its one-line description second, so
/auditfindsseo-auditand/marketingfinds the skill that describes itself that way. Each row shows the skill's name and what it does. - Up and Down move the highlight, wrapping at both ends.
- Enter or Tab inserts the highlighted skill as
/skill-nameand puts the caret straight after it, so you carry on typing the rest of your instruction without pressing anything else. Clicking a row does the same. - Escape dismisses the list and nothing else: it does not clear the prompt or interrupt the mission the way Escape otherwise would.
- It never gets in the way. The list only opens on a trigger at a word boundary, so a path like
src/dashboardand an amount like$12.50stay ordinary text, and it closes the moment nothing matches what you have typed. While no list is open, every key above behaves exactly as it does in the table.
Completing a skill only writes characters into your draft. It grants nothing and starts nothing: Launch and Send are still the only things that do that.
Long pastes
Pasting something long (six lines or more, or a single line over 800 characters) puts a `[Pasted #1: 42 lines]` stand-in in the prompt instead of the blob, so you can still read the sentence you are writing. Nothing is truncated: the stand-in expands back to the exact characters you pasted when the message is sent, and the sent message is what goes into your history. Clearing the prompt with Escape discards the stand-in and the text behind it together.
Pasting images
Paste a screenshot (PNG, JPEG, or WebP) into the prompt and it becomes an attachment on the message you send, listed as a removable Image 1 pill under the prompt. Up to four images per message, 5 MiB each; anything else is refused with the reason, on the spot.
This works in both prompts: the New Mission launcher and a running session's follow-up prompt. Orgabot writes each image into its own private store, adds the file paths to the instruction the worker receives, and the worker opens them from disk. Attachments are removed after seven days.
One exception: while a session is waiting on a question, the prompt is the answer field, and an answer carries text only. Pasting an image there says so rather than showing it attached. Send the images with an ordinary follow-up instead.
Reading the scrollback
The scrollback holds the whole session, both sides of it:
- Your own turns appear in it. Every instruction, answer, and steering message you send is written into the log where you sent it, labelled
you, so scrolling up shows what you asked between the worker's replies rather than only the worker's half. - Failures are red. A line of worker output that reads as an error is toned red and a warning amber. A green summary is not:
0 errorsand8060 passed, 0 failedstay ordinary, while2 failed, 0 errorsis red, because the failure is the part you need to see. - Destinations are links. A URL printed into the log is clickable, and a bare
#1150links to that issue or pull request in the mission's own repository. Only those two shapes link, and only when the mission actually recorded a repository, so a link in the scrollback always goes somewhere real.
The log is the only thing that scrolls
The terminal is laid out the way Claude Code or Codex is: a compact head, one row of mission metadata, the log, and the prompt.
┌──────────────────────────────────────────────────────────────┐
│ ▶ running Implement OAuth support #123 PR #456 4m 09s ⋯ │
├──────────────────────────────────────────────────────────────┤
│ org · project · mission 8fc2… · role · model · branch … │
├──────────────────────────────────────────────────────────────┤
│ │
│ scrollable log stream │
│ │
├──────────────────────────────────────────────────────────────┤
│ > steering prompt [Send] │
└──────────────────────────────────────────────────────────────┘The head, the metadata row, and the prompt stay put; the log stream is the only region that scrolls vertically. Nothing above or below it scrolls inside itself any more: a long log never pushes the head or the prompt off screen, and no band of context can squeeze the log down to a sliver.
The head is one row. It leads with the mission's state, shown once, then the mission name, then the window controls. There are no decorative dots and no second line. Under pressure the mission name is the one thing that gives, clipping to an ellipsis; the state and the controls keep their size.
The metadata is one row, too. It opens with the linked pull request and issue (and the CI/CD run when there is one) and the running time, then org, project, mission id, role, worker, model, branch, the turn's definition of done, the session lineage, and the Objective chip, all on a single line that never wraps and never grows the terminal. Org, project, and role show their names (the org chart's name, the registry's project name, the seat's title), with the underlying id on hover. A long value is clipped, with the full value on hover, and the mission id and the branch each keep a copy button. When the facts outgrow the width, the row scrolls sideways behind a small fade at its right edge rather than showing a scrollbar or a second line.
The Objective chip
A mission that a larger Objective delegated shows an objective chip on the metadata row: the Objective's title, how many roles are done, and its status, with the next action and its owner on hover. Click it and the Objective's detail opens at the top of the log: the definition of done, the next action's detail, the delegated plan, and the correlated chronology of every role, with an Open button beside each role that swaps this terminal to that role's session. It lives in the log because it is the one piece of context tall enough to need scrolling, and the log is the only thing that scrolls. It opens where you leave it: a repaint while the Objective advances does not close it under you, and Hide (or the chip again) closes it.
How long it has been running
At the start of the metadata row, after the issue / PR links, the terminal shows the mission's running time: running 4m 09s while the mission is live, ticking every second, and took 1h 06m once it has finished, frozen at how long it actually took. It counts from the mission's recorded start, so it survives a refresh and a dashboard restart, and a mission whose start (or, once it has ended, whose end) was never recorded shows no clock at all rather than a made-up number.
What the worker is doing, in plain words
The worker's actions are narrated, not traced. A tool call prints one short sentence saying what it accomplishes rather than the command that did it, so a mission reads as work in progress instead of a shell transcript:
| The worker runs | The terminal says |
|---|---|
npm test -w @highwire/web -- --run | -> Running the tests |
cd framework && npm run typecheck | -> Checking types |
git commit -m "fix the gate" | -> Committing changes |
gh pr create | -> Checking GitHub pr |
an edit of src/app.ts | ✎ src/app.ts |
Four further reductions keep the feed readable:
- Looking around prints nothing. Reading a file, grepping, listing a directory and re-checking
git statusare how a worker orients itself between the things you are actually watching for. Each of those used to be its own line, dozens per round. Now the spinner below says the worker is busy and the scrollback stays about what it did. - A run of calls that mean the same thing collapses to one line. Two test runs back to back are one
Running the tests; a repeat after other work is a genuine second action and prints again. A file edited four times is named once. - A call a reader gains nothing from prints nothing. An edit is already reported by its own
✎ src/app.tsline, and a barepwdsays nothing at all. - Nothing that acts is dropped. Anything that changes something (a test run, a build, a commit, a push, a GitHub call, a delegation, a connector call) keeps its line, a tool Orgabot does not recognise still gets a line naming it, and the exact command is always in the audit log and the gate transcript regardless of what the feed shows.
The working spinner
While a mission is running, verifying, reviewing or delivering, the foot of the scrollback carries one live line: a spinner, a verb that changes as it works (Percolating…, Discombobulating…, Untangling…), and the recorded phase with how long it has been in it.
That line replaces the old heartbeat. A quiet phase used to append ⧖ still verifying . 3m00s to the scrollback every minute, which said only that nothing had happened, once per minute, forever. The spinner says the same thing without growing the log. It appears only while the runtime reports the session live and working: a mission waiting on you, or one that has finished, does not spin.
Heartbeats never appear in the log. Both shapes of heartbeat, the log line above and the journal's own heartbeat event, feed the spinner and nothing else. They are still recorded for diagnostics: the lines are in the mission log file, so tail -f on a mission log in a terminal shows liveness exactly as before, and the events are in the lifecycle journal.
Set `ORGABOT_VERBOSE_TOOL_LOG=1` to put the raw form (-> Bash npm test -w @highwire/web -- --run) back in the feed for a debugging session. It is a display switch only: it changes no command, no gate, and no evidence.
The terminal takes half the browser width, with a minimum of 720px and never wider than the window. Full-bleed lines on a wide monitor are hard to read, so it does not stretch. Its height is the whole dashboard workspace above the mission bar: what used to be a separate Maximize state is simply the size a terminal opens at, so most of the terminal is log.
Window states and the taskbar
A mission terminal is a window, and the mission bar at the foot of the dashboard is its taskbar. The two are the same persistent object in two presentations, and moving between them is animated so you can see where a terminal went.
A terminal has three sizes: the taskbar entry, the normal terminal, which fills the dashboard workspace with the navigation and taskbar still visible, and fullscreen. There is no intermediate Maximize: the normal terminal already is that size.
The window controls at the right of the terminal's head row are icons only, each with a tooltip on hover and an accessible label:
- Details opens the full mission record.
- Pop out opens the terminal in its own browser window.
- Minimize puts the terminal straight back into its taskbar entry.
- Fullscreen gives the terminal the whole viewport; the same button leaves it again. Escape also leaves fullscreen; it does not interrupt the mission (a second Escape means what it always meant).
- Close returns to the tabs.
A window operation is never a mission operation. Minimizing, going fullscreen, popping out, and closing a terminal do not stop, interrupt, or finish anything. Ending a mission is its own control, and orgabot cancel <mission-id> is what ends a wedged one.A popped-out terminal is the same live session in another window. The mission keeps its taskbar entry, marked with a small external-window icon, and clicking that entry focuses the window you already have rather than opening a second copy. Closing the popped-out window leaves the mission running.
The taskbar as a status rail
Each taskbar entry names its mission's state in a character as well as a colour, so it reads in a screenshot, in a high-contrast theme, and to a screen reader:
◌ Launching
● Implement auth
! Review plan
● SEO audit
✓ Security review
× Deploy staging! is reserved for a mission waiting on you (an approval, a decision, an answer), which is a stronger claim than a mission merely waiting on the world. Running missions stay visually calm: the only continuous animation on the bar belongs to the transient launching state.
A mission you have just submitted takes a compact ◌ Launching entry immediately, before it has an id, and that entry widens into the mission's own entry when the launch lands. If the launch comes back needing you first, the entry says so and the launcher comes back up with the request in it. It does not steal your keyboard focus, so a keystroke meant for another terminal cannot land in the wrong session.
Motion and animation
The dashboard animates by default, and Orgabot carries its own motion setting under System → AI & Execution → Motion and animation:
- Full motion (the default): terminals animate between the taskbar and their window.
- Reduced motion: transitions still show where a window went, but they are brief and nothing loops.
- No motion: windows change state instantly.
- Match my system setting: follow the browser's
prefers-reduced-motion.
Orgabot does not inherit the browser's prefers-reduced-motion automatically. The operating system's answer applies only when you pick the last option, so a system-wide preference cannot silently override a choice you made here. The setting is stored for the whole install (~/.orgabot/motion.json), not per browser.
Animation carries no information of its own: every state it shows is also written out in text and colour, so turning it off loses nothing. If the stored setting cannot be read, Orgabot holds at reduced motion rather than assuming you asked for everything to move.
When the terminal closes itself
A terminal you were watching run closes when its mission leaves the mission bar: it completed, it was cancelled, or you acknowledged its failure. The pane and its taskbar tab go together, so a finished mission does not leave an orphaned terminal floating in the middle of the screen.
A terminal you opened yourself on a mission that had already finished (from a mission card, a link, or the inbox) stays until you close it. It was never live in the mission bar, so there is nothing for it to leave with, and the full scrollback of a finished mission is still there to read.
When a launch is queued instead of started
Admission control can park a launch rather than start it: the organization's token bandwidth for the window is spent, every concurrent mission slot is busy, or the host itself is short of memory or under load. That is not a failure. Nothing was spawned, nothing was lost, and the throttle queue relaunches the work under the same id once capacity frees up (orgabot throttle queue lists it, orgabot throttle resume-due drains it).
The New Mission terminal says so directly. A queued launch gets an amber "Queued" note naming the project and what ran out, never the red flash a refusal gets, and your instruction is consumed exactly as a launch consumes it, because the queue entry now carries it verbatim.
There is no session to hand the terminal over to, though: the throttle refuses before the mission is composed, so there is no mission yet to stream. So the terminal stays open, and the button at its foot becomes Acknowledge, which closes it. Acknowledging dismisses the note and nothing else: it does not cancel the queued launch, does not relaunch it, and sends no request at all. Start typing the next instruction and the button goes back to Launch.
Steering a running mission
Type into the terminal while a mission is running and the message is never refused. One of two things happens:
- Injected, if the worker's live session is hosted by the process handling your message, so the worker's attention is redirected mid-flight;
- Durably queued, otherwise, and delivered at the mission's next round.
Live steering used to sit behind an ORGABOT_LIVE_STEERING environment flag. That flag is gone: steering is ordinary behavior now. The registry of live worker handles is still in-process by design: a worker's stdin only exists in the process hosting that worker, which is exactly why a message that cannot be injected is queued rather than rejected.
Injection confers no authority. A steered message only redirects the worker's attention. Brokered tools, verification gates, and approvals are untouched: the round still ends in the same commit → verify → review → ship pipeline.
Queued does not mean immediate
A queued steering message fires at a round boundary. If the current round is long, the message sits in the queue until that round completes. Here "completed" means the round finished, not that the mission is over. If you need the redirection to take effect now, interrupt instead of steering.
Interrupts
Escape (or the equivalent UI action) requests an interruption. The UI must not report the mission as interrupted until the runtime acknowledges the request. When escalation is needed, the worktree, session, branch, and recovery evidence are all preserved, so nothing is lost by interrupting.
The interrupt travels to the process that owns the mission
A mission you launch from the dashboard runs as its own detached process, and only that process holds a live handle on the worker. The dashboard never signals a process id it read from a record: process ids get recycled, so signalling one risks killing something unrelated.
So Escape does not signal anything. It publishes an interrupt request, the owning process picks it up within a few seconds, stops its own worker through the handle it legitimately holds, and records what actually happened. While the request is outstanding the terminal says "interrupt requested: awaiting acknowledgement from the process that owns this mission", and it says nothing more than that: the interrupted state itself is only ever read back from what the runtime recorded.
Three things follow, and each is deliberate:
- A request is bound to the round it was aimed at. An interrupt you asked for is about the turn you were looking at, so it is never applied to a later round that happens to start afterwards.
- A request that nothing honours expires rather than staying armed indefinitely, and the record says it expired.
- If the owning process is already gone, the liveness sweep closes the request out as
owner_gone. Nothing claims a worker was stopped when none was.
An interrupted mission lands in awaiting_instruction with its branch, worktree, worker session, and pull request all intact, and your next message continues the same mission.
If a mission is genuinely wedged and does not acknowledge a stop, orgabot cancel <mission-id> is what ends it.
Ending a mission early
Sometimes the answer is not "redirect this", it is "stop this". The terminal's composer row carries an ⊘ End mission button for that, and it is present for the whole of an active session: while the mission is queued, while it is plainly running, and while it is paused waiting on a question or an approval. You do not have to wait for the mission to pause before you can stop it.
The button sits at the far right of the row, deliberately far from Send, because ending is irreversible. Clicking it asks you to confirm first, and cancelling the confirmation leaves the mission completely untouched.
Confirming does all of this as one governed operation:
- a live worker session hosted by the dashboard's own process is terminated;
- every pending input request on the mission is cancelled, so a blocked
orgabot askreturns instead of waiting out its timeout; - every pending approval is denied, never approved. Ending a session is not a back door that authorizes the action a gate was holding;
- the mission record moves to
cancelledthrough the ordinary lifecycle path, so the journal, the analytics outcome, and every dashboard projection see exactly one terminal transition.
Work in progress is not verified, reviewed, or delivered. An ended mission stays ended: a detached worker whose current round is still finishing cannot overwrite the record with completed, and its brokered tools and connector tokens stop at its very next command.
A mission that has already finished (completed, failed, or cancelled) shows no End button, because there is nothing left to end.
Acknowledging a failed mission
A failed mission stays pinned to the taskbar until you acknowledge it, and expanding it from there opens the terminal with an Acknowledge button in the same row as Send follow-up. That button sits at the far right of the row, where End mission sits on an active session: it is the control that closes the panel, so it belongs at the row's trailing edge rather than crowded in beside Send. The two never appear together, because End is offered only while a mission is queued, running, or waiting, and Acknowledge only once it has failed.
Acknowledging files the failure away everywhere at once (see Acknowledging an incident, once). It closes the panel; it does not retry, cancel, or otherwise change the mission.
Edit-only vs operate
The terminal exposes the same choice the CLI does, and it matters:
| Mode | Who runs shell | Use when |
|---|---|---|
| Edit-only (default) | Orgabot | Ordinary code changes. Orgabot owns git and verification. |
| Operate | The worker, through the policy-checked broker | The worker must run commands itself, typically to self-verify. |
An edit-only worker cannot self-verify. If a task genuinely needs shell, choose operate up front rather than spending a round discovering it.
In operate mode the command gate enforces the role's tool and connector grants live, fail-closed, with a denial written to the audit log. Natural language never widens that: a worker cannot talk its way into a tool it was not granted.
Follow-up rounds
orgabot follow-up (and the terminal's equivalent) continues a finished or held mission on its own branch, resuming the recorded worker session. A follow-up round is a full round: it re-verifies and re-reviews. It does not inherit anything you did not pass it, so state your requirements in the message.
By default the round runs in the shell that typed it, and dies with that shell. orgabot follow-up ... --background detaches the round exactly as orgabot mission --background does: it runs under its own process, its pid and exit are observed, and its output is appended to the mission's log (orgabot logs <mission-id>). Use it whenever the typing shell is not one you control for the life of the round, such as an agent session that may be restarted or run out of quota. A mission that is still running is not relaunched by the flag: its follow-up is steered live or queued, which returns at once.
A follow-up round is named after what you asked for in it. The round's pull request title (and therefore the squash-commit subject on the default branch), its commit message, the terminal header, and the mission's card and list entries all state the follow-up instruction, not the instruction that launched the mission. Before this, two rounds of one mission opened two pull requests with the same title, character for character, and a shipped follow-up read as the mission repeating itself.
The mission still keeps its origin, so the thread is not lost:
- the terminal shows a follow-up on line naming the launch instruction, above the log;
- the mission drill-down shows the round's ask under Current round and the launch text in full under Launch instruction;
- the pull request body states the round's ask as its Problem and names the mission it continues.
A follow-up that only asks for the same work again ("retry", "continue", "keep going") renames nothing: it keeps the mission's original objective, so a retry never produces a pull request titled retry.
When the terminal loses its feed
The terminal is fed by one connection (a streamed session feed, or the poll it degrades to). If that connection dies, for example because the orgabot dashboard process was restarted while the tab stayed open, the tab keeps the last snapshot it received and cannot correct it.
The panel says so across the whole view rather than in one line of the log: a banner above the session states that nothing on the panel is being updated and that what you are looking at is the last state this tab received. Everything under it, including the status pill and the outcome block, should be read that way, so a running pill sitting beside a completed outcome is a disconnected tab and not a contradiction. Reload the page to reattach.
The green acceptance under the prompt follows the same rule. It is composed when it is drawn, not frozen when it was sent, so:
- while the feed is live it points you at the log above;
- once the feed is known to be down it still states what the server accepted (including the mode the round resolved to) but stops promising a live log, and tells you to reload to follow the round;
- it disappears once this tab has seen the round it announced start and then finish, instead of staying pinned through later rounds.
Mission outcomes
When a mission ends or is held, its Mission Outcome lands in the log as a structured block that Orgabot owns, rather than the worker's closing prose pasted into a panel. It answers three questions: what happened, what you need to know, and what you need to do.
MISSION COMPLETED
Delivered
• Added OAuth support for iCloud accounts.
• Added refresh-token handling.
• Opened PR #842.
Next steps
1. Test an existing iCloud account after upgrading.
[Review PR ↗] [Merge PR…] ▸ View details- Delivered, Held because, Not completed, Next steps, and Optional appear only when they have content. A straightforward success shows a Delivered list and its actions and nothing else. The lists come from the worker's report, normalized: its headings and bullets are read where it has them, a report with none contributes its first sentences, and the mission's own facts (the error, an unmet acceptance criterion, the operator steps the mission recorded, the pull request) are placed where they belong rather than parsed for. A failed mission's report reads under Not completed; a held mission's reason reads under Held because. Next steps is reserved for what a human does after the mission: the steps the mission recorded and a heading the worker wrote by that name, never the reason it stopped.
- Contextual actions live inside the outcome. Review PR, Merge PR, and the deliverable link belong to this state of the mission, so they sit in the block, not in the footer. The footer is reserved for interacting with the mission itself: the steering prompt, Send, End mission, and Acknowledge.
- View details keeps the raw agent completion report, the error text, the workflow contract, and the branch behind a disclosure. Nothing is deleted; the primary presentation is just no longer the harness's own wording.
- Outcomes scroll with the log. The block is part of the timeline. New activity appends below it and pushes it up like any other history, and its actions go with it rather than staying anchored to the screen.
Outcome lifecycle
An outcome is current, superseded, or resolved.
- Current is the latest actionable statement of the mission. There is only ever one, and while the mission stays settled it is the last thing in the log.
- Superseded is what a current outcome becomes the moment you send a follow-up, or the mission runs again: it stays where its round ended, marked
Superseded by follow-up, dimmed, with its action buttons removed because they may no longer be valid. Your prompt and the new round's lines continue below it, and the round's own outcome eventually lands below those as the new current block. - Resolved is a current outcome whose recommended action was taken: once the pull request is merged the block says
ResolvedandPull request merged., keeps a link to the merged PR, and stays in history.
A held mission whose pull request merges elsewhere
A mission can end up held with its pull request already open: its own review loop ended without merging, a fix round is still pending on the PR, or the journal is waiting on delivery evidence it has not yet recorded. If that same pull request is then merged by a different route (another orgabot review-loop run, a manual merge on GitHub, the Merge button on a different session), nothing about the held mission changes on its own: the PR is gone, but the record still says held, and the terminal keeps offering Review PR / Merge PR… for work that no longer exists.
A background reconciliation pass (driven from the dashboard's recovery timer, never from a page load) periodically re-checks a held mission's own delivery PR, first-hand and repo-scoped, the same evidence rule pending-record reaping uses. When it observes that PR merged, it settles the mission itself, but only when the outstanding hold is one a merge can actually answer: whether the round's own evidence just had not landed on the journal yet, or the review loop's own "ended without merging" hold. A hold about something a merge cannot settle: the delivered change only addressed part of the issue, a workflow gate outside delivery was rejected, a dependency is still unavailable, an approval or input is still outstanding, is left exactly as it was; the PR having merged does not answer any of those questions. The block then reads Resolved: PR #N merged by <route>, the Review PR / Merge PR actions are withdrawn, and the merge is recorded on the mission's journal as an observed fact, never as a verification or review this mission itself ran. A merge that contradicts this mission's own recorded verification failure never completes it either: the two disagree, so the mission stays held and says so.
A pull request that is instead observed closed without merging is not a completion; it is a different, explicit hold, because the work was never delivered.
Merging the pull request
When a session's pull request is open and the mission is not mid-run, the current Mission Outcome block offers Merge PR... beside Review PR. It is a two-step control (the second click confirms, because merging is irreversible), and it runs the same governed merge path as orgabot merge and orgabot ship, never a raw gh pr merge:
- Everything is re-evaluated at execution time on the server: the pull request's live head SHA, the mission's SHA-bound verification evidence, the journaled independent review (reviewer must not be the producer), the CI rollup for that exact head, and delivery authority. Anything unreadable holds; nothing is assumed from the record alone.
- A role-owned mission merges under its own connector identity. When the role's token cannot be minted the merge holds and says why; it never falls back to your ambient
ghcredentials. - Your confirmed click is recorded as a real approval naming the exact pull request and head SHA, and the approval decision is journaled through the same gate model every other approval uses.
- A draft pull request is promoted with
gh pr readyinside the shared merge path, exactly as the CLI does.
The answer is deterministic: merged, already merged, held (with the gate that held it, so you know what to fix before asking again), or failed (with the forge's own error), shown inside the outcome block that offered the control. The durable outcome then flows back into the session like every other lifecycle fact, and a merged PR marks the outcome resolved.
What the terminal is not (yet)
The terminal now persists launch, observation, acknowledged Escape interruption, follow-up, redirection, workflow evidence, governed merge, and completion for missions regardless of their launch surface. It does not yet inject a steer into a live worker hosted by another process: that cross-process bridge is separate, tracked work, and the instruction is durably queued for the next round instead. See Roadmap and current state.