Session Lifecycle
Spettro saves your conversation history so you can pause and resume work, clear context when it gets full, and pick up where you left off — even across TUI restarts.
This page covers the full lifecycle: auto-save, debounced writes, session resume, compaction, and the clear/compact distinction.
Auto-save
Spettro automatically saves the current session to disk as you work:
- After every completed agent run (when the assistant message is appended).
- Debounced during a run — tool-stream updates and progress comments are persisted at most once every 2 seconds to avoid thrashing the disk.
- On interrupt — when you press
Escmid-run, the kept-progress summary is saved immediately. - On
/clear,/compact, and session switch — an unconditional save guarantees nothing is lost at these critical points. - On exit — the final turn inside the debounce window is flushed before the TUI shuts down.
Storage path
Sessions live under ~/.spettro/sessions/. Old sessions can be reclaimed
with /storage clean, which never touches the active session
and always keeps the most recent few per project. Each session is identified by a
project-specific hash combined with a timestamp:
~/.spettro/sessions/
└── <session-id>/
├── metadata.json — project hash, start time, goal state
├── messages.json — chat messages (user, assistant, system)
├── tasks.json — session task graph (todos.json kept as legacy alias)
└── events.jsonl — tool traces, approval decisions, agent spawns
Task graph
Session tasks form a persistent dependency graph, not just a flat list. The
agent manages it with the task-create, task-update, task-get,
task-list and task-delete tools (the flat todo-write tool remains as an
alias writing to the same store):
- Each task has an
id,content,status(pending,in_progress,completed,blocked,cancelled) and optionaldependencies(IDs of tasks that must be completed first). - Dependencies are validated on every change: unknown IDs, self-references and
cycles are rejected, and a task cannot be moved to
in_progressorcompletedwhile any dependency is incomplete. task-listreturns tasks in dependency order with a derivedblocked_byfield, and supports the pseudo-filtersready(pending, all dependencies met) andblocked.- The TUI side panel and
/tasks listrender the graph live during runs; pending tasks gated by incomplete dependencies show as blocked. task-deleteremoves a task by id (or prunes all completed/cancelled tasks withclear_completed); references to deleted tasks are stripped from other tasks' dependencies so the graph stays valid.- The graph is persisted per session, so a
/resumerestores the plan exactly where it was left.
The session directory is created inside the project-local .spettro/ directory
when one exists, falling back to the global ~/.spettro/sessions/.
What is NOT saved
Transient stream blocks (the live "thinking…" and "answering…" messages that update character-by-character during a run) are stripped before saving. Only the final, authoritative assistant message is persisted.
Resume
You can load a previous session with /resume:
/resume
This opens a picker showing saved sessions for the current project:
Choose a session to resume:
› 2025-01-15 14:30 — implementing the auth middleware
2025-01-14 10:15 — reviewing PR #42
2025-01-12 16:00 — setting up CI pipeline
↑/↓to navigate.Enterto load the selected session.Escto cancel.
When a session is loaded:
- The chat transcript is restored exactly as it appeared (user messages, assistant responses, system messages, tool traces, plan cards).
- The structured conversation history (
convHistory) is rebuilt, so the LLM has full context of what was said and done before. - Session events (tool activity, approval decisions, agent spawns) are replayed into the activity feed and side panel.
- Session tasks (todos) are restored.
If the session had an unfinished goal in progress, Spettro remembers its
state (objective, iteration count, no-progress counter, elapsed time) and
offers /goal resume after loading.
Auto-resume on startup
At startup, Spettro does not auto-resume — you always start with a fresh
transcript. Use /resume explicitly to return to a previous session.
Compact (/compact)
When the conversation grows long, the context window fills up. Compaction replaces the entire transcript with a summary, freeing token budget for new work:
/compact
The LLM reads the full conversation and produces a condensed summary. The
summary is injected as a system message prefixed with ── conversation compacted ──, and the old messages are discarded.
You can focus the compaction on a specific topic:
/compact auth middleware
This gives the LLM a hint about what to prioritise in the summary.
Two-stage compaction (reference-based)
Compaction is two-stage. Stage 1 is cheap and lossless-by-reference; stage 2 is the summarizer.
Stage 1 — offload tool results. Every tool result larger than ~500 tokens is already persisted to the session spool at execution time. Before summarizing anything, compaction replaces each such result in the older turns with a short stub that keeps the tool name, an args digest, the size, the ok/error status, and the first/last line:
[offloaded: re-read with tool-output {"id":"spool:7"}] shell-exec args={"command":"go test ./..."} — 48210 chars, 1204 lines, status error, head: "…", tail: "FAIL spettro/internal/agent"The full output stays on disk and the model can re-read it at any time with the
tool-outputtool ({"id":"spool:7","offset":0,"limit":4000}). If offloading alone brings the estimate back under the auto-compact threshold, compaction stops here — no summarizer call, no token spend, nothing lost.Stage 2 — summarize. If the history is still too large (or on an explicit
/compact), the older turns are summarized as before, but the summarizer sees the stubs instead of raw truncations and is instructed to carry thetool-outputIDs into the summary verbatim, so dropped outputs remain re-readable after summarization.
After compaction:
- Token usage and context pressure are reset to zero.
- The structured conversation history is rebuilt from the summary (one cache miss on the next request, then the new prefix caches again).
- Session tasks are kept.
Auto-compact
Auto-compaction runs automatically when the context window exceeds a configured threshold:
/compact auto on # enable
/compact auto off # disable
/compact auto status # check current setting
When enabled, Spettro compacts in two places:
- Between turns (TUI and ACP): after an agent turn, if context occupancy is above the threshold percentage.
- Inside the run loop (all modes, including headless and
/goal): before each model step, the runtime estimates context pressure and, past the threshold, summarizes older turns into a single message while keeping the first turn (the task) and the most recent turns verbatim. A one-line notice ("compacted 42k → 6k tokens …") appears in the transcript. This is what lets long unattended goal runs survive without anyone watching the gauge.
The threshold percentage is configurable in ~/.spettro/config.json
(default 85 % of the model's effective window). The auto_compact_* settings
below apply to both triggers.
Auto-compact uses a failure budget: if the summarizer fails 3 times in a row
(provider errors), auto-compaction pauses instead of burning a failing call
every step; a successful compaction (e.g. manual /compact) resets the
counter. Failures never abort the run — the runtime warns and retries at the
next threshold crossing, and an over-budget request still gets one forced
compaction as a last resort.
Configuration
| Config key | Default | Description |
|---|---|---|
auto_compact_enabled |
true |
Enable auto-compaction. |
auto_compact_threshold_pct |
85 |
Context window % at which auto-compact triggers. |
auto_compact_max_failures |
3 |
Consecutive failures before auto-compact gives up. |
Policy
/compact policy
Shows the current thresholds, failure counter, and warning level:
context window: 100000 tokens
threshold: 85000 tokens (85 %)
currently used: 32000 tokens
status: OK (32%)
auto-compact: on
failures: 0 / 3
The context gauge in the status bar turns yellow at ≥75 % and red at ≥90 %.
Live updates during a run
Both the context gauge and the session cost counters update after every LLM request inside a turn, not only when the agent finishes. Multi-step runs (tool loops, goal iterations) therefore show rising occupancy and cost while the agent is still working, so you can interrupt early if a run is burning more context or budget than expected.
Two counters are kept deliberately separate:
| Counter | What it measures | Status-bar role |
|---|---|---|
Context occupancy (contextTokens) |
Largest single LLM request of the current/most recent run — how full the window is | Drives the N / M ctx gauge and auto-compact |
Session cost (totalTokensUsed) |
Sum of every prompt+completion token across the whole session | Goodbye stats, remote status, /stats |
A multi-step run that re-embeds the same history on every step does not inflate the gauge: only the largest request counts as occupancy, while each step still adds its cost. When the run ends, the final totals only add any remainder that live updates missed (for example a dropped event), so cost is never double-counted.
/stats still shows the full provider-reported breakdown (input, output,
cache read/write, per-model) once you want the detailed accounting.
In ACP mode the same live path emits a usage_update session
notification after every request, and the completed turn's aggregated usage
is returned on the session/prompt response.
Clear (/clear)
/clear
- Saves the current conversation to disk (exactly as
/resumewould find it). - Clears the chat transcript, the structured conversation history, and the token counters.
- Starts a fresh session.
Use /clear when you want to start a new topic without losing the previous
one. The saved session is available via /resume later.
Full lifecycle example
1. Start Spettro → fresh session
2. Work for a while → auto-save runs in background (debounced)
3. Context is getting
tight (yellow gauge) → auto-compact when crossing 85%
4. Continue working → auto-save continues
5. Switch topics → /clear (saves + starts fresh)
6. Next day → /resume, pick yesterday's session
7. Work more → /compact manually to keep context lean
8. Quit → flushSave writes the last turn
Retention
Sessions are never automatically deleted. They accumulate under
~/.spettro/sessions/. You can remove old sessions manually:
rm -rf ~/.spettro/sessions/<session-id>
There is no built-in session manager or retention policy yet.
Background jobs
Spettro tracks detached shell processes started by the agent with run_in_background
(e.g., dev servers, watch builds, long-running scripts). Jobs are process-wide
session state: they outlive individual agent turns and are killed when the session
ends.
Listing jobs
/jobs
or
/jobs list
Prints every tracked job with its ID, status, command, and elapsed time:
background jobs:
- job-1 [running] npx vite --port 5173 (started 5m23s ago)
- job-2 [exited] go run ./cmd/server (started 2m10s ago)
kill with /jobs kill <id> or /jobs kill all
Killing a job
/jobs kill job-1
Kills the job's entire process group. Accepts any job ID shown in the listing.
/jobs kill all
Terminates every running job at once.
Lifecycle
- Jobs are created when the agent calls
bashorshell-execwithrun_in_background: true. - Output is captured in a per-job ring buffer (up to 1 MiB of combined stdout/stderr, oldest bytes dropped when exceeded).
- When the session ends (TUI exit,
/exit), all remaining jobs are killed automatically. - Jobs survive
/clear(which only resets the conversation). Use/jobs kill allto clean up explicitly.
Tool output spooling
Oversized tool results (from file-read, grep, repo-search, shell-exec,
bash, web-fetch) are automatically spooled to disk instead of being
hard-truncated. The model receives a truncated head with a footer containing a
spool:N ID and an offset, and can page through the full result using
job-output {"job_id":"spool:N","offset":Z} or the dedicated tool-output
tool ({"id":"spool:N","offset":Z,"limit":M}).
In addition, every tool result over ~500 tokens — even ones small enough to
stay in context untruncated — is written to the spool at execution time. This
backs reference-based compaction (see Compact): when the
context fills up, oversized results are swapped for [offloaded: …] stubs
pointing at their spool IDs rather than being lost to summarization.
Spool files are tied to the conversation, not to a single run: they survive
run end, and are deleted on /clear and when the process exits (TUI exit,
/exit).
# example: model receives truncated grep output with a footer
[truncated: 12,400 of 13,000 lines omitted; use job-output {"job_id":"spool:2","offset":1800} to read more]
# model pages through the omitted portion
~> job-output {"job_id":"spool:2","offset":1800}
<~ spool=spool:2 size=280000 next_offset=9800 (more available)
# the next chunk of content...
The bash-output tool also accepts job_id and offset fields (in addition
to command), so it can double as a spool reader.
