Keyboard shortcuts

Press ← or → to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

What it does alone, and what it asks

What runs without asking is the agent’s autonomy level; what it has read decides what waits for a person anyway; and a person’s decisions are kept with who made them.

Outputs on this page are from real runs on a local devnet, long identifiers shortened with …. The model is a scripted one, which plays a fixed list of tool calls, because the environment these pages were made in reaches no model provider: with a real model the runtime is the same, and only --model changes.

Autonomy

--autonomy sets what runs without asking. Each level allows everything the one below it does:

LevelReadsWrites (workspace)External (web)Spending
L0nononono
L1yesnonono
L2yesasknono
L3yesyesnono
L4yesyesaskno
L5 (default)yesyesyesask
L6yesyesyesyes, within the charter

Whatever the level, spending stays within the charter: the levels decide what the agent tries, the ledger what it can do.

Content from outside, such as a fetched page, taints the session for 24 hours: until then, external calls and spending wait for approval even at L6, so instructions hidden in a page cannot move money on their own. Another agent’s message (over A2A), a task’s output that task_status reads, and the task a tainted session gives a subagent are content from outside too. A browser or a desktop held to the hosts the operator names is the one exception: it acts on their pages as the level allows, while the session’s other external calls, and all spending, still wait (A browser and a desktop).

Quarantine

A tainted session asks before every external call, since what it read may steer the model. --quarantine (on sion agent run and on profiles) keeps what tools return from outside out of the conversation instead: the model is told what was held, under a handle, and asks questions of it with quarantine_read, giving a JSON schema for the answer. A separate call of the model, given no tools and told that the content is data, reads it and answers; the runtime checks the answer against the schema before the model sees it. Here the reader is the scripted model too, so the answer it gives is the script’s, not a reading of the page:

$ sion agent run agent-7 9dea3a0f…1e98 --task 'What is tiny_http's latest version, and its license? Then look at its sources.' --model script:held.json --web --quarantine --autonomy L5
session 97ec1566b9b1912b for agent 9dea3a0f
  → fetch_url {"url":"https://docs.rs/crate/tiny_http/latest"}
  ← [Held in quarantine as q1: 41726 characters from fetch_url, content from outside. Read what you need from it with quarantine_read: the handle, a question, and a JSON schema for the answer.]
  → quarantine_read {"handle":"q1","question":"What is the latest version of tiny_http, and what is its license?","schema":{"properties":{"license":{"enum":["MIT","Apache-2.0","MIT OR Apache-2.0","another"],"type":"string"},"version":{"format":"version","type":"string"}},"required":["version","license"],"type":"object"}}
  ← [read from q1 without tools]
    {"license":"MIT OR Apache-2.0","version":"0.12.0"}
  → fetch_url {"url":"https://docs.rs/crate/tiny_http/latest/source/Cargo.toml"}
  ← [Held in quarantine as q2: 48582 characters from fetch_url, content from outside. Read what you need from it with quarantine_read: the handle, a question, and a JSON schema for the answer.]
  → fetch_url {"url":"https://docs.rs/crate/tiny_http/0.12.0/source/"}
  ← waiting for a person's approval
session 97ec1566b9b1912b: waiting for you: fetch_url {"url":"https://docs.rs/crate/tiny_http/0.12.0/source/"} (an argument comes from content held in quarantine (0.12.0); request d70d438b). Answer with `sion agent continue 97ec1566b9b1912b --approve` or `--decline <why>`.

An answer made of booleans, numbers, strings from an enum the model gave, and strings of a strict format (a date, a time, an email address, a URL, a UUID, hex digits, a version) cannot carry instructions, so the session is not tainted, and its next fetch ran as L5 allows. A free string can ({"type": "string", "maxLength": 200}), so an answer with one taints the session, as reading the content would have. The strings an answer gives are labelled as from outside, so the fetch whose address carries 0.12.0 waited: an external call or a payment whose input carries one waits for a person, whatever the level, a skill or an allowance says, since its argument came from content no one checked, and a person approves each such call on its own (--always is not offered). Without --quarantine, the first page taints the session and the next fetch waits, whatever it carries:

$ sion agent run agent-7 9dea3a0f…1e98 --task 'What is tiny_http's latest version? Then look at its sources.' --model script:open.json --web --autonomy L5
session 923af375a850d5cf for agent 9dea3a0f
  → fetch_url {"url":"https://docs.rs/crate/tiny_http/latest"}
  ← <!DOCTYPE html>
    <html lang="en">
        <head>
            <meta charset="UTF-8">
            <meta name="viewport" content="width=device-width, initial-scale=1">
            <meta name="generator" content="docs.rs 0.0.0 (ca50bb9ce3590453e5c5a422d0cdc34eeef12c58 2026-10-27 )"><link rel="canonical" href="https://docs.rs/crate/tiny_http/latest" /><link rel="stylesheet" href="/-/static/vendored.css?0-0-0-ca50bb9ce3590453e5c5a422d0cdc34eeef12c58-2026-10-27" media="all" />
            <link rel="stylesheet" href="/-/static/style.css?0-0-0-ca50bb9ce3590453e5c5a422d0cdc34eeef12c58-2026-10-27" media="all" />
            <link rel="stylesheet" href="/-/static/font-awesome.css?0-0-0-ca50bb9ce3590453e5c5a422d0cdc34eeef12c58-2026-10-27" media="all" />

            <link rel="search" href="/-/static/opensearch.xml" type="application/opensearchdescription+xml" title="Docs.rs" />

            <title>tiny_http 0.12.0 - Docs.rs</title><script nonce="PlT0lFZkRwK7zSFa9gnddwKH20d9db+CfQqQcDulWWiUyJhr">(function() {
    …
  → fetch_url {"url":"https://docs.rs/crate/tiny_http/latest/source/Cargo.toml"}
  ← waiting for a person's approval
session 923af375a850d5cf: waiting for you: fetch_url {"url":"https://docs.rs/crate/tiny_http/latest/source/Cargo.toml"} (the session has read content from outside; external calls and spending need approval until the taint expires; request 2aea6228). Answer with `sion agent continue 923af375a850d5cf --approve` or `--decline <why>`.

What a quarantined read stops, and what it does not (agent:quarantine): the content cannot instruct the model, whose conversation never holds it, and an answer kept to values cannot either. But an answer is what the content made the reader say: a number or an enum’s value can be wrong because the page lied. A string is matched in an argument as it was given, so an argument the model writes from one in another form (spelled differently, or a part shorter than four characters) is not caught. And a browser, a desktop and the shell are not held, since the model acts on what they show: their results taint the session as before.

Approvals

At L5 the payment waits. The session is saved, and the command returns:

$ sion agent run agent-7 bdbdb742…60df --task 'Plan the labelling job and pay bob 3 USD for it.' --model script:ask.json --workspace work
session 9795b3a33b6e299b for agent bdbdb742
  → write_file {"content":"Buy 1 GPU-hour of data labelling from bob for 3 USD.\n","path":"plan.md"}
  ← wrote 53 bytes to plan.md
  → ledger_pay {"amount":"3","purpose":"data labelling","to":"bob"}
  ← waiting for a person's approval
session 9795b3a33b6e299b: waiting for you: ledger_pay {"amount":"3","purpose":"data labelling","to":"bob"} (spending at autonomy L5; request 74f23e21). Answer with `sion agent continue 9795b3a33b6e299b --approve` or `--decline <why>`.
$ sion agent continue 9795b3 --approve
  → ledger_pay {"amount":"3","purpose":"data labelling","to":"bob"} (approved by cli:operator)
  ← Pulse 16: Done: agent paid 3 USD to bob (receipt claim 3722068c); fee 0.0011 USD. Transaction 6500057e….
agent: Wrote the plan to plan.md and paid bob 3 USD for data labelling.
session 9795b3a33b6e299b: done

The call that waits names its request, 74f23e21, the start of its key: it stays the same for as long as the call waits. A decision that names it (--request 74f23e21, or /approve 74f23e21 on a channel) is refused if another call waits by then, so it cannot decide a call you did not see. An approval covers that one call and lasts 15 minutes. The session keeps each decision, with who made it (here cli: and the user name, on a channel as the channel names them) and when. Declined, the call is not made and the model is told why:

$ sion agent continue 834618 --decline 'we already paid bob for this job'
  → ledger_pay {"amount":"3","purpose":"data labelling","to":"bob"} (declined by cli:operator: we already paid bob for this job)
  ✗ a person declined this call: we already paid bob for this job
agent: I did not pay bob: you declined, since the job is already paid for.
session 834618219452230b: done

Calls like it, for the rest of the session

--always approves the call and, for the rest of the session, the same call again: a command to run again after a fix, say. --always <rule> approves the calls of its tool that the rule matches, a rule as a skill’s allowed-tools writes it (Skill permissions): write_file(notes:*), shell(git status:*), fetch_url(domain:docs.rs). At L2, where each write waits, the first of two notes is approved with the writes under notes/; the second runs without asking, and the model is told who allowed it; a write elsewhere still waits:

$ sion agent run agent-7 bdbdb742…60df --task 'Write down the sources and the plan in notes/.' --model script:notes.json --workspace work3 --autonomy L2
session 7e977e07449b5888 for agent bdbdb742
  → write_file {"content":"- the labelling vendor's price list\n","path":"notes/sources.md"}
  ← waiting for a person's approval
session 7e977e07449b5888: waiting for you: write_file {"content":"- the labelling vendor's price list\n","path":"notes/sources.md"} (a write at autonomy L2; request a72bac04). Answer with `sion agent continue 7e977e07449b5888 --approve` (`--always` for the same call again this session) or `--decline <why>`.
$ sion agent continue 7e977e --approve --request a72bac04 --always 'write_file(notes:*)'
  → write_file {"content":"- the labelling vendor's price list\n","path":"notes/sources.md"} (approved by cli:operator, with write_file(notes:*) for the rest of the session)
  ← wrote 36 bytes to notes/sources.md
  → write_file {"content":"1. Label 1,000 images.\n","path":"notes/plan.md"}
  ← [run without asking: allowed for this session by cli:operator: write_file(notes:*)]
    wrote 23 bytes to notes/plan.md
  → write_file {"content":"See notes/.\n","path":"README.md"}
  ← waiting for a person's approval
session 7e977e07449b5888: waiting for you: write_file {"content":"See notes/.\n","path":"README.md"} (a write at autonomy L2; request 7533eb1b). Answer with `sion agent continue 7e977e07449b5888 --approve` (`--always` for the same call again this session) or `--decline <why>`.
$ sion agent continue 7e977e --decline 'leave the README as it is'
  → write_file {"content":"See notes/.\n","path":"README.md"} (declined by cli:operator: leave the README as it is)
  ✗ a person declined this call: leave the README as it is
agent: Wrote the notes; the README is left as it was.
session 7e977e07449b5888: done

What a person allows has the limits of a skill’s pre-approval: it never covers a payment, which is approved on its own (--always on one is refused), nor a call whose argument comes from content held in quarantine; it cannot be given while the session has read content from outside, and one given before does not hold until the taint expires; and it never goes past what the autonomy level refuses (agent:session-allowance). On a channel, /approve always [rule] does the same, /allowed lists what the session may do without asking, and /allowed off withdraws it.

sion agent continue <session> --say "…" tells a finished session more and lets it go on. sion agent sessions lists the sessions on this machine; sion agent trace <session> prints one as a single JSON line in the chat format with tool calls that fine-tuning tools read, with how it ended. sion agent trace <session> --otel prints its steps as OpenTelemetry spans, OTLP’s JSON, for a tracing backend: the session (invoke_agent), each model asked (chat, with its tokens) and each call (execute_tool, with its outcome), named as OpenTelemetry’s generative-AI conventions name them. Those conventions are still at their Development stage, so the names may change; a call’s effect and key, and whether the session was tainted, are under sion..