Skip to content

Stop a snoozed task's container (and respawn it on wake) - #432

Merged
tildesrc merged 2 commits into
mainfrom
panopticon/snooze-stops-container
Sep 24, 2026
Merged

tildesrc merged 2 commits into
mainfrom
panopticon/snooze-stops-container

Conversation

@tildesrc

Copy link
Copy Markdown
Contributor

Snoozing a task muted it on the dashboard while its container kept running, holding CPU, RAM and a model session for as long as the operator ignored it. Snooze now means "stop".

Spawner.pause stops the container of a task this runner claims that is actively snoozed, and releases the claim — which also clears the reported lifecycle phase, so the task composes queued. Waking needs no path of its own: a lapsed (or cleared) deadline leaves an unclaimed, un-snoozed task, which is exactly what spawn_one claims and spawns, through the full visible lifecycle, with the per-task clone and the CLI session history in its config volume intact — so the agent resumes where it left off.

Stopping is unconditional, even mid-turn: /workspace is a host bind mount, so the checkout survives; what's lost is the in-container process, not work on disk.

Task.snoozed_until stays a recorded fact the task service never compares to a clock (the determinism invariant). The arithmetic moves to core/snooze.py — pure, now passed in — and is shared by the two callers that own a clock: the dashboard (mutes the row) and the session service (stops the container), so the row you see muted and the container we stop are decided by the same rule.

Matching gates keep the rest of the pass off a paused task, all against the same in-tick snapshot that still shows it claimed and live:

  • spawn_one skips a snoozed task — so it neither comes up in the first place nor gets re-spawned right after pause released it;
  • reconcile skips it — otherwise it would read exit_reason off the container we just killed and report failed: container exited (exit 137);
  • _is_orphan (so heal and mark_healing) skips it — claimed-with-no-session is the orphan shape, but a deliberate stop is not an orphan;
  • spawnable_tasks mirrors the filter so the candidate list and the spawn can't disagree.

Shell tasks are never paused: their script runs once, so stopping it would be a cancel, not a pause (the same reason heal skips them).

No schema change (snoozed_until already exists), no migration, and no REST/MCP surface change — pausing is entirely runner-side, over the existing release endpoint.

Plan: the task's plan.md artifact.

Known gap, deliberately left: pressing R on a snoozed task releases its claim, and the snooze gate then holds it at queued — R does not force a wake. Auto-clearing the snooze on R is a separate call.

🤖 Generated with Claude Code

Snoozing a task muted it on the dashboard while its container kept running,
holding CPU, RAM and a model session for as long as the operator ignored it.

`Spawner.pause` now stops the container of a task this runner claims that is
actively snoozed, and releases the claim — which also clears the reported
lifecycle phase, so the task composes `queued`. Waking needs no path of its
own: a lapsed (or cleared) deadline leaves an unclaimed, un-snoozed task,
exactly what `spawn_one` claims and spawns, with the per-task clone and the
CLI session history in its config volume intact.

`snoozed_until` stays a recorded fact the task service never compares to a
clock; the arithmetic moves to `core/snooze.py` (`now` passed in) and is
shared by the two callers that own a clock — the dashboard, which mutes the
row, and the session service, which stops the container. Matching gates keep
`spawn_one`, `reconcile` and `heal`/`mark_healing` off a paused task, so
nothing in the same pass undoes the stop or reports the deliberate kill as a
failure. Shell tasks are never paused: their script runs once, so stopping it
would be a cancel.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Comment thread src/panopticon/terminal/dashboard.py Outdated
"e",
"snooze",
"Snooze",
"Snooze the highlighted task for 12 hours (stops its container)",

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We don't need to specify this in the dashboard.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Dropped — the e/E legend strings (and the matching rows in docs/dashboard.md) are back to their original wording. The action_snooze docstrings still describe the stop/respawn behavior; say the word if you'd rather those stayed out of the dashboard module too.

Review: the dashboard's help text doesn't need to spell out the container
side-effect. Reverts the `e`/`E` legend strings (and the matching rows in the
keybinding reference) to their original wording.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
@tildesrc
tildesrc marked this pull request as ready for review September 24, 2026 20:34
@tildesrc
tildesrc merged commit cc2a9eb into main Sep 24, 2026
3 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant