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.

you prompt agent thinks · uses tools reply streamed back next turn — full context kept
One turn of a session, repeated. Context carries forward across every turn.

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:

CommandWhat 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).
listList 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).
pruneDelete 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:

FlagMeaning
--prompt <text>The opening message via flag, so it is never parsed as a target. Preferred over the positional.
--mode ask|bypass|readonlyOverride the agent's permission mode.
--no-streamDon'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>/.
--detachStart the first turn without auto-attaching to the live view.
--view focused|compact|fullLive 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
Tip. If you omit the opening prompt, the first turn defaults to 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:

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.

Upgrading rupu. The worker is a long-lived daemon, so installing a newer 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.

Record versions. A session record that carries skills is written as version 2; one without skills stays version 1. A rupu that knows session skills refuses a record from a newer rupu (“this session was written by a newer rupu; upgrade rupu to continue it”) rather than write it back without the fields it doesn't know; 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.

ActionRouteNotes
SendPOST /api/sessions/:id/sendBody {prompt?, skills?}; 409 when the session is stopped.
StopPOST /api/sessions/:id/stopEnds the worker and running turn, marks the session stopped; a later send reactivates it. Local host only.
CompactPOST /api/sessions/:id/compactCompacts 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 / deletePOST …/archive, POST …/restore, DELETE /api/sessions/:idSame as the CLI commands.
MessagesGET/POST /api/sessions/:id/messagesThe 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

UseWhen
SessionInteractive, 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.
WorkflowA 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.