Agents

An agent is a single AI worker — one Markdown file whose YAML frontmatter configures it and whose body becomes its system prompt.

What is an agent

An agent is the smallest unit of work in rupu: a narrow, single-purpose AI worker. Each agent lives in one Markdown file. The file has two parts — a block of YAML frontmatter that tells rupu how to run the agent (which provider, which model, which tools, what it is allowed to do), and a Markdown body that is sent verbatim to the model as its system prompt.

Good agents are narrow, explicit, and testable. A well-scoped agent answers six questions clearly: what job it owns, what tools it may use, what sequence it should follow, what counts as done, what it must not do, and what shape the final answer should take. When several specialists, review boundaries, or fix loops are involved, reach for a workflow instead of one giant prompt.

YAML frontmatter provider · model · tools · permissions system prompt the Markdown body an agent rupu run <name>
Config plus a system prompt is everything an agent is.

The agent file

An agent is a Markdown file with YAML frontmatter. The frontmatter (between the opening and closing --- lines) tells rupu how to run the agent; everything after the closing --- is the system prompt. rupu parses the frontmatter with strict unknown-field rejection — a misspelled key fails fast rather than being silently ignored. A rupu release older than a field also refuses an agent that sets it, so upgrade remote hosts before adding new keys to agents they run.

Agent files resolve from two locations:

Project-local agents shadow global agents by name:. Shadowing is all-or-nothing — rupu does not merge frontmatter or prompts. rupu agent list shows whether each agent comes from project or global scope, and rupu agent validate <name> / --all checks that an agent file parses, names only tools rupu knows and only skills that are installed.

Frontmatter fields

KeyTypeDefaultMeaning
namestringrequiredAgent identifier used by rupu run <name> and by workflows. Lowercase, hyphen-separated.
descriptionstringnoneHuman-readable summary shown by rupu agent list.
providerstringanthropicWhich LLM provider (account) to use. Canonical names: anthropic, openai, gemini, copilot.
authapi-key | ssoresolver chooses (SSO preferred when both exist)Optional auth-mode hint when a provider has both credentials.
modelstringconfig default_model, else claude-sonnet-4-6Provider-specific model id. Not validated at parse time; an invalid id fails at runtime.
toolsarray<string>the default grant (below)The agent's tool grant: what its model may call. See Tools & permissions.
maxTurnsinteger50Hard cap on model turns (not a token budget).
permissionModeask | bypass | readonlyaskPermission model, decided per tool call from the tool's effect. CLI --mode overrides it.
dispatchableAgentsarray<string>none (no dispatch)Allowlist of agent names this agent may start with dispatch (kinds subagent and agent, and the legacy dispatch_agent / dispatch_agents_parallel).
dispatchableWorkflowsarray<string>noneAllowlist of workflow names this agent may start with dispatch {kind: workflow} (and the legacy run_workflow).
messagingbooltruefalse turns the messaging tools (board.*, msg.send) off for this agent wherever it runs. See Messaging.
skillsarray<string>noneSkills the model may load with skills.load. See Skills.
preloadSkillsarray<string>noneSkills whose whole body goes into the system prompt; implies granted.
effortstringprovider defaultCross-provider reasoning level: auto, minimal, low, medium, high, max.
thinkingDisplaysummarized | omitted | updatessummarizedReasoning-display hint; honored by Anthropic adaptive models only.
contextWindowstringmodel defaultCross-provider context tier: default or 1m.
outputFormattext | jsonfree-form textHint for structured output; describe the JSON shape in the prompt too.
outputSchemaobject (inline YAML)noneJSON Schema for Anthropic structured outputs. Only takes effect together with outputFormat: json.
concernsobjectnoneCoverage-concerns block; injects the coverage tools and the catalog into the system prompt.
findingsProfilefull | summaryfullFindings contract for findings.report. A workflow step's findings_profile or the workflow's defaults.findings_profile overrides it.
fallbacksarray of {model, provider?}[recovery].fallbacks from config, else noneOrdered fallback models tried when a reply cannot be used. See Fallbacks.
maxTokensintegerdiscovered output capPer-request output-token cap.
contextWindowTokensintegerdiscovered input limitInput-token limit used for proactive compaction.
compactAtPercentinteger80Percentage of the input limit at which compaction triggers; clamped to 10–95.
anthropicOauthPrefixboolprovider defaultAnthropic SSO only — enables/disables the OAuth system prefix.
anthropicPromptCachebool[providers.<name>] prompt_cache, else onAnthropic only; false disables prompt caching for this agent.
anthropicTaskBudgetintegernoneAnthropic-only soft output budget, separate from maxTurns.
anthropicContextManagementstringnoneAnthropic-only server-side pruning of older tool blocks (tool_clearing).
anthropicSpeedstringnoneAnthropic-only account-gated fast mode (fast).
Note. Keep agents portable by avoiding the anthropic* fields unless you specifically need them. For checked-in repo agents, set provider, model, tools, and permissionMode explicitly so behavior does not drift with a mutable global default.

Advanced frontmatter

The fields above cover most agents. The ones below tune reasoning, token limits, findings, failure recovery, skills, messaging and provider-specific behavior. All are optional; leave them out and rupu uses the discovered or configured default.

Provider accounts

provider: names an account, not only a vendor. A bare vendor name (anthropic, openai, …) is its own account; to keep two identities for one vendor, log in to a named account with --kind and point the agent at it, e.g. provider: anthropic-work. auth: (api-key or sso) then picks between that account’s credentials when it holds both. See Providers → Accounts.

Reasoning, context & token limits

KeyTypeDefaultMeaning
effortstringprovider defaultOne cross-provider reasoning level: auto, minimal, low, medium, high, max; aliases adaptive (= auto) and xhigh (= max). rupu translates it per provider — Anthropic adaptive models get thinking.type: adaptive plus output_config.effort, pre-4.6 Anthropic models thinking.budget_tokens, Gemini thinkingLevel / thinkingBudget, OpenAI Codex reasoning.effort, OpenAI-compatible endpoints reasoning_effort forwarded verbatim.
thinkingDisplaystringsummarizedWhat reasoning the model returns alongside its answer (the thinking.display knob). summarized returns a readable summary captured into the transcript; omitted still runs (and bills) thinking but returns no text; updates returns between-tool-call progress notes and needs a capable model, else degrades to summarized. Honored only by Anthropic adaptive models (4.6+ and the Claude-5 family); elsewhere the run degrades to the model's default display with a one-time warning rather than failing.
contextWindowstringdefaultdefault or 1m (aliases 1M, one_million). On Anthropic, 1m opts an API-key account into the 1M-token context beta; an SSO account uses a [1m] suffix on model: instead. Neither changes the input limit rupu discovers.
maxTokensintegerdiscovered output capPer-request output-token cap. Overrides the cap discovered from the provider’s model list; when neither is known, Anthropic gets 8192 and other providers get no cap. 0 is ignored. Extended thinking draws from this budget.
contextWindowTokensintegerdiscovered input limitInput-token limit used for proactive context compaction. Overrides the discovered limit; compaction is off only when neither is known. 0 is ignored.
compactAtPercentinteger80Share of the input limit at which older turns are summarized before the next turn. Clamped to 10–95. When the output cap is known and shares the input window, compaction also triggers early enough that a full-length reply still fits.

Limits come, in order, from the agent's pins, a [[providers.<name>.models]] entry in config.toml, the provider's live model list (cached for 1 hour in ~/.rupu/cache/models/), else unknown. Every run writes a model_limits notice to its transcript saying what it will use and where each value came from. Pin maxTokens / contextWindowTokens only to override what rupu discovers — see Reliability → Model limits discovery.

Coverage & findings

KeyTypeDefaultMeaning
concernslistnoneCoverage-concerns block (include: a catalog, or inline concerns). Injects the coverage tools and prepends the concern catalog to the system prompt. A workflow step’s own concerns: wins.
findingsProfilefull | summaryfullContract for findings.report (alias report_finding), whether the tool comes from a concerns: block or an explicit tools: grant. full requires a complete structured report and rejects summary / severity / evidence as separate arguments; summary records the lightweight summary / severity / evidence form and refuses a report. Precedence: workflow step → workflow defaults → agent → full; rupu run --findings-profile overrides it for a standalone run. Children started with dispatch resolve only from their own agent file.

See Coverage and Findings.

Fallbacks

fallbacks: is an ordered list of models to try when the agent’s own model can’t give a usable reply or the provider fails the request (a refusal, repeated truncation, an overloaded provider). Each entry has a model and an optional provider; without one, the entry means the provider the run started on. Unknown keys inside an entry are an error. The list replaces [recovery].fallbacks from config.toml for this agent (it is not merged), and fallbacks: [] means no fallbacks. A cross-provider entry sends the whole conversation to that provider, so list one only when the run's data may go there. See Reliability → Configuring fallbacks.

fallbacks:
  - model: claude-sonnet-5-5        # same provider as the agent
  - provider: openai-codex          # another provider
    model: gpt-5.6-cyber

Skills

skills: and preloadSkills: grant the agent Agent Skills installed under ~/.rupu/skills/ or the project's .rupu/skills/. Installing a skill gives it to no agent: an agent without these keys has no skills, no skill index and no skills.load tool.

skills: [pdf, code-review]     # listed in the prompt; the model loads one when a task matches it
preloadSkills: [house-style]   # its whole body is in the system prompt from the start
EntryGrants
code-reviewthe skill of that name in whichever layer has it. If both the global and the project layer have it, the agent fails to load until the entry names a layer.
global:code-review, project:code-reviewthat layer's skill
*every valid global skill (never a project skill)
project:*every valid project skill

A run binds each name once, so granting the same name from both layers (global:x and project:x, or * and project:* over a name in both) is an error. An unknown name, an invalid skill or a malformed entry fails the agent's load with a did-you-mean, and rupu agent validate reports the same.

A granted skill reaches the run as:

A skill never widens the agent's tools: grant or its permission mode. A child started with dispatch gets its own agent file's skills, never its parent's. A session fixes its agent's skills when it starts (see Sessions → Skills).

Messaging

Every run with a message space is offered board.claim, board.release, board.post, board.read and msg.send by default. messaging: false turns them off for this agent wherever it runs: as a standalone run, a workflow step, a sub-agent, a session's agent or an agentiflow's lead or unit. The run is still in its space, so the operator and its children reach it, and every child it launches has messaging off too. A file that sets messaging: false and also names a messaging tool in tools: (by name, alias or board.* / msg.*) fails to load. See Messaging.

Anthropic-only fields

KeyValuesMeaning
anthropicOauthPrefixtrue | falseSSO (OAuth) only. Enables or disables Anthropic's OAuth system prefix.
anthropicPromptCachetrue | falseExplicit prompt caching, on by default. false drops the cache_control breakpoints (e.g. for a gateway that rejects them). Overrides [providers.<name>] prompt_cache in either direction.
anthropicTaskBudgetpositive integerSoft output-token budget, separate from maxTurns.
anthropicContextManagementtool_clearingServer-side pruning of older tool blocks.
anthropicSpeedfastAccount-gated fast mode.
Use the frontmatter names, not wire names. Keys such as thinking, budget_tokens, thinkingBudget, context_management or speed are what rupu sends to a provider, not frontmatter fields — strict parsing rejects them. Use effort, thinkingDisplay, anthropicContextManagement and anthropicSpeed.

Example

---
name: deep-auditor
description: Long-running audit agent with tuned limits, a skill and a fallback chain.
provider: anthropic-work        # a named account
auth: sso
model: claude-sonnet-4-6
tools: [read_file, grep, glob]
permissionMode: readonly
effort: high
maxTokens: 32000
compactAtPercent: 70
concerns:
  - include: secrets-in-source
findingsProfile: summary
skills: [code-review]
messaging: false
fallbacks:
  - model: claude-sonnet-5-5       # same account
  - provider: openai-codex       # another provider
    model: gpt-5.6-cyber
anthropicContextManagement: tool_clearing
---

Audit the repository for hard-coded secrets and record each one you find.

A complete example

This is the code-reviewer agent that ships as a rupu template — a read-only reviewer that can inspect a local file, a diff, or a pull request and return concise findings.

---
name: code-reviewer
description: Read a file, diff, or PR and return concise textual findings.
provider: anthropic
model: claude-sonnet-4-6
tools: [read_file, grep, glob, scm.prs.get, scm.prs.diff]
maxTurns: 12
permissionMode: readonly
---

You are a code reviewer.

If the prompt contains a PR reference, use the SCM tools to inspect the PR metadata and diff.
If the prompt names local files or includes a diff, review that directly.

Focus on:
- correctness bugs
- missing edge-case handling
- unclear or brittle structure
- missing tests where behavior changed

Return either:
- `no issues`
- or a short bulleted list ordered by severity

Do not edit code.

Note the shape: the frontmatter pins a provider and model, declares the smallest useful tool set, and runs readonly so a reviewer can never mutate the thing it is reviewing. The body gives the agent a role, a process, and an explicit output contract.

Tools & permissions

tools: is the agent's tool grant: what its model may call. Each entry is one of:

EntryMeans
bash, findings.report, scm.prs.get, …that tool, by its canonical name
report_finding, query_findings, …a legacy name, accepted forever as an alias of its canonical tool (see Tool names)
findings.*, scm.*, issues.*, board.*, …every tool in that namespace
core.*the core tools: bash, read_file, write_file, edit_file, grep, glob, ast_grep
*the whole catalog, except the authority tools board.directive and board.retract

There are no other wildcards (scm*, coverage_* are errors). Authority is never implied by *. board.directive and board.retract let one agent direct the others, so * leaves them out: an agent gets them only by naming them (or board.*), and an agentiflow lead gets them from its origin grant.

An unknown name is an error. An agent whose tools: names a tool rupu doesn't know fails to load — rupu run, a workflow step and a sub-agent dispatch all refuse it — with a suggestion when a known name is close. rupu agent validate catches it ahead of time.

A tool is offered only where it works. The connector tools (scm.*, issues.*, github.*, gitlab.*) need configured SCM connectors, coverage.* a concerns: block, assets.mark an engagement profile, the board and mailbox tools a message space, goal.* and budget.status an agentiflow lead, and workflows.generate a provider to author with. dispatch and join work in every agent run, and so do the roster tools (agents.list, agents.get, workflows.list, workflows.get, catalog.search). A wildcard silently leaves out a tool the run can't serve; a tool named exactly that the run can't serve is left out too, and the run says so with a tool_unavailable notice in its transcript.

Omitting tools: gives the default grant: the core tools, the launch pair and every connector tool (core.*, dispatch, join, scm.*, issues.*, github.*, gitlab.*). For reusable agents an explicit tools: is better than the default.

Ambient grants. The run's context adds tools the agent didn't list: a concerns: block adds the coverage tools and findings.report; an active engagement profile adds findings.report and assets.mark; a message space adds the default messaging tools; an agentiflow lead's or unit's origin adds its coordination tools. A run granted dispatch (by any route, a legacy name included) also gets join. None of this is hidden: every run's transcript opens with a tool_grant event listing exactly the tools the model was offered and why, and every call is audited (tool_audit).

Tools vs. actions. tools: is this agent's full grant. A workflow step’s actions: can only narrow the connector subset of that grant for one step — it never touches any other tool, and never grants a tool beyond what tools: already allows. See Step actions.

Tool names

Tools are named namespace.verb (findings.report, coverage.mark); the core fs/shell tools stay unqualified. Earlier names remain accepted forever, in tools: and when a model calls them, as aliases:

CanonicalAlso accepted
findings.reportreport_finding, findings.record
findings.verifyfinding.verify
findings.query / findings.tagquery_findings / tag_findings
assets.markasset_mark
coverage.mark / coverage.status / coverage.remainingcoverage_mark / coverage_status / coverage_remaining
coverage.concerns.search / coverage.concerns.detailcoverage_concerns_search / coverage_concerns_detail
dispatchdispatch_agent, dispatch_agents_parallel, run_workflow (see Legacy launch names)
workflows.generategenerate_workflow
goal.coverage (agentiflow lead)coverage.status, inside a lead only

Structural search with ast_grep

ast_grep matches code by its syntax tree rather than its text, so a pattern finds every call site or definition however it’s formatted. It runs the ast-grep binary, which must be on PATH as ast-grep (brew install ast-grep or cargo install ast-grep); without it every call returns an “ast-grep not found” error. It is a read tool: readonly mode allows it. It takes a pattern, a lang (required: any language ast-grep supports, e.g. rust, python, typescript, tsx, javascript, go, java, c, cpp) and an optional path inside the workspace.

A pattern is ordinary code in the target language, with metavariables where anything may appear:

MetavariableMatches
$NAMEexactly one syntax node, captured as NAME (upper-case). The same name used twice must match identical code.
$$$ / $$$NAMEzero or more nodes, such as an argument list or a block body; the named form captures them.
$_one node without capturing it.
PatternlangFinds
impl $TRAIT for $TYPE { $$$ }rustevery trait implementation
$X.unwrap()rustevery .unwrap() call, whatever it is called on
subprocess.run($$$, shell=True, $$$)pythonshell-invoking subprocess calls
fetch($URL, $$$)typescriptfetch call sites with any options
$EL.innerHTML = $VALUEjavascriptdirect innerHTML assignments

The agent sees one path:line:col: match line per hit (1-based, workspace-relative); no matches is empty output, not an error, while a malformed pattern or a bad path surfaces ast-grep's diagnostic as the error. Under a concerns: block, each matched file is recorded in the coverage ledger like a grep hit. Each call also records up to 200 matches as structured data (file, range, matched text and every metavariable's capture), which the control plane uses to show a source slice and a syntax-tree preview per match.

Permission modes

permissionMode is ask, bypass or readonly. Every tool declares one effect, and the mode decides each call from the effect alone — never from the tool's name, so a new tool is gated correctly the day it lands:

EffectExamples
readread_file, grep, glob, ast_grep, findings.query, coverage.status, scm.prs.get, goal.status, join
recordrupu's own bookkeeping: findings.report, findings.verify, findings.tag, assets.mark, coverage.mark, board.post, msg.send
writechanges the workspace or runs code: bash, write_file, edit_file
externalacts outside this machine: scm.prs.create, scm.branches.create, issues.comment, issues.create, github.workflows_dispatch
spawnstarts another run or model call: dispatch (and its aliases), workflows.generate
Effect ↓ / Mode →bypassask (interactive)ask (no operator)readonly
readallowallowallowallow
recordallowallowallowallow
writeallowpromptallow, with a noticedeny
externalallowpromptallow, with a noticedeny
spawnallow; child keeps its own modeallow; child capped at askallow; child capped at askallow; child capped at readonly

ask is the safest default for agents that edit code. Pass --mode readonly for unattended runs that must not change anything. The CLI --mode flag always overrides the value in the file.

Launching children

An agent can start other runs as tool calls in the middle of its own run, keep working while they run, and fold their results back into its context. This is composition at the agent layer — distinct from a workflow, which composes agents at the step layer. A writer agent can dispatch a reviewer mid-draft, revise against the findings, and carry on, without the workflow author fanning out at the YAML level.

Three tools drive it. The legacy names (dispatch_agent, dispatch_agents_parallel, run_workflow, generate_workflow) are aliases that keep their old behavior and output shapes.

dispatch fieldMeaning
kindsubagent (default), agent or workflow
nameThe agent, or for kind workflow the workflow, to run
fileKind workflow only, instead of name: a file workflows.generate returned in this run
promptAn agent child's task. Required for subagent and agent
inputsA workflow's declared inputs. For an agent child, structured inputs appended to its prompt as a JSON block
waittrue starts the child and waits for it in one call. Default false
timeout_secsWith wait: how long to wait. Default 300, at most 3600
kindRuns asIts record
subagenta task in the parent's process; it dies with the parenttranscript under the parent's run: <RUPU_HOME>/runs/<parent run id>/sub/<sub_id>/transcript.jsonl
agentits own rupu run processits own run and transcript, like any rupu run
workflowits own rupu workflow run --plain processits own workflow run under <RUPU_HOME>/runs/<id>/

join returns one entry per child: handle, kind, name, run_id, codename, status (pending, running, done, failed, abandoned or interrupted) and, once it ended, success, output, error, cause, tokens and duration_ms. While a run has children it has not joined, each turn starts with a status block naming them, so the agent knows what is still out without polling. A workflow child is checked before anything starts: it parses, has no approval gate and no host: / distribute: step, every agent its steps run is one the caller may dispatch, and its inputs resolve.

Rules every kind shares

---
name: writer
tools: [read_file, edit_file, dispatch]    # join is implied by dispatch
dispatchableAgents: [security-reviewer, perf-reviewer]
dispatchableWorkflows: [release-checks]
---

Draft the change, then dispatch the reviewers on your diff.
Revise against their findings before you finish.

When the parent ends

A run's unjoined children are stopped when the run ends and recorded abandoned, and so is whatever they left below them. A workflow step that pauses, or a rupu run / rupu workflow run stopped by SIGTERM or SIGINT, stops them the same way but records each interrupted; when that run's conversation is continued (rupu workflow resume, rupu run --continue), the continued run takes on every child under the same handles — ended ones as they ended, the rest continued from their own records. Every launch is appended to the parent's children ledger, <RUPU_HOME>/runs/<run id>/children.jsonl, which join, the orphan reaper and the control plane's Children tab read. Each child gets a codename below its parent's (<parent>><role>#n), and a sub-agent can be continued by its sub_… id like any run.

Legacy launch names

The tools that predate dispatch keep working, in tools: and when a model calls them:

Old callDoesReturns
dispatch_agent {agent, prompt, inputs?}a subagent, waited for{ok, agent, output, findings, tokens_used, duration_ms, transcript_path, sub_run_id, codename, error?}
dispatch_agents_parallel {agents: [{id, agent, prompt, inputs?}], max_parallel?}one subagent per entry, all checked before any starts, all waited for{ok, results: {<id>: ...}, all_succeeded}
run_workflow {workflow, inputs?}a workflow child, not waited for{handle, participant}
generate_workflow {description, inputs?}workflows.generate, then starts it{handle, participant, generated_file}
join {handle}join on one child{status, success?, output?}, or {status: "failed", error}

A model only sees dispatch, so a prompt written for an old name arrives as a dispatch call in the old shape. See MCP & tools for the tool surface and Workflows for step-layer composition.

Structured outputs

By default an agent returns free-form text. Two frontmatter fields tighten that when a downstream step or tool needs a predictable shape.

outputFormat: json is a hint — it asks the model to return JSON (describe the shape in the prompt too). On its own it is prompt-driven: nothing structurally guarantees the result parses or matches a particular schema.

outputSchema: goes further. Declare a JSON Schema inline in the frontmatter and, when outputFormat: json is also set, rupu hands it to Anthropic's structured-output mode (output_config.format = {type: "json_schema", schema: ...}), which returns a guaranteed schema-conforming object. Without outputFormat: json the schema has no effect; providers other than Anthropic ignore it.

---
name: security-reviewer
outputFormat: json
outputSchema:
  type: object
  properties:
    findings:
      type: array
      items:
        type: object
        properties:
          severity: { type: string, enum: [low, medium, high, critical] }
          title: { type: string }
          body: { type: string }
        required: [severity, title]
        additionalProperties: false
  required: [findings]
  additionalProperties: false
---

Audit the diff and return your findings.

With a guaranteed shape, downstream workflow steps can rely on those fields in their template expressions instead of defensively parsing free text.

Providers & models

rupu supports four LLM providers, each with two authentication modes (API key or SSO):

ProviderAPI keySSONotes
anthropicyesbrowser callbackConsole API key or Claude.ai SSO. The most exercised provider.
openaiyesbrowser callbackPlatform API key or ChatGPT SSO.
geminiyesbrowser callbackAPI key via Google AI Studio; SSO via Vertex / CLI.
copilotyesdevice codeAPI-key path uses a GitHub PAT; requires paid Copilot.

An agent picks its provider with provider: and its model with model:. Model ids are provider-specific and are not validated at parse time — an invalid id fails at runtime when the provider is called. When an agent omits model:, rupu uses the provider's default_model from config, else claude-sonnet-4-6; for stable project behavior, set model: per agent. rupu run --model / --provider override the file for one run.

Inspect the models rupu knows with rupu models list and refresh the cached model lists with rupu models refresh. Authenticate a provider before running an agent against it:

# API key (or pipe the secret on stdin)
rupu auth login --provider anthropic --mode api-key --key <secret>

# SSO
rupu auth login --provider anthropic --mode sso

Generate one from a description

Don’t want to hand-write the frontmatter? Describe the agent in plain language and let rupu draft it for you. create scaffolds the .md, with --describe a model drafts the definition, and the file opens in your editor to refine.

# scaffold + AI-draft an agent from a one-line brief
rupu agent create security-reviewer \
  --describe "A read-only reviewer that audits a diff for OWASP Top 10 issues and returns JSON findings"

# or scaffold an empty agent and edit it yourself (omit --describe)
rupu agent create my-agent

The same generate-from-a-description flow is available in the control plane’s authoring UI.

Running an agent

List and inspect the agents rupu can see, validate them, then run one:

# list every resolvable agent (project + global scope)
rupu agent list

# show one agent's resolved frontmatter and prompt
rupu agent show code-reviewer

# check every agent file in both layers (tools:, skills:, syntax)
rupu agent validate --all

# run an agent with a prompt
rupu run code-reviewer --prompt "Review the staged diff for correctness bugs"

Use --mode to override the file's permissionMode at run time — for example --mode bypass or --mode readonly in a non-interactive context. --skill / --preload-skill add skills to one run on top of the agent's own, and --no-messaging / --messaging-tool switch its messaging tools off or narrow them. Every run is recorded as a JSONL transcript you can replay later:

# inspect a recorded run
rupu transcript

Continuing a run

If a run stops before it finishes — a closed terminal, a killed process, a crash — pick it up where it left off instead of starting over. rupu rebuilds the conversation from the run's transcript, drops the turn that was in flight, and tells the agent it was interrupted. The run's command is restored too: its repo target and every flag it was launched with; a flag given on the continue replaces only its own setting. Run it from the same directory as the original run.

# continue an interrupted run
rupu run code-reviewer --continue <agent_run_id>

# continue any ended run with something new to do
rupu run code-reviewer --continue <agent_run_id> "now write the fix"

# a run that failed on a provider-side outcome (a refusal, say) can move to another model
rupu run code-reviewer --continue <agent_run_id> --model <model> [--provider <provider>]

A run that had already finished prints its recorded answer without calling the model; with a prompt it gets a new turn after its answer. A run that failed can't be continued bare — give the continue a prompt, or, when it failed on a provider-side outcome, a --model / --provider. The continued run also takes on the children the interrupted one launched. rupu run pause / rupu run resume act on workflow runs only; a standalone agent run has no pause boundary.

Next. When a job needs multiple specialists, review boundaries, fan-out, or fix loops, compose agents into a workflow rather than growing one agent prompt.