Sessions
A session is a long-lived, interactive conversation with an agent — it keeps context across many turns and can use tools the whole time, unlike a one-shot rupu run.
What is a session
rupu run is a single shot: you give an agent one
prompt, it works, and it exits. A session is the
conversational counterpart. You start it once against an agent, then keep
sending follow-up prompts; each turn sees the full history of everything
that came before — earlier messages, tool calls, file edits, and results.
Because the context survives between turns, a session is the right surface
for back-and-forth work: ask a question, read the reply, refine, correct
course, ask the agent to also look at the writer side. The same agent
files in .rupu/agents/ that power
rupu run back a session — a session just wraps
one in a durable, multi-turn loop.
Starting & using a session
Everything lives under the rupu session
subcommand. A session id is ses_<ULID>; every command that takes one also
accepts a unique prefix or the session's codename. The full set:
| Command | What it does |
|---|---|
start <agent> [target] [prompt] | Start a persistent agent session and run its first turn. |
send <session-id> [prompt] | Send a follow-up prompt to an existing session, or invoke a skill in the next turn (--skill). |
attach <session-id> | Attach to the current or last run in a session (the live view; --view focused|compact|full). |
list | List sessions (--all, --archived). |
show <session-id> | Show session details (--view, --pager / --no-pager, --no-color). |
usage-timeline <session-id> | Per-turn token series across every run the session recorded. |
stop <session-id> | Stop an active session worker. |
compact <session-id> | Summarize older turns now to reclaim context window (--window). |
archive <session-id> | Archive an inactive session and its owned transcripts. |
restore <session-id> | Restore an archived session and its owned transcripts. |
delete <session-id> | Permanently delete a session and its owned transcripts (--force required; a session whose worker is still running is refused). |
prune | Delete archived sessions older than a cutoff (--older-than 30d, --dry-run). |
start accepts an agent name, an optional
target reference (e.g.
github:owner/repo#42), and an optional opening
prompt. Its flags:
| Flag | Meaning |
|---|---|
--prompt <text> | The opening message via flag, so it is never parsed as a target. Preferred over the positional. |
--mode ask|bypass|readonly | Override the agent's permission mode. |
--no-stream | Don't render tokens as they arrive, for every turn in this session. The request still streams on the wire. |
--into <path> | For repo targets: clone into this directory instead of ./<repo>/. |
--detach | Start the first turn without auto-attaching to the live view. |
--view focused|compact|full | Live renderer mode when auto-attaching. |
--engagement-profile <id> | Engagement profile(s) every turn of the session runs under (repeatable or comma-separated). See Engagement profiles. |
--skill <skill> | A skill every turn of the session is given beside the agent's own (name, global:name or project:name). Repeatable. |
--preload-skill <skill> | A skill whose instructions go into the session's system prompt. Repeatable. |
# start a session against an agent, with an opening question $ rupu session start code-explorer --prompt "where is the run target parsed?" # …rupu prints the new session id, e.g. ses_01J… then continue the thread: $ rupu session send ses_01J… "now also check the writer side" # re-open the live view of a running session, or list what you have $ rupu session attach ses_01J… $ rupu session list
go. Pass
--prompt when your message could be mistaken
for a target reference.
The live view
A session turn runs in a child rupu worker process, so the attached view
(after start or attach) can be left without killing the
active turn. While attached:
- p or Enter submits the next prompt once the session is idle; Esc cancels local prompt composition without sending.
- x cancels the active turn and returns the session to idle.
- q leaves the viewer while keeping the session alive; d or Ctrl-] detaches without stopping the worker.
- ? shows the attach help.
The prompt input also accepts slash commands: /help, /status,
/history, /runs, /transcript,
/cancel, /compact (summarize older turns now; the session must be idle),
/coverage (the session's coverage progress, for an agent with a concerns:
block, after its first turn), /stop, /detach and
/quit (or /exit), plus read-only CLI views routed inline:
/workflow list|show|show-run|runs …, /session list|show …,
/transcript list|show …, /issues list|show …. The routed views work in
the full-screen attach view only. A prompt that starts with /name where name
is an installed skill invokes that skill instead (see Skills in a session).
The attach header shows the session's token totals, Cov <complete>/<total> when the
agent declares concerns:, and a context gauge, ctx 52K/200K 26%:
the last turn's input tokens against the model's input limit (the resolved model limit, else the agent's
contextWindowTokens). It is green well below the compaction point, amber within 15 points of it,
and red at or past it. The compaction point is the model's compact_at_percent (default 80%; an agent
can set compactAtPercent). The gauge is hidden until a turn has completed or when no input limit is known.
The session worker
A session is backed by a persistent worker process that
runs in the background and survives between turns. That's what lets
rupu session send continue the same
conversation with all its context intact rather than starting over — each
prompt is queued for the already-running worker as the next turn. Sends can queue behind a
running turn. The live view is just a window onto that worker: --detach
(or the /detach command) leaves the view while
the worker keeps running, and rupu session attach
re-opens the view on the current or last run.
rupu session stop <id> ends the worker and any running turn and marks the session
stopped; a later send reactivates it. A running session must be stopped before it can be
archived or deleted.
rupu does not change a running session by itself. rupu session send
restarts a worker whose rupu binary is older than its own (judged by the binary's modification time; the worker is kept
when that can't be read). To be sure, and before using --skill on a running session, run
rupu session stop <id> after an upgrade.
Skills in a session
A session fixes its skills when it starts. rupu session start
resolves the agent's skills: and preloadSkills: once, against the
global layer and the session's project layer, and stores the set and the rendered skills section on the session. Every
turn sends that same section, so the system prompt is byte-identical from turn to turn and the prompt cache holds. A
preloaded skill's body is the one it had at start; a session that wants its newer version is a new session. A skill
installed after the session started is not offered, even under *. A skill the model loads
is read at its current version (one edited since the start loads with a skill_changed notice),
and a skill uninstalled mid-session is left out of each turn with a skill_missing notice while
the session keeps running.
rupu session start --skill <name> and --preload-skill <name>
(repeat the flag for several) add skills to the session on top of its agent's; its skill_index
records them with reason session.
rupu session send --skill <name> <id> [prompt] (repeatable) invokes a skill in that
turn. It takes any valid skill installed in the session's global or project layer, granted or not, including one marked
disable-model-invocation. The global: and project:
prefixes work as in a grant. A bare name installed in both layers, a wildcard, an unknown name (with a did-you-mean) and a
name the session holds from the other layer are refused before anything is sent. The prompt is optional when a skill is
given. The skill's body goes ahead of the prompt in the turn's message:
<skill name="pdf" base="/Users/me/.rupu/skills/pdf" version="3f2a9c0d41b7"> ...the skill's instructions... </skill> summarise x.pdf
A prompt that starts with /name does the same as --skill name
(rupu session send <id> "/pdf summarise x.pdf"), after any --skill
entries; rupu session start takes /name in its first prompt the same
way. A token that names no installed skill starts the prompt as text. version is the first 12 hex
digits of the skill's content hash, and the turn's transcript records a skill_load with
via: operator. From then on the skill is part of the session with reason
operator: the model may load it again with skills.load and read its
files. It is never added to the index, so the system prompt does not change.
The skills the model loaded survive turns and compaction: the session keeps its loaded set from turn to turn, and every compaction puts those skills back at their current version. A loaded skill uninstalled for a while is named as missing and stays in the set, so it comes back once it is installed again.
rupu session list shows such a session with status newer rupu, and
rupu session prune leaves it alone. A queued turn that invokes skills is turn request version 2:
a worker that knows --skill fails a turn queued by a newer rupu with a transcript saying to restart
the worker (rupu session stop <id>), rather than run it without what it asked for.
Compacting a session
A long session's stored conversation can be summarized on demand:
rupu session compact ses_01J…
rupu session compact ses_01J… --window 1000000 # size against an explicit input limit
It summarizes the older turns so the history fits under the same threshold a run compacts at, and stores the result as
the session's history. It refuses a session that is running (stop it, or /cancel the turn,
first), does nothing when the history has fewer than four messages, and needs a known input limit: when the model's
limits are unknown and the agent pins no contextWindowTokens, pass
--window. /compact in the attach view queues the same thing for the
worker. Every compaction re-attaches the skills the model had loaded.
Messages
A session has a message space of its own,
<sessions>/<session_id>/messages/, shared by every turn and by the children a turn
dispatches. The session stays joined between turns; rupu session stop and
rupu session archive record its participants as left. As the operator
you can message a running session from the CLI:
# reaches the session's agent before its next model call rupu message send ses_01J… "skip the vendored code" rupu message send ses_01J… --urgent "stop editing tests" # what was said, and who is in the space rupu message list ses_01J… --follow rupu message participants ses_01J…
Without --to a message goes to everyone in the session's space (broadcast);
--to takes a participant, a codename, role:<agent> or
step:<id>. A session that isn't running takes no message. The control plane shows the same
space on the session page's Messages tab and serves it as
GET/POST /api/sessions/:id/messages, with a local SSE stream at
…/messages/stream.
When a turn fails
A turn can fail — the provider refuses, the context window overflows,
the recovery ladder runs out. The session does not lose the turn: a failed session turn keeps its
conversation, so your next send continues it instead of re-asking from scratch.
When nothing is left to try, the run ends with the outcome's title and the hint
no recovery left — send another message to continue, or start a new session on another model.
A session resolves its model limits on its first turn and keeps them, including a limit the provider lowered, for later
turns. Recovery and fallbacks are covered on Reliability.
Codenames and attribution
Each session gets one human codename alongside its ULID — a crew plus
the agent's role, such as amber-lantern/heron. The crew is derived from the session id
and the role from the agent's name, so the same agent keeps the same role across sessions. Names are minted once and
stored on the records, so the name in rupu session list, the transcript and the control plane
is the same on every turn, and any rupu session command accepts it in place of the id.
A run uses the customer of the nearest assigned ancestor of its working directory, and a session's record carries the customer of its directory as of its latest turn. Every turn's transcript records the customer it actually ran under, and the control plane prices each turn at that turn's own attribution rather than the session's latest customer.
Sessions in the control plane
/sessions/:id in the control plane is a
chat view over a persistent session: type a prompt and the turn streams back token by token, with tool calls and
thinking in position. Start a session from an agent's page (POST /api/agents/:name/session,
which takes the prompt, mode, target and the session's skills / preload_skills).
Sending queues the turn for the session's worker, as the CLI does; a send may carry skills to
invoke in that turn, and its prompt may be empty when a skill is given. The composer opens a menu of the installed skills
when a message starts with /; the prompt is sent as typed and the CLI reads the
/ tokens.
| Action | Route | Notes |
|---|---|---|
| Send | POST /api/sessions/:id/send | Body {prompt?, skills?}; 409 when the session is stopped. |
| Stop | POST /api/sessions/:id/stop | Ends the worker and running turn, marks the session stopped; a later send reactivates it. Local host only. |
| Compact | POST /api/sessions/:id/compact | Compacts the stored conversation now, detached; while it runs, send / stop / archive / delete answer 409, and the session detail reports the outcome as last_compaction. Local host only. |
| Archive / restore / delete | POST …/archive, POST …/restore, DELETE /api/sessions/:id | Same as the CLI commands. |
| Messages | GET/POST /api/sessions/:id/messages | The session's message space; the Messages tab on the session page. |
The session list (/sessions) takes the control plane's query line
(status, agent, model,
is:active / is:archived, customer, …).
Sessions on a remote host are driven the same way; a remote rupu must advertise session.skills
before a send or start with skills reaches it. These are the same on-disk sessions, transcripts and worker as the CLI's.
Sessions vs runs vs workflows
| Use | When |
|---|---|
| Session | Interactive, exploratory, back-and-forth work where you want to refine across turns and keep context — debugging, code exploration, iterative changes. |
Run (rupu run) | A single, self-contained task you can describe in one prompt. No conversation needed — describe it, let it work, review the result. |
| Workflow | A repeatable, multi-step pipeline defined in YAML — deterministic ordering, templated inputs, and an action allowlist. Reach for this when the same sequence runs again and again. |