Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
18 changes: 18 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,24 @@ new version heading in the same commit.

## [Unreleased]

## [0.446.0] - 2026-09-17
### Added
- **Pause a session.** A new session status, `paused`, that is neither live nor finished: pausing kills the
agent's claude — giving the box back its ~500 MB, its runtime-account slot and its concurrency-cap slot —
while leaving the conversation on disk, so Resume relaunches on the SAME transcript with the full context
back (`claude --resume`). While paused the session is **readable and nothing else**: its conversation
timeline scrolls as usual, and every path that could hand the agent a process back refuses it by name —
a chat-thread reply (Slack/Discord/ClickUp/Telegram and DM continuity), a console chat message, a Quick
Shortcut inject, Take over, Reload, the browser terminal's WebSocket, and the wake-up resume lane, where
a finishing delegate's news stays **queued** for the resume instead of being dropped. A pause is
deliberately not an ending — no episode, no completion card, and no roll-up scores it: the outcome
verdict skips it, the maturity score counts it as in-flight rather than as a stopped run, and the
stale-session tidy won't archive a conversation someone means to come back to. Stop stays available on a
paused run, for "I'm not coming back". Refused where it would be a one-way trip (a run with no resumable
conversation is told to stop instead). Pinned by `scripts/session-pause-test.cjs`.
**For users:** Pause a session from the terminal's **Operations** menu to stop an agent without losing
its work — the conversation stays readable, and Resume picks it up exactly where it left off with its
full context. [Open Sessions](#/sessions)
## [0.445.1] - 2026-09-17
### Fixed
- **Agent operating notes brought back in line with the tools agents actually have.** The OS-owned
Expand Down
48 changes: 45 additions & 3 deletions docs/session-lifecycle-hooks.md
Original file line number Diff line number Diff line change
Expand Up @@ -57,7 +57,8 @@ spins on (`Session.working`), because a warm pane outlives the turn it answered,

Set by: `UserPromptSubmit`, a server-side delivery (`chatSend` / inject), a resident spawn.
Cleared by: `clearTurnBusy`, called from `markTurnIdle` (Stop), `StopFailure`, `SessionEnd`, and every
terminal status transition (`done` / `stopped` / `crashed`).
terminal status transition (`done` / `stopped` / `crashed`), plus `paused` (see below), which is not
terminal but does mean no turn is in flight.

**It used to be a one-way latch.** The clear lived inside `markTurnIdle`'s `resident` branch only, so a
member's own interactive session returned before reaching it and never cleared — the console drew a
Expand All @@ -68,8 +69,8 @@ spinner on finished sessions forever. Live northwind carried the flag on 72 of 5
that no missed signal can strand a spinner:

1. `busy_since` is set;
2. the row is not `stopped`/`crashed` (`done` still counts — an agent that calls `report` flips its row
mid-turn and keeps working);
2. the row is not `stopped`/`crashed`/`paused` (`done` still counts — an agent that calls `report` flips
its row mid-turn and keeps working);
3. the runtime is still there (a pane that died mid-turn leaves the flag set);
4. no turn-END was recorded after the turn started (`last_activity > busy_since`) — this is what heals
rows latched by an older build, with no migration needed;
Expand All @@ -78,6 +79,47 @@ that no missed signal can strand a spinner:
A one-time migration in `src/state/db.ts` also NULLs the already-latched rows (terminal ones, plus any
turn older than the 2h ceiling); a genuinely in-flight turn is untouched, so it is safe on a live box.

## `paused` — the status that is neither live nor terminal

`TerminalManager.pauseSession` kills a session's claude and stamps the row `paused`. Killing the process
is the point: it is what frees the ~500 MB of context, the runtime-account slot and the concurrency-cap
slot (every counter reads `status = 'running'`). The conversation is untouched — claude's transcript is a
file on disk — so `resumeSession` relaunches with `claude --resume <pinned id>` and the full context is
back. Pausing is refused unless there is a live pane AND a pinned, resumable conversation; without the
latter a "pause" would be a one-way stop wearing the wrong word.

It is a STATUS rather than a flag over `stopped` because a paused run has not finished, and three
roll-ups would otherwise score it as one: `outcome.ts` (which would read it `incomplete` /
`stopped-midway`), `agent-stats.ts` (where it would count against the maturity tier that decides whether
an agent may edit a teammate unattended), and the 14-day stale-session tidy (which would archive a
conversation somebody meant to come back to). All three exclude it.

**The invariant: nothing may hand a paused agent a process back except `resumeSession`.** Two layers
enforce it, and both are needed:

- `reachable()` refuses a `paused` row, which covers every path that types into a live pane — inject,
`deliverToResident`, a pasted file, the wake-up inject lanes.
- Every path that RESURRECTS checks `status === 'paused'` itself, because those read a false from
`reachable()` as "the pane is gone, relaunch it" — the exact opposite of a pause. Those are
`reviveResident` (the cold path behind Slack/Discord/ClickUp/Telegram thread continuity and DM
continuity), `chatSend`, `takeoverRun`, `takeoverToTerminal`, `reloadSession`, and the wake-up **resume
lane** in `wakeups.ts`, where a finishing delegate would otherwise start a fresh claude on the paused
transcript. That last one keeps the wake-up **pending** rather than dropping it, so the news is
delivered by the resume.

Two more edges outside the manager: `sharedTerminalAuthz` refuses the ttyd WebSocket for a paused session
(a tab left open or a pasted terminal URL never passes through `/api/sessions/:id/attach`), and the
generic `/resume` route — which only lifts the stay-stopped sentinel — refuses a paused row, since
letting the terminal attach would resurrect the agent by the back door and leave the status lying.
`blockResume` drops the same sentinel a stop does, so ttyd's silent auto-reconnect can't revive it either.

`stopSession` deliberately still works on a paused row: pausing is not a decision to end the run, and
Stop is how you say "I'm not coming back" — it writes the episode and clears the paused stamp.

Pinned by `scripts/session-pause-test.cjs` (the server half) and `scripts/session-revive-gates-test.cjs`
(the console half — a paused session offers neither attach-Resume nor Take over, and always renders
read-only).

## Testing

`scripts/turn-lifecycle-test.cjs` (in `npm run test:governance`) pins the whole machine against a stubbed
Expand Down
4 changes: 2 additions & 2 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

4 changes: 2 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "agent-os",
"version": "0.445.1",
"version": "0.446.0",
"description": "A generic, governed operating system for running autonomous agents safely across brands. Ships with a local web console.",
"license": "MIT",
"type": "commonjs",
Expand All @@ -27,7 +27,7 @@
"check-deps": "bash scripts/install-deps.sh --check",
"dev": "ts-node src/cli.ts serve",
"demo:dev": "ts-node src/demo.ts",
"test:governance": "node scripts/version-sync-test.cjs && node scripts/context-injection-test.cjs && node scripts/governance-conformance.cjs && node scripts/tier-a-policy-test.cjs && node scripts/policy-baseline-test.cjs && node scripts/heredoc-intent-test.cjs && node scripts/capability-registry-test.cjs && node scripts/composio-envelope-test.cjs && node scripts/composio-identity-test.cjs && node scripts/idle-reaper-test.cjs && node scripts/dm-continuity-test.cjs && node scripts/telegram-dm-lane-test.cjs && node scripts/cli-link-origin-test.cjs && node scripts/alert-staleness-test.cjs && node scripts/run-as-identity-test.cjs && node scripts/email-identity-guard-test.cjs && node scripts/deps-freshness-test.cjs && node scripts/runtime-account-test.cjs && node scripts/runtime-account-misattribution-test.cjs && node scripts/runtime-usage-refresh-test.cjs && node scripts/keychain-credential-test.cjs && node scripts/credential-preflight-test.cjs && node scripts/runtime-login-test.cjs && node scripts/rotate-on-reload-test.cjs && node scripts/headless-resumable-test.cjs && node scripts/session-revive-gates-test.cjs && node scripts/claude-config-seed-test.cjs && node scripts/claude-config-isolation-test.cjs && node scripts/output-style-test.cjs && node scripts/session-cost-test.cjs && node scripts/chain-model-test.cjs && node scripts/task-workers-test.cjs && node scripts/tuning-patch-test.cjs && node scripts/task-runs-test.cjs && node scripts/task-pr-links-test.cjs && node scripts/task-draft-delete-test.cjs && node scripts/task-discussion-delivery-test.cjs && node scripts/task-resume-test.cjs && node scripts/task-unblock-test.cjs && node scripts/audience-session-access-test.cjs && node scripts/warm-chat-test.cjs && node scripts/poke-warm-caller-test.cjs && node scripts/wakeup-queue-test.cjs && node scripts/stranded-human-stop-test.cjs && node scripts/inject-submit-test.cjs && node scripts/blocked-routing-test.cjs && node scripts/self-dispatch-guard-test.cjs && node scripts/task-proposals-test.cjs && node scripts/npm-boundary-test.cjs && node scripts/agent-edit-guard-test.cjs && node scripts/per-agent-context-test.cjs && node scripts/goal-update-guard-test.cjs && node scripts/insights-signal-test.cjs && node scripts/outcome-derivation-test.cjs && node scripts/episode-quality-test.cjs && node scripts/memory-upkeep-test.cjs && node scripts/automem-health-test.cjs && node scripts/memory-store-switch-test.cjs && node scripts/memory-preload-test.cjs && node scripts/turn-lifecycle-test.cjs && node scripts/resume-seed-test.cjs && node scripts/outcome-vocabulary-test.cjs && node scripts/skill-presets-test.cjs && node scripts/skill-edit-proposal-test.cjs && node scripts/notify-hook-route-test.cjs && node scripts/review-notify-test.cjs && node scripts/turn-idle-background-guard-test.cjs && node scripts/waiting-brief-test.cjs && node scripts/runtime-death-alert-test.cjs && node scripts/github-per-member-test.cjs && node scripts/github-multi-org-test.cjs && node scripts/card-measurement-test.cjs && node scripts/scheduler-admission-test.cjs && node scripts/tick-liveness-test.cjs && node scripts/audit-mirror-test.cjs && node scripts/request-metrics-test.cjs && node scripts/tool-usage-test.cjs && node scripts/sessions-list-perf-test.cjs && node scripts/summarizer-degradation-test.cjs && node scripts/agent-history-scope-test.cjs && node scripts/webhook-ingress-test.cjs && node scripts/slack-content-filter-test.cjs && node scripts/slack-ingress-test.cjs && node scripts/discord-ingress-test.cjs && node scripts/chat-attachments-test.cjs && node scripts/clickup-task-bridge-test.cjs && node scripts/agentric-commands-test.cjs && node scripts/whats-new-test.cjs && node scripts/opencode-gate-test.cjs && node scripts/protected-path-guard-test.cjs && node scripts/attach-grace-test.cjs && node scripts/attach-file-liveness-test.cjs && node scripts/feed-smoke.cjs && node scripts/activity-classify-test.cjs && node scripts/goal-room-test.cjs && node scripts/secret-rotation-test.cjs && node scripts/update-watch-test.cjs && node scripts/runtime-update-watch-test.cjs && node scripts/setup-wizard-test.cjs && node scripts/md-pdf-test.cjs && node scripts/proposal-surfacing-test.cjs && node scripts/process-janitor-test.cjs && node scripts/detached-work-steer-test.cjs && node scripts/statusline-install-test.cjs && node scripts/docs-create-agent-test.cjs && node scripts/agent-stats-rollup-test.cjs && node scripts/task-discussion-rollup-test.cjs && node scripts/session-insights-stamp-test.cjs && node scripts/loop-stall-attribution-test.cjs && node scripts/session-progress-test.cjs && node scripts/goal-metric-review-test.cjs && node scripts/capability-gap-test.cjs && node scripts/workflow-proposal-test.cjs && node scripts/automation-edit-proposal-test.cjs",
"test:governance": "node scripts/version-sync-test.cjs && node scripts/governance-conformance.cjs && node scripts/tier-a-policy-test.cjs && node scripts/policy-baseline-test.cjs && node scripts/heredoc-intent-test.cjs && node scripts/capability-registry-test.cjs && node scripts/composio-envelope-test.cjs && node scripts/composio-identity-test.cjs && node scripts/idle-reaper-test.cjs && node scripts/dm-continuity-test.cjs && node scripts/telegram-dm-lane-test.cjs && node scripts/cli-link-origin-test.cjs && node scripts/alert-staleness-test.cjs && node scripts/run-as-identity-test.cjs && node scripts/email-identity-guard-test.cjs && node scripts/deps-freshness-test.cjs && node scripts/runtime-account-test.cjs && node scripts/runtime-account-misattribution-test.cjs && node scripts/runtime-usage-refresh-test.cjs && node scripts/keychain-credential-test.cjs && node scripts/credential-preflight-test.cjs && node scripts/runtime-login-test.cjs && node scripts/rotate-on-reload-test.cjs && node scripts/headless-resumable-test.cjs && node scripts/session-revive-gates-test.cjs && node scripts/session-pause-test.cjs && node scripts/claude-config-seed-test.cjs && node scripts/claude-config-isolation-test.cjs && node scripts/output-style-test.cjs && node scripts/session-cost-test.cjs && node scripts/chain-model-test.cjs && node scripts/task-workers-test.cjs && node scripts/tuning-patch-test.cjs && node scripts/task-runs-test.cjs && node scripts/task-pr-links-test.cjs && node scripts/task-draft-delete-test.cjs && node scripts/task-discussion-delivery-test.cjs && node scripts/task-resume-test.cjs && node scripts/task-unblock-test.cjs && node scripts/audience-session-access-test.cjs && node scripts/warm-chat-test.cjs && node scripts/poke-warm-caller-test.cjs && node scripts/wakeup-queue-test.cjs && node scripts/stranded-human-stop-test.cjs && node scripts/inject-submit-test.cjs && node scripts/blocked-routing-test.cjs && node scripts/self-dispatch-guard-test.cjs && node scripts/task-proposals-test.cjs && node scripts/npm-boundary-test.cjs && node scripts/agent-edit-guard-test.cjs && node scripts/per-agent-context-test.cjs && node scripts/goal-update-guard-test.cjs && node scripts/insights-signal-test.cjs && node scripts/outcome-derivation-test.cjs && node scripts/episode-quality-test.cjs && node scripts/memory-upkeep-test.cjs && node scripts/automem-health-test.cjs && node scripts/memory-store-switch-test.cjs && node scripts/memory-preload-test.cjs && node scripts/turn-lifecycle-test.cjs && node scripts/resume-seed-test.cjs && node scripts/outcome-vocabulary-test.cjs && node scripts/skill-presets-test.cjs && node scripts/skill-edit-proposal-test.cjs && node scripts/notify-hook-route-test.cjs && node scripts/review-notify-test.cjs && node scripts/turn-idle-background-guard-test.cjs && node scripts/waiting-brief-test.cjs && node scripts/runtime-death-alert-test.cjs && node scripts/github-per-member-test.cjs && node scripts/github-multi-org-test.cjs && node scripts/card-measurement-test.cjs && node scripts/scheduler-admission-test.cjs && node scripts/tick-liveness-test.cjs && node scripts/audit-mirror-test.cjs && node scripts/request-metrics-test.cjs && node scripts/tool-usage-test.cjs && node scripts/sessions-list-perf-test.cjs && node scripts/summarizer-degradation-test.cjs && node scripts/agent-history-scope-test.cjs && node scripts/webhook-ingress-test.cjs && node scripts/slack-content-filter-test.cjs && node scripts/slack-ingress-test.cjs && node scripts/discord-ingress-test.cjs && node scripts/chat-attachments-test.cjs && node scripts/clickup-task-bridge-test.cjs && node scripts/agentric-commands-test.cjs && node scripts/whats-new-test.cjs && node scripts/opencode-gate-test.cjs && node scripts/protected-path-guard-test.cjs && node scripts/attach-grace-test.cjs && node scripts/attach-file-liveness-test.cjs && node scripts/feed-smoke.cjs && node scripts/activity-classify-test.cjs && node scripts/goal-room-test.cjs && node scripts/secret-rotation-test.cjs && node scripts/update-watch-test.cjs && node scripts/runtime-update-watch-test.cjs && node scripts/setup-wizard-test.cjs && node scripts/md-pdf-test.cjs && node scripts/proposal-surfacing-test.cjs && node scripts/process-janitor-test.cjs && node scripts/detached-work-steer-test.cjs && node scripts/statusline-install-test.cjs && node scripts/docs-create-agent-test.cjs && node scripts/agent-stats-rollup-test.cjs && node scripts/task-discussion-rollup-test.cjs && node scripts/session-insights-stamp-test.cjs && node scripts/loop-stall-attribution-test.cjs && node scripts/session-progress-test.cjs && node scripts/goal-metric-review-test.cjs && node scripts/capability-gap-test.cjs && node scripts/workflow-proposal-test.cjs && node scripts/automation-edit-proposal-test.cjs",
"test:alert-staleness": "node scripts/alert-staleness-test.cjs",
"test:deps": "node scripts/deps-freshness-test.cjs && node scripts/runtime-account-test.cjs && node scripts/runtime-account-misattribution-test.cjs && node scripts/runtime-login-test.cjs && node scripts/claude-config-seed-test.cjs && node scripts/claude-config-isolation-test.cjs",
"test:dm-continuity": "node scripts/dm-continuity-test.cjs",
Expand Down
Loading
Loading