Skip to content
Reference

Reference

Lookup tables: every command, every environment variable, the on-disk layout, the manifest schema, the audit record format, and a glossary. For prose explanations see the USER-GUIDE and OPERATIONS.

Contents

Command index

milieu (imperative CLI)

milieu setup [--dir <dir>] [--defaults] [--dry-run] [--yes]      Guided first run
milieu agent create <code> [--role <role>]                       Create a new agent
milieu agent ls                                                  List agents

milieu permission grant <agent> <rule>                          Allow a CLI pattern
milieu permission revoke <agent> <rule>                         Remove a permission
milieu permission [--as <code>] ls                              List the caller's permissions

milieu run [--as <code>] -- <cmd> [args...]                     Run a CLI, sandboxed
milieu run [--as <code>] -- <cmd> then <cmd> ...               Pipe sandboxed stages

milieu message [--as <code>] send <to> <subj> [--attach <f>]   Send a message (body on stdin)
milieu message [--as <code>] inbox                             List messages
milieu message [--as <code>] read <id>                         Print + mark consumed
milieu route                                                    Deliver outbound -> inbound (one-shot)
milieu wake [--as <code>] [--skill <name>] [--auto]            Drain inbox, run skill, reply
milieu ask  [--as <c>] --to <c> [--subject <s>] [--timeout <d>]  Send and block until reply

milieu skill [--as <code>] ls                                  List the caller's skills
milieu act [--as <c>] [--in <p>] [--attach <f>]... <skill>     Execute a skill (input on stdin)
milieu act [--as <c>] [--in <p>] --auto                        Auto-select and execute a skill

milieu project create <code> [--name <n>] [--instructions <f>] Create a project
milieu project ls | show <code>                                List or inspect projects
milieu project member add|rm <code> <agent>                    Manage the member roster

milieu session --as <c> open [--in <p>] [--topic <t>] [--id <id>]   Open a session
milieu session [--as <c>] ls | show <id> | close <id> | rm <id>    Read/close/remove sessions
milieu act --as <c> --session <id> <skill>                     Continue a session (stdin -> reply)
milieu cleanup session --retention <d>                         Sweep idle sessions (never swept by default)
milieu cleanup builds --keep-builds <n>                        Trim recorded skill builds (never by default)
milieu compact [--as <c>] [--keep <n>] [--over <b>] [--dry-run] [<id>...]   Summarise old turns

milieu schedule add <name> --agent <c> --cron <expr> --input <text>
                           [--skill <n>] [--project <c>] [--disabled]   Declare recurring work
milieu schedule ls                                              List schedules and next firing
milieu schedule show <name>                                     One schedule, with its run history
milieu schedule enable|disable <name>                           Pause or resume one
milieu schedule rm <name>                                       Remove it and its state

milieu eval --as <c> [--skill <s>] [--case <n>] [--backend <b>] [--judge] [--json]  Grade a skill
milieu eval --as <c> add <name> --skill <s> [--contains <s>] [--tools <t>] < input  Record a case
milieu eval --as <c> ls [--skill <s>]                           List cases and what they check
milieu eval --as <c> rm <name> --skill <s>                      Remove a case

milieu export --to <dir> [--force]                              Write this installation as a manifest
milieu a2a serve [--listen <addr>] [--url <public>] [--tls]    Serve Agent2Agent to outside agents
milieu a2a card <agent>                                        Print an agent's discovery card
milieu skill [--as <c>] versions <name>                        List recorded builds of a skill
milieu skill [--as <c>] diff <name> [<from>] [<to>]            Compare two builds
milieu skill [--as <c>] rollback <name> [<selector>]           Restore a build
milieu apply -f <m> [--prune]                                  Prune removes undeclared SKILLS only
milieu knowledge [--as <c>] [--in <p>] ls                      List knowledge (agent>project>global)
milieu knowledge [--as <c>] [--in <p>] get <path>             Print an item (most-specific)
milieu knowledge [--as <c>] [--in <p>] search [--lex|--sem|--hybrid] [--type <t>]
                 [--tag <t>] [--status <s>] [--fresh] [--current] [--verified] <query>   Search
milieu knowledge [--scope <layer>] draft [--path <p>] [--type <t>] [--force] <file>
                                                               Draft an article from a .pdf/.md/.txt
milieu knowledge [--as <c>] [--in <p>] reindex [--scope <layer>]   Rebuild one layer's index
milieu knowledge [--as <c>] [--in <p>] lint [--scope <layer>]      Check one layer (OKF + contradictions)

milieu memory [--as <c>] [--in <p>] [--global] save <name> [--description <d>] [--type <t>]   Save (stdin)
milieu memory [--as <c>] [--in <p>] ls                         List memories (union, labeled)
milieu memory [--as <c>] [--in <p>] get <name>               Print a memory (most-specific)
milieu memory [--as <c>] [--in <p>] search <query>            Search memories
milieu memory [--as <c>] [--in <p>] [--global] rm <name>     Remove a memory

milieu cleanup [blob|transcript|messages] [--dry-run] [--grace <d>] [--retention <d>] [--keep-per-topic <n>]  Reclaim derived data
milieu lint [<agent>]                                          Check agent + skill + project config
milieu audit verify                                            Verify the audit log hash chain
milieu audit tail [<n>]                                        Print the last n audit records
milieu audit query [--agent <c>] [--cmd <n>] [--sandbox <k>] [--failed] [--since <w>] [--until <w>] [--limit <n>]  Filter the log
milieu audit report <agent> [--since <w>] [--until <w>] [--prices <f>]  Activity + token cost report

milieu gateway init                                            Create the gateway CA + server identity (idempotent)
milieu gateway issue --cn <name> [--out <dir>]                Issue a proxy identity (cert + pinned CA)
milieu gateway serve --config <gateway.toml>                  Run the trusted-zone broker (mTLS)
milieu gateway audit verify                                   Verify the brokered-call log hash chain
milieu gateway audit tail [<n>]                               Print the last n brokered calls

milieu api init [--host <name>]                                Create the API CA + TLS server cert (idempotent)
milieu api key new --agent <code> [--label <t>]              Mint an API key (shown once)
milieu api key ls [--agent <code>] | revoke <id>             List or revoke API keys
milieu api serve [--listen <addr>]                            Run the REST control-plane server
milieu api audit verify                                       Verify the API access log hash chain
milieu api audit tail [<n>] [--edge rest|a2a]                  Print the last n API/A2A requests

Roles: administrator, associate (default), assistant, auditor.

milieu daemon

milieu daemon --interval <d>        Poll cadence (default 1s)
              --skill <name>        Skill run on each unread message (default respond)
              --auto                Auto-select the skill per message; --skill is the fallback
              --wake-timeout <d>    Per-agent wake deadline (default 2m)
              --retention <d>       Hourly-reclaim: prune settled messages & transcripts older than this (default 168h; negative = keep)
              --grace <d>           Blob GC: keep unreferenced blobs newer than this (default 1h)
              --keep-per-topic <n>  Transcript per-topic cap (0 = no cap)
              --quiet               Raise the log floor to warn (audit log still records)

The hourly reclaim pass (also runnable on demand as milieu cleanup) GCs orphaned attachment blobs and applies retention to transcripts and settled messages. Blob GC runs even with a negative --retention, since attachments from one-shot act --attach runs are never message-referenced and would otherwise accumulate; the grace window protects in-flight uploads. Memory and knowledge are authored data and are never touched.

milieu setup (guided)

milieu setup [--dir <dir>] [--defaults] [--dry-run] [--yes]

Asks seven questions — sandbox, model, embeddings, agent roster, servers — writes the answers to milieu.yaml, and applies them through the same engine apply uses. Nothing is written until the final confirmation. Answers may be piped in on stdin; --defaults asks nothing.

milieu apply (declarative)

milieu apply -f <manifest.yaml> [--dry-run] [--prune] [--secrets-out <file>]

Session quotas

milieu api quota set --agent <code> [--max-open <n>] [--rate <n> --window <dur>]
milieu api quota ls

Bounds how many conversations a principal may hold open and how fast it may start new ones. Checked when a session is opened over either the REST API or the A2A endpoint — both admit conversations for the same principal — and never mid-conversation, so work already admitted is not stranded. 0 is unlimited on each knob independently; “may open none” is a revoked key, not a quota of zero. Refusals are 429 with Retry-After over REST and JSON-RPC -32005 over A2A. The arrival limit is a token bucket, so a full window’s allowance may be spent as a burst but the sustained rate holds.

REST API endpoints

All act as the key’s agent; ?in=<project> is membership-gated.

Method + pathDoes
GET /v1/healthliveness (unauthenticated)
GET /v1/whoamithe authenticated agent’s identity
GET /v1/skillsthe agent’s skills
POST /v1/skills/{name}/run (?in=)run a skill, returns {skill,output}; optional attachments, topic
POST /v1/skills/run (?in=)auto-select a skill for the input, then run; optional attachments, topic
POST /v1/wake (?skill=&auto=)process the agent’s inbox
GET /v1/inbox (?unread=)list messages
POST /v1/inbox/{id}/readmark read, return the full message
POST /v1/messages {to,subject,body,attachments?}send a message
POST /v1/ask {to,body,timeout,attachments?}send + wait for the reply
POST /v1/sessions · GET /v1/sessions · GET/DELETE /v1/sessions/{id} · POST /v1/sessions/{id}/closeexternal conversations (caller’s own only)
GET /v1/knowledge · /v1/knowledge/item?path= · /v1/knowledge/search?q= (?in=&mode=&type=&tag=&status=&fresh=1&current=1&verified=1)read knowledge (union); items carry their OKF metadata
GET /v1/memory · /v1/memory/item?name= · /v1/memory/search?q= (?in=)read memory (union)

Cross-agent delivery (/v1/messages, the /v1/ask reply path) requires milieu daemon running alongside.

Permission rule grammar

Matched on argument tokens, not a flattened string:

RuleMatches
cmdany invocation of cmd
cmd(a b)exact: argv equals tokens a b
cmd(a b:*)prefix: argv begins with a b; later args unrestricted
cmd(:*)any argv
cmd()empty argv

The trailing :* is a suffix wildcard — it constrains the leading tokens but not what follows (curl(https://host:*) still permits extra args). Use an exact rule to bound the whole command line.

Built-in tools

These are handled inside milieu rather than as sandboxed subprocesses, and each is audited with sandbox builtin. All but skill are granted like any other command — a rule naming the tool in the agent’s allow list.

ToolGrantPurpose
knowledgeknowledgeQuery the agent’s knowledge base
memorymemoryRead and write the agent’s memory
transcripttranscriptRecall past runs
sessionsession(request-input:*)Say the reply is a question: marks the session input-required
MCP toolsmcp(<route>[ <tool>]:*)Tools brokered from an external MCP server, projected with its own schemas as <route>__<tool>
gatewaygatewayBrokered HTTP call; the credential stays in the gateway
codecodeRun a Starlark program (see below)
skillnone — part of the skill definitionRead the running skill’s own bundled files

The code tool

code runs a short Starlark program — Python syntax, no standard library — so a model can do in one step what would otherwise be a chain of tool calls. Fetching a list and acting on each element is one script, not one round trip per element, which is both faster and materially cheaper in tokens.

Starlark is used because it has no ambient authority: no open, no sockets, no imports. Everything a script can reach is a function milieu injected, and which functions those are is derived from the agent’s own grants:

InjectedPresent whenReturns
run(cmd, args=[], stdin="")any subprocess command is grantedstruct .exit_code .stdout .stderr
gateway(path, method=, query=, headers=, body=)gateway is grantedstruct .status .body
knowledge(*args) / memory(*args) / transcript(*args)each granted separatelystruct .ok .text
json, re, time, mathalwaysmodules (see below)

A script is therefore never a route around permissions: run calls the same runner as a direct tool call, is permission-checked identically, and writes its own audit record. Granting code alone grants nothing to call.

The program sets a global result, which is returned to the model; print() output is returned alongside it. Because Starlark has no exceptions, a call that ran and failed returns a value to branch on (.exit_code, .status), while one that could not be attempted — a denied permission — aborts the program.

Runs are bounded at 10M execution steps and 30 seconds, and captured output at 8 KiB. A step-limit breach is reported to the model as a non-terminating loop so it can fix the actual fault.

Four modules are always present, because a capability’s return value is of no use until it can be parsed. All are values-in, values-out and widen nothing a script can reach:

ModuleNotes
jsondecode/encode. The shape most API and MCP results arrive in
rePython-shaped search/match/findall/sub/split/escape. The engine is Go’s RE2, so patterns may not use lookahead, lookbehind or backreferences — a compile failure says so explicitly. In exchange matching is linear-time, so a model-written pattern cannot hang a run
timeparse_time/from_timestamp/now and the duration units. Subtract two times for a duration; divide by time.hour for a number
mathThe usual functions. Note floor/ceil return ints, sqrt a float

time.now() is pinned to a single instant per run. Parsing and comparing timestamps is ordinary computation and is why the module is present; reading the clock is the one part that is ambient authority. Pinning keeps “older than 30 days” working while keeping a run reproducible and denying a script a timing side channel — now() cannot measure how long anything took. It does advance between runs.

Environment variables

VariableDefaultPurpose
MILIEU_HOME$PWD/.milieuRoot state directory
MILIEU_AGENTDefault caller agent (override with --as)
MILIEU_SANDBOXbwrap (Linux) / sbexec (macOS)Sandbox backend; none is never auto-selected — set it explicitly to opt out of isolation
MILIEU_SANDBOX_PATH/opt/homebrew/bin:/usr/local/bin:/usr/bin:/bin (or build-time SANDBOX_PATH)Fixed search path for sandboxed commands; relative entries ignored
MILIEU_LLMfirst available (claude, then openrouter), else echoLLM backend
ANTHROPIC_API_KEYClaude API key (enables claude)
MILIEU_LLM_URLhttps://openrouter.ai/api/v1OpenAI-compatible chat base URL for openrouter (also LiteLLM, vLLM, local llama-server)
MILIEU_LLM_TOKENBearer token for the openrouter endpoint (falls back to OPENROUTER_API_KEY)
MILIEU_LLM_MODELanthropic/claude-haiku-4.5Model id for openrouter, e.g. openai/gpt-4o, google/gemini-2.5-pro
MILIEU_ENV_PASSTHROUGHComma-separated host var names that agent.toml [env] $VAR references may resolve
MILIEU_MAX_ITERATIONS50Ceiling on a run’s tool-use loop. A skill’s max-iterations is clamped to this; a skill that declares none gets 10. A value that is not a positive integer is ignored
MILIEU_SCHEDULERfalseLet milieu daemon fire declared schedules. Off by default: agents that act on their own initiative are a different proposition from agents that only answer. Schedules can be declared, linted and applied either way
MILIEU_EMBEDfirst available of openai, voyageEmbedding backend
MILIEU_EMBED_URLOpenAI-compatible embeddings base URL (enables openai), e.g. http://localhost:8080/v1
MILIEU_EMBED_TOKENBearer token for the embeddings endpoint (optional)
MILIEU_EMBED_MODELbackend defaultModel for the active embeddings backend (text-embedding-3-small for openai, voyage-3-lite for voyage)
VOYAGE_API_KEYEnables the voyage backend
OTEL_ENABLEDfalseGate for the OpenTelemetry SDK in the long-running servers (api serve, gateway serve, daemon). When off, the global no-op providers stay in place and telemetry costs nothing
SERVICE_VERSIONembedded VCS revision, else devSets the service.version resource attribute; left unset, binaries built from a git checkout are auto-stamped with their commit SHA (-dirty when the tree was modified)

When OTEL_ENABLED=true, exporters and the rest of the resource are configured out-of-band through the standard OTEL_* environment variables the SDK reads directly — OTEL_EXPORTER_OTLP_ENDPOINT/_HEADERS/_PROTOCOL, OTEL_TRACES_EXPORTER/OTEL_METRICS_EXPORTER, OTEL_TRACES_SAMPLER, and OTEL_RESOURCE_ATTRIBUTES (e.g. deployment.environment=staging). Setting OTEL_METRICS_EXPORTER=prometheus makes the API server expose a scrape endpoint at GET /v1/metricsz instead of pushing metrics over OTLP. See OPERATIONS §Observability.

Set by the runner inside an agent sandbox (not operator-set): MILIEU_GATEWAY_URL (when a [gateway] block is present), and HTTPS_PROXY/HTTP_PROXY (when a restricted [network] policy is enforced by the loopback egress proxy).

Filesystem layout

$MILIEU_HOME/var/
├── skills/<agent>/<name>/<digest>/       # every build of a skill that has run
├── sessions/<id>/                          # external conversations: index.md, one file per turn, summary.md
├── global/{knowledge,memory,index}/        # global layer (shared, ungated)
├── agents/<code>/
│   ├── agent.toml                          # identity, role, permissions, network, [env], [[mount]], [gateway]
│   ├── personality.md                      # voice/tone brief (prepended to the system prompt)
│   ├── responsibility.md                   # mandate/scope brief (prepended to the system prompt)
│   ├── skills/<name>.md | <name>/SKILL.md  # capability definitions
│   ├── knowledge/  memory/  index/         # the agent's own layers
│   ├── work/                               # the ONLY sandbox-writable path
│   ├── in.db  out.db                       # message queues
│   └── state/<topic>/                      # run transcripts, filed by topic (+ TOPICS.md)
├── projects/<code>/
│   ├── project.toml  instructions.md       # config + prepended instructions
│   └── knowledge/  memory/  index/         # membership-gated project layers
├── evals/<agent>/<skill>/<case>.md          # evaluation cases (declared by an agent's `evals:`)
├── schedules/<name>.toml                   # recurring task declarations (apply writes these)
├── schedules/state/<name>.json             # when each last fired (the daemon writes these)
├── blobs/                                  # content-addressed attachments (SHA-256)
├── audit/audit.jsonl                       # append-only, hash-chained
├── gateway/{ca,server}/  gateway/audit.jsonl   # trusted-zone broker state
├── proxy/<code>/                           # an agent's proxy identity (host-only)
└── api/{ca,server}/  api/keys.db  api/audit.jsonl   # API server state

Manifest schema

Multi-document YAML, apiVersion: milieu.dev/v1. Reconcile is upsert, no prune: declared resources are set to match the manifest; resources absent from the manifest are never deleted.

apiVersion: milieu.dev/v1
kind: Agent
metadata: { name: mira }
spec:
  role: associate                                   # administrator|associate|assistant|auditor
  personality: ./agents/mira/personality.md         # voice/tone brief, copied in + injected at run
  responsibility: ./agents/mira/responsibility.md   # mandate/scope brief, copied in + injected at run
  permissions: ["knowledge(search:*)", "curl(:*)"]  # permission rules
  network: ["api.github.com:443"]                   # [network] allow entries
  gateway: { endpoint: https://gw:8443, identity: gateway-id }  # issues a proxy identity
  apiKey: { create: true, label: ci }               # mints an API key (once)
  sessionQuota: { maxOpen: 5, rate: 20, window: 1h }  # 0/omitted = unlimited per knob
  skills:                                           # copied into the agent; --prune removes undeclared
    - ./agents/mira/skills/respond                  # directory form (SKILL.md + resources)
    - ./agents/mira/skills/handover.md              # flat form
  evals:                                            # one directory per skill, named for the skill
    - ./agents/mira/evals/respond                   # grades the `respond` skill
---
apiVersion: milieu.dev/v1
kind: Project
metadata: { name: acme }
spec:
  members: [mira]
  instructions: ./acme.md
  knowledge: [{ source: ./acme-docs }]
---
apiVersion: milieu.dev/v1
kind: Knowledge
metadata: { name: global }
spec: { layer: global, sources: [./global-docs] }   # layer: global|agent (+ agent: <code>)
---
apiVersion: milieu.dev/v1
kind: Gateway
metadata: { name: default }
spec:
  listen: 0.0.0.0:8443
  routes:
    - { name: github, prefix: /github, backend: rest,
        upstream: https://api.github.com,
        auth: { kind: bearer, tokenEnv: GH_TOKEN } }
    - { name: oms, prefix: /oms, backend: mcp,          # an MCP server, brokered
        upstream: https://mcp.vendor.example/mcp,
        auth: { kind: bearer, tokenEnv: OMS_MCP_TOKEN },
        mcp: { tools: [order_status] } }                # operator allowlist (required)
    - { name: counsel, prefix: /counsel, backend: a2a,  # an A2A peer, brokered
        upstream: https://peer.example/a2a/advisor,
        auth: { kind: bearer, tokenEnv: PEER_TOKEN } }  # POST /send and /card
---
apiVersion: milieu.dev/v1
kind: API
metadata: { name: default }
spec: { host: api.example.com }
---
apiVersion: milieu.dev/v1
kind: Web
metadata: { name: portal }
spec:
  host: portal.example.com                          # extra SAN for the server cert
  operator: admin                                   # created if absent; the portal
                                                    # will not start without one
---
apiVersion: milieu.dev/v1
kind: Schedule
metadata: { name: nightly-audit }                   # the name is also the transcript topic
spec:
  agent: auditor
  skill: review-log                                 # omit to auto-select per run
  cron: "0 3 * * *"                                 # minute hour dom month dow, host local time
  input: "Review yesterday's audit log and record anything anomalous."
  # project: acme                                   # optional scope; membership is enforced
  # disabled: true                                  # declare it without starting it
---
apiVersion: milieu.dev/v1
kind: Settings
metadata: { name: runtime }
spec:
  sandbox: sbexec                                   # bwrap|sbexec|none
  llm: openrouter                                   # claude|openrouter|echo
  llmModel: anthropic/claude-haiku-4.5
  embed: voyage                                     # openai|voyage
  embedModel: voyage-3-lite

Notes:

  • Secrets are never in the manifest. apply mints API keys / CA fingerprints and prints them once (or to --secrets-out <file>; keep it out of VCS). A key is minted only if the agent has no active one.
  • Settings (backend and model names) are env-only; apply writes milieu.env in MILIEU_HOME for the operator to source. No token is ever written there.
  • sessionQuota bounds conversations opened over REST and A2A, since both admit them for the same principal. It is applied whether or not a kind: API document is present. Zero (or omitted) is unlimited on each knob independently, so an agent without one is unchanged; “may open none” is a revoked key, not a quota of zero.
  • Web declares the operator’s name, never their password. apply takes a password out of band (milieu setup prompts for one) or generates one and reports it once. An existing operator is left alone — a re-apply must not reset the credential to the installation; milieu web admin passwd does that.
  • evals: declares an agent’s evaluation suites: one directory per skill, named for the skill it grades (./agents/mira/evals/respond grades respond, the same convention skills: uses). Installing replaces rather than merges — the declared cases are the suite, so a case deleted from the source stops being graded — and a directory with one unparseable case installs none of it. Like skills:, a nil field means the manifest does not manage them and evals: [] means there are none; only --prune removes an undeclared suite.
  • Schedule declares recurring work but never switches it on: whether anything fires is MILIEU_SCHEDULER on the daemon, so a manifest can be applied to a host that runs nothing. Re-applying leaves an existing schedule’s clock alone — it neither resets the history nor replays what was missed while the schedule was declared differently. Nothing is pruned: a schedule that vanishes from a manifest may have moved to another file, and silently stopping recurring work is worse than leaving it declared. Remove one with milieu schedule rm.
  • The kind/spec shape mirrors Kubernetes so manifests can map onto CRDs later.

Audit records

One JSON object per line in var/audit/audit.jsonl (and, in the same shape, the API and gateway access logs):

FieldMeaning
seqmonotonic sequence number
agentthe acting agent
cmdcommand or tool name
argsargument tokens
exit_codeprocess/tool exit code
started_at, ended_attimestamps
sandboxnone · bwrap · sbexec · builtin
modelthe model that served the call — present only on records that called one
usagetoken counts for the call — present only on records that called a model
skill_versionthe skill’s declared version — on act records only
skill_digestsha256 of the skill as loaded (manifest + bundled resources), so an edit without a version bump is still visible
stopwhy a run ended early: max_iterations, max_tokens, no_tool, error. Absent when the model finished on its own — which is also how records written before the field existed read
prev_hash, hashchain fields; hash covers prev_hash + seq + payload

usage holds input_tokens, output_tokens, cache_write_tokens, cache_read_tokens, and calls (how many model calls the record covers — a skill run makes one per tool-loop iteration, plus one for topic classification, and reports their sum). Both model and usage are absent from records that never reached a model, which keeps them byte-identical to logs written before token accounting existed, so pre-existing chains still verify.

Usage rides inside the hashed payload, so token counts are as tamper-evident as the rest of the record.

builtin rows are host-side tool calls with no subprocess (currently the knowledge and skill tools). audit verify recomputes the chain and reports the first break:

audit: CHAIN BROKEN after 41 record(s): hash mismatch at seq 42 (record content was modified)

Hash chaining detects edits, reorders, insertions, and deletions in the middle — but not tail truncation (a shorter-but-valid chain). Anchor the latest hash externally if you need to detect that.

audit query filters those records without leaving the CLI: --agent, --cmd, and --sandbox match a field exactly, --failed keeps only non-zero exit codes, --limit <n> keeps the newest n matches, and --since / --until bound started_at. A <when> value is either a Go duration measured back from now (--since 24h, --since 168h) or an absolute RFC3339 timestamp (--until 2026-06-01T00:00:00Z). audit report <agent> aggregates the same window into a Markdown summary — totals, failures, per-command / per-sandbox breakdowns, and token usage with cost — for the auditor role.

Cost and the price table

The log records tokens, never money. A token count is a fact about a call that stays true; a price is policy that changes, differs per model and contract, and would be frozen wrong the moment it entered an append-only log. audit report therefore computes cost at render time from var/audit/prices.json — correct the table and every historical report restates.

{
  "claude-opus-5":    {"input": 5, "output": 25, "cache_write": 6.25, "cache_read": 0.5},
  "claude-haiku-4-5": {"input": 1, "output": 5,  "cache_write": 1.25, "cache_read": 0.1}
}

Rates are per million tokens, the unit providers publish, so an entry can be copied off a price page without arithmetic. The four kinds are priced separately because providers charge them differently — a cache read is a fraction of a fresh input token, and a cache write costs more — so collapsing them misprices any run that used caching.

A key matches by longest prefix, so claude-opus-5 prices claude-opus-5-20260401: providers append dated suffixes, and an exact-match table would silently lose the price on the next release. A model with no entry is reported as unpriced and excluded from the total rather than counted as free, and the report names what it left out. With no price table at all, reports show tokens and omit cost.

--prices <file> values the same window against a different table — what a period would have cost on another tier, or under a rate you are negotiating:

./milieu audit report alice --since 168h --prices ./enterprise-rates.json

Because the log holds tokens rather than money, any table can be applied to any past window. A report priced this way says so in its footer, so its figures are never mistaken for what the agent is actually billed. Unlike the default table, a file named here must exist and parse — the request was for those rates specifically, so falling back silently would answer a different question.

Glossary

TermMeaning
agentA directory under var/agents/<code>/ with an identity, role, permissions, skills, and its own knowledge/memory layers. Not a long-running process.
roleadministrator / associate / assistant / auditor; recorded in agent.toml. Auditors are read-only and cannot send messages.
skillAn Agent-Skills-format capability (<name>.md or <name>/SKILL.md); the body is the system prompt.
layerOne of global / agent / project. Reads union most-specific-first (agent > project > global) and shadow on clash.
projectA membership-gated workspace with instructions and its own knowledge/memory layers.
union / shadowUnion = merge all applicable layers; shadow = a more-specific file hides a lower one with the same path/name.
routeCopy delivered messages from senders’ outboxes to recipients’ inboxes.
wakeDrain an inbox, run a skill per unread message, reply.
gatewayA trusted-zone broker that holds secrets and injects them into brokered calls so they never enter the sandbox.
API serverA REST control plane letting external programs drive milieu as an agent via a bearer key over TLS.
blobA content-addressed (SHA-256) attachment in var/blobs/, shared across agents by digest.