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 Esc mid-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 optional dependencies (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_progress or completed while any dependency is incomplete.
  • task-list returns tasks in dependency order with a derived blocked_by field, and supports the pseudo-filters ready (pending, all dependencies met) and blocked.
  • The TUI side panel and /tasks list render the graph live during runs; pending tasks gated by incomplete dependencies show as blocked.
  • task-delete removes a task by id (or prunes all completed/cancelled tasks with clear_completed); references to deleted tasks are stripped from other tasks' dependencies so the graph stays valid.
  • The graph is persisted per session, so a /resume restores 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.
  • Enter to load the selected session.
  • Esc to cancel.

When a session is loaded:

  1. The chat transcript is restored exactly as it appeared (user messages, assistant responses, system messages, tool traces, plan cards).
  2. The structured conversation history (convHistory) is rebuilt, so the LLM has full context of what was said and done before.
  3. Session events (tool activity, approval decisions, agent spawns) are replayed into the activity feed and side panel.
  4. 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-output tool ({"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 the tool-output IDs 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 /resume would 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 bash or shell-exec with run_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 all to 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.