# Cayu
> Cayu is an open-source Python framework for building and running domain-specific long-horizon agents, with a durable runtime built in.
Framework: Cayu's Python APIs, components, and defaults for building and running
agents: models, tools and integrations, context, knowledge, memory, web/browser
capabilities, multi-agent coordination, workspaces, verification, and evals.
Harness: the system developers assemble with Cayu, including context, tools, memory,
policies, domain logic, verification, and evals.
Runtime: the framework’s integrated execution machinery for durable sessions and
state, task dispatch, workers, recovery, approvals, budgets, replay, and recorded
execution evidence.
Agent: the resulting product that performs work in a particular domain.
Cloud: optional deployment and shared operation of agents built with the framework.
Developers build and customize the domain-specific harness with Cayu. The runtime
is reusable across domains. Claude Code, Codex, and Cursor can help developers
write a Cayu project; they are development tools, not harnesses to import.
A bounded goal may require hours or days of execution. Cayu supports recorded
progress across interruptions, human input, and recovery. Ambiguous external
effects require reconciliation; durable state does not establish an unknown result.
## Pages
- [Homepage](https://cayu.dev/): What Cayu is, how it fits, a sample long-running job, and a quickstart.
- [Framework](https://cayu.dev/framework/): Agent-building capabilities, harness composition, the integrated runtime, evaluations, deployment, Python contracts, and cost evidence.
- [End-to-end walkthrough](https://cayu.dev/walkthrough/): A campus dining agent built in 14 steps, from Python building blocks and harness composition to durable sessions, approval, and verification.
- [Why Cayu?](https://cayu.dev/why-cayu/): Why domain-specific long-horizon agents benefit from an integrated Python framework.
- [Comparison](https://cayu.dev/comparison/): Framework capabilities compared through a versioned public code-audit snapshot, including execution and recovery boundaries.
- [Quickstart](https://cayu.dev/docs/get-started/quickstart/): Installation, project generation, tests, evaluations, and model configuration.
- [Documentation](https://cayu.dev/docs/): Guides, runtime and operational references, and the Python, HTTP and CLI API reference.
- [Examples](https://github.com/cayu-dev/cayu/tree/main/examples): Executable examples.
## Framework, application, and Cloud
Cayu is Apache-2.0 and runs independently of Cayu Cloud on application-selected
infrastructure. Applications own their customer interfaces, authentication,
business authorization, domain logic, and completion criteria. The framework
includes a packaged operator dashboard for sessions, workflows, usage, and evals.
Configure durable storage, access policy, model providers, and execution boundaries
for the deployment. Native Python tools are trusted application code.
Cayu Cloud is an optional shared workspace for building, deploying, operating,
and improving agents built with Cayu. It is currently invite-only. Console is the building and editing
experience. Compound focuses on evaluations and reviewed improvement. Its planned
Self Improvement — Genetic Algorithm experience is marked Coming.
Cayu supports multiple provider integrations and replaceable Python
contracts. This is separate from Cloud's model-access and billing experience.
Cayu's paired cost comparisons measure a context, caching, or tool-exposure
strategy on a given workload and price source. Their results are workload-specific,
not universal token or whole-workflow savings.
- [Cayu for the enterprise](https://cayu.ai/)
- [Cayu Cloud](https://cloud.cayu.dev/)
- [Source](https://github.com/cayu-dev/cayu)
- [Runtime contracts](https://github.com/cayu-dev/cayu/blob/main/docs/runtime-contracts.md)
- [Cost optimization guide](https://github.com/cayu-dev/cayu/blob/main/docs/cost-optimization.md)
- [Community](https://discord.gg/VUNHpjTDQ)
# Cayu developer manual
Detailed Python entries are listed in https://cayu.dev/docs/python-api.json.
# Building an application UI over Cayu sessions
This guide ships with the Cayu package, so the installed version is always available offline with `cayu guide app-ui`. [Release source](https://github.com/cayu-dev/cayu/blob/v0.9.1/src/cayu/guides/app-ui.md).
This guide is for a custom browser UI (a "bring your own UI" app) that shows
Cayu sessions: a list of runs, a live timeline, a progress panel, token usage.
Most of the work in such a UI is watching sessions change, and the easy way to
write that (a short fixed timer that never stops) is what makes these apps
expensive. One idle tab written that way made about 1,600 requests an hour and
used a third of a 0.5 vCPU web task.
Follow the rules below. The first one removes most of the others.
## Follow live sessions with client.js
`mount_cayu(..., path="/cayu")` serves a dependency-free ES module at
`/cayu/client.js`, with TypeScript declarations at `/cayu/client.d.ts`. It is
versioned with the server: `GET /cayu/api/contract` advertises it under
`client` (`module_url`, `types_url`, `version`, and `guide_topic: "app-ui"`).
`create_server` serves it next to its API when the API path ends in `/api`
(`/client.js` for the default `/api`). Import it from the served URL; there is
no build step and nothing to install.
```js
const cayu = await connect() // reads /api/contract and checks contract_version
```
The module is served behind the same access dependency as the API, and requests
use same-origin credentials, so cookie sessions work unchanged. For header-based
auth, pass `connect({ headers: { Authorization: ... } })`, and load the module
from a URL your app serves if the import itself cannot carry the header (the
file ships in the package as `cayu/server/browser_client/client.js`).
It provides:
- `followSession(sessionId, { afterSequence, excludeEventTypes, onEvent, onStatus, onError, signal })`
returns `{ done, stop, lastSequence, status, transport, lastError }`.
- `watchSessions(filter, { onChange, onError, onStop, intervalMs, signal })`
returns a function that stops the watch.
- `getSessionUsage(sessionId)` reads `GET /api/sessions/{id}/usage`.
What the client guarantees, so your code does not have to:
- **Transport.** `followSession` uses the session follow stream when the
contract advertises `sse.session_follow`, and otherwise polls
`GET /api/sessions/{id}/events?after_sequence=...`, backing off while nothing
new arrives. A stream that fails repeatedly falls back to polling.
- **Order.** `onEvent` gets each event once, in `sequence` order, including
across reconnects.
- **Hidden tabs.** While `document.hidden` is true the client sends no requests
and closes any open stream. When the tab is visible again it resumes after
the last sequence it delivered, with no gap and no full refetch.
- **Session end.** `done` resolves once the session is `completed`, `failed`,
or `interrupted` and its remaining events are delivered. After that the
client sends nothing for that session.
- **Overview cadence.** `watchSessions` refreshes at most every 15 seconds
(lower `intervalMs` values are raised to 15 s), doubles the interval while
the list is unchanged, and stops (`onStop("idle")`) once no listed session
is `pending`, `running`, or `interrupting`.
- **Errors.** Transient failures (network, 408, 429, 5xx) are retried with
exponential backoff and jitter, and never sooner than `Retry-After`. Other
4xx responses (401, 403, 404, 422, ...) and responses that do not match the
contract stop the operation and call `onError` with a `CayuClientError`
whose `kind` says why (`auth`, `not_found`, `contract`, `http`). A stopped
`followSession` also rejects `done` with that error.
`interrupted` includes pauses for tool approval or user input. After the UI
answers one, call `followSession` again with
`afterSequence: follow.lastSequence`. Likewise, call `watchSessions` again after
starting new work if the previous watch stopped as idle.
Do not hand-write polling loops for session events or session lists. If
`client.js` does not fit, use the same endpoints with the rules below.
## If you must poll
- Poll only while the tab is visible (`document.visibilityState === "visible"`),
and pause the timer on `visibilitychange`.
- Poll only while something is active. Stop when every session you show is
terminal, and start again when the user starts new work.
- Refresh overview data (lists, counts, dashboards) no more often than every
15 seconds.
- Back off while responses do not change, for example by doubling the interval
up to a ceiling of a minute or two.
- Stop following a session once it is terminal and you have its terminal event.
- Read events incrementally with `after_sequence`, advancing to
`scan_through_sequence` when the page has one. Never re-read a session's whole
history to find what changed.
- On errors, back off exponentially with jitter, honor `Retry-After`, and stop
on 401, 403, and 404 instead of retrying them.
## Split overview from detail
Give each screen its own bounded read. The overview reads
`GET /api/sessions` with a status filter and a `limit`; a detail view reads one
session, its events after a cursor, and its usage. Do not build one
"everything" endpoint (for example an app-level `/api/state` that rebuilds the
whole workspace) that loads every job, its pending actions, and its full event
history on every call. Its
cost grows with every run the app has ever made, and every open tab pays it on
every refresh.
## Ask Cayu whether a session is executing
Use `await app.inspect_session_execution(session_id)` instead of a local worker
dictionary or an age threshold on `last_activity_at`. A console answer may resume
the session in the Cayu server process while an application worker has no local
task for it. A model stream or tool may also run silently for minutes.
```python
from cayu import SessionExecutionConfig
# Optional when constructing CayuApp; these are the defaults.
execution_config = SessionExecutionConfig(
heartbeat_interval_seconds=15,
lease_seconds=60,
owner_label="review-worker", # optional application label; never include secrets
)
execution = await app.inspect_session_execution(session_id)
if execution.state == "executing":
label = "Running here" if execution.local_owner else "Running in another worker"
elif execution.state == "owner_lost":
label = "Execution owner lost; inspect recovery"
else:
label = execution.state
```
Pass `session_execution=execution_config` to `CayuApp` to change the interval or
label. The lease must cover at least three heartbeat intervals. The default
heartbeat writes one bounded owner row every 15 seconds per active session epoch,
independently of model deltas and tool progress. Claim and release each add one
small write. It does not update `last_activity_at`, rewrite the checkpoint, or
load the transcript. Coarse model/tool/publishing boundaries update
`last_progress_at` and `last_progress_kind` on the next heartbeat.
`SessionExecutionState` includes an opaque process-instance `owner_id`,
`owner_kind`, `run_epoch`, a hashed `operation_id` reference, `claimed_at`,
`heartbeat_at`, and `lease_expires_at`. Owner kinds cover application runners,
Cayu server streams, task workers, recovery, and foreground child delivery.
No hostname, PID, prompt, tool argument, or provider payload is included.
SQLite and PostgreSQL make the same projection visible to other processes;
in-memory stores work only inside their process.
`GET /api/sessions/{id}/state` exposes it under `execution`. Its ETag changes
on heartbeat renewal and when the lease expires. Polls themselves never renew
the lease or change the epoch:
```bash
curl --fail --silent --show-error "$CAYU_URL/api/sessions/$SESSION_ID/state"
curl --silent --show-error -H 'If-None-Match: W/"previous-etag"' \
"$CAYU_URL/api/sessions/$SESSION_ID/state"
```
`waiting` means a retained human input, approval, or foreground child wait; it exposes no live owner.
`idle` means work has not started, and `terminal` means it has settled.
`owner_lost` means nonterminal work has no living owner for the current epoch,
including a lease that expired after a process died. PostgreSQL uses the database
clock; SQLite and memory use the host clock, so shared SQLite deployments require
synchronized host clocks. `unknown` covers
older writers and custom stores that cannot attest execution presence. Treat it
as uncertain rather than declaring a run dead. Maintained durable stores need
the additive schema migration 113 before a new binary starts writing leases.
This projection grants no execution authority. A live owner blocks stale-run
fencing and appears as `active_execution_owner` in recovery planning. The
inactivity-gated recovery calls honor it too: a `recover_incomplete_session(...)`
or `recover_incomplete_sessions(...)` request that sets `inactive_for_seconds`
and runs before expiry returns `skipped_execution_owner` with
`execution_lease_expires_at`, and batch recovery includes that result too. A
request that leaves `inactive_for_seconds` at its default `None` is an explicit
recover-now call: it does not consult the execution lease and is not skipped.
Only send it when you know the owner is gone.
Your own startup recovery loop must retry skipped entries after the reported
time rather than run a single sweep and discard them. The Cayu server's
`startup_recovery_statuses` sweep does this already: sessions it skipped for a
live lease, typically left by a process that crashed just before the restart,
are re-planned in the background after the lease expires, with the same
`recovery_inactive_after_seconds` threshold. It retries up to three times, then
logs the sessions whose owners are still alive and leaves them to explicit
recovery. A process crash can leave up to 60 seconds of the default lease.
Explicit operator interruption retains its existing cancellation contract;
execution presence does not authorize forced takeover.
Transient renewal errors do not release the row, and the heartbeat keeps retrying
for as long as the run is active, even after a store outage outlasts the lease.
Once the store answers again, the expired lease cannot be renewed, so the same
process reclaims observation for its run. That reclaim succeeds only while the
transaction still matches that process's run epoch; a successor fence wins and
the stale heartbeat stops.
Retiring a local recovery owner stops its heartbeat even when the durable
invocation must remain for a later recovery attempt. A live but hung operation
continues to heartbeat: lack of progress alone is not proof of death. Use the
application's operation deadline or explicit interruption to stop it.
After an owner is lost, use the existing recovery plan and fenced claim; inspecting or
showing the state never reserves recovery. An expired token cannot renew or
replace a successor's owner row. During orderly completion, retained cleanup
keeps its heartbeat until physical cleanup finishes.
## Keep GET handlers read-only
A GET handler in your app must not reconcile decisions, migrate data, backfill
projections, or write anything. Reads happen far more often than writes, from
every tab, and a read that can write makes idle tabs contend with real work. Do
that work on the write path that changes the data, or in a durable event sink
that reacts to the event once (`cayu guide human-attention` describes durable
sinks and how to repair them).
## Use usage and cost, not event folds
Do not compute tokens, cost, or other aggregates by folding a session's full
event history on each request. Use `GET /api/sessions/{id}/usage` (or
`getSessionUsage`) for tokens and `POST /api/sessions/{id}/cost` with a price
book for cost. For an aggregate Cayu does not provide, keep a projection that
you update from new events with an `after_sequence` cursor when they are
written, and read the projection.
## Idle budget
An idle open tab should cost under 1% of a 0.5 vCPU web process. In practice an
idle tab sends nothing while it is hidden or while nothing is running, and only
occasional cheap, bounded requests otherwise. When your installed Cayu provides
`cayu diagnostics requests`, check an idle tab against the budget with:
```bash
cayu diagnostics requests --budget-idle-cpu 0.01 --vcpu 0.5
```
## Example: a progress panel
A panel that shows a running session's tool activity and final token usage.
It streams or polls through `client.js`, pauses while hidden, and stops by
itself when the session ends.
```js
const cayu = await connect()
export function showProgress(sessionId, panel) {
const steps = panel.querySelector("ol")
const status = panel.querySelector("[data-status]")
const follow = cayu.followSession(sessionId, {
excludeEventTypes: ["model.text.delta"],
onStatus: (value) => {
status.textContent = value
},
onEvent: (event) => {
if (event.type.startsWith("tool.call.")) {
const item = document.createElement("li")
item.textContent = `${event.tool_name ?? "tool"}: ${event.type.slice(10)}`
steps.append(item)
}
},
})
follow.done
.then(async ({ reason, status: final }) => {
if (reason !== "terminal") return // stopped by the caller
const usage = await cayu.getSessionUsage(sessionId)
status.textContent = `${final}, ${usage.usage.total_tokens} tokens`
})
.catch((error) => {
status.textContent = `Stopped: ${error.message}`
})
return () => follow.stop() // call when the panel unmounts
}
```
For a list of active runs, use `watchSessions` instead of a timer:
```js
const stop = cayu.watchSessions(
{ status: ["pending", "running"] },
{ onChange: (sessions) => renderRuns(sessions) },
)
```
# Approvals and human input
An agent may need a missing fact or permission to act. A user-input checkpoint records the question; a tool approval records a protected invocation. Both can outlive the process that created them.
## Decide where the gate belongs
For a damaged-order agent, proposing a replacement can be a reversible preparation step. Executing the exact replacement is the consequential action. Bind policy and approval to that execution. Customer prose describing a desired outcome is not a verified approval receipt.
Use `AlwaysRequireApprovalToolPolicy` for named tools requiring a human decision. Compose application validation through supported guards when necessary. The policy must be registered in the application; prompt instructions alone cannot enforce authorization.
## Resolve the owning action
Inspect the pending action's session, round and call identities. Resolve it through the public approval or input APIs under authenticated authority. A foreground parent may point to a child's action: follow discovery to the owning child rather than treating the parent reference as a separate approval.
An authorized resolution resumes the recorded invocation under its admitted contract. Unrelated user input should not be used to wake a protected tool call. A stale or mismatched approval must remain rejected.
## Notify and verify
Use [human attention](https://cayu.dev/docs/build/human-attention/) to hand a durable question to your application notification sink. The sink's delivery receipt and the actual approval are separate evidence.
Test a pause, a rejected decision, an accepted bounded decision and a repeated delivery. The [support-agent recipe](https://cayu.dev/docs/recipes/support-agent/) provides an installed executable example; [durable operations](https://cayu.dev/docs/build/durable-operations/) supplies the complete lifecycle contract.
# Building applications with Cayu
This guide ships with the Cayu package, so the installed version is always available offline with `cayu guide authoring`. [Release source](https://github.com/cayu-dev/cayu/blob/v0.9.1/src/cayu/guides/authoring.md).
For a complete installed-package support agent with restartable clarification and
external approval receipts, run `cayu guide order-support`.
This guide is the canonical concept map and implementation path for Cayu
applications. Generated projects repeat only their local commands and
registration rules in `AGENTS.md`.
For local development, the supported loop is:
`edit the requested behavior -> inspect -> check -> test -> eval`
Configure reasoning through `ThinkingConfig` on the agent, run/resume request, or
workflow `StepRunOptions`. Effort values are provider-specific; consult
`cayu guide thinking` for the exact vocabulary,
precedence, local rejection rules, and backend acceptance boundaries.
## 1. Start with one model-only agent
In a fresh generated project, edit the existing agent, test, and eval in place.
Do not retain the starter and add a second agent. Give the agent a focused job,
domain input, and observable output. A system prompt is optional until the job
requires one, and a model-only agent needs no tools.
Use safe local defaults for reversible development choices. Ask questions when
the requested behavior itself is ambiguous. Questions about recipients,
credentials, spending authority, destructive effects, ambiguous retries,
durable recovery, and infrastructure begin when the user requests those
capabilities or asks to deploy.
## Cayu Map
Use only the concepts your agent needs. Start with the first row and add another
only when the requested behavior requires it.
| When you need it | Cayu concepts | Start here |
| --- | --- | --- |
| Create or extend a generated application | complete scaffold convention, normalized plan, explicit registration | `cayu guide applications` |
| One model-driven agent | `CayuApp`, `AgentSpec`, `ModelProvider`, `RunRequest` | `cayu guide anatomy` |
| A provider-neutral run result | `run_to_completion`, `RunOutcome`, events | `cayu guide anatomy#verify-the-contract` |
| Model-specific routing or capabilities | provider registration, model catalog, thinking, structured output | `cayu guide providers`, `cayu guide structured-output` |
| A capability outside the model | `Tool`, `ToolSpec`, `ToolContext` | `cayu guide references#domain-tool` |
| Replay or mutation semantics | `ToolEffect`, idempotency keys | `cayu guide tool-effects` |
| Notify a person about a durable pause | pending actions, attention identity, durable sink handoff, reconciliation | `cayu guide human-attention` |
| Durable operational changes | proposal, policy-bound approval, action receipt, verification, recovery | `cayu guide durable-operations` |
| Rebuild service-backed tools after a restart | component behavior identities, injected or environment-bound knowledge | `cayu guide durable-service-tools` |
| Authority or a human decision | `ToolPolicy`, approvals, user-input checkpoints | `cayu guide references#approvals` |
| Files or commands during a run | `Environment`, `Workspace`, `Runner` | `cayu guide references#environments` |
| Application workspace references or inventories | `WorkspaceReferenceBinding`, `ToolContext.require_workspace_binding` | `cayu guide authoring#workspace-references-and-inventories` |
| Durable uploads or generated files | `ArtifactStore`, artifact/workspace bridges | `cayu guide references#artifacts` |
| Secrets or restricted network access | vaults, virtual credentials, egress policies | `cayu guide references#secrets-egress` |
| Tools exposed over MCP | MCP adapters and manifest policy | `cayu guide references#mcp` |
| Conversation history that survives restarts | `SessionStore`, transcripts, checkpoints, resume | `cayu guide references#sessions` |
| Context approaching a model limit | token counting, context policies, compaction, overflow recovery | `cayu guide references#context` |
| Reviewed or retrievable knowledge | knowledge stores, review state, recall tools | `cayu guide references#knowledge` |
| Durable background work | `TaskStore`, dispatcher, worker, event watcher | `cayu guide references#background-work` |
| A task done only when an independent check accepts it | `WorkContract`, `VerifiedTaskWorker`, completion verifier, result resolver | `cayu guide verified-work` |
| Deterministic orchestration | workflow helpers and runtime hooks | `cayu guide references#workflows-hooks` |
| Delegated model work | subagent tools and child-session policy | `cayu guide references#subagents` |
| Behavioral regression proof | `EvalSuite`, runtime assertions, replay | `cayu guide references#evals` |
| Usage limits or cost control | usage events, run limits, budgets, pricing | `cayu guide references#cost-control` |
| Developer and operator inspection | `cayu inspect`, `cayu check`, console, dashboard, tracing | `cayu guide references#observability` |
| An HTTP control plane | `cayu[server]`, authenticated FastAPI application | `cayu guide references#server` |
| A public or multi-user agent product | `cayu new NAME --preset service`, maintained tenant-safe service factory | `cayu guide references#server` |
| A maintained repository-coding starter | `cayu new NAME --preset coding`, explicit workspace, knowledge, reviewer, and input composition | `cayu guide authoring#coding-composition` |
| A product API around repository coding | application-owned authentication, durable intake, workers and independent acceptance | `cayu guide authoring#coding-product-host` |
| Advanced authority, isolation, caching, or speculation | composed runtime strategies with explicit evidence boundaries | `cayu guide references#advanced-runtime` |
This map is a menu, not a checklist. A conversational, classification,
generation, or research agent does not automatically need a tool, workflow,
task queue, environment, approval step, knowledge store, deployed application
server, or multi-agent topology. The package-shipped `cayu guide references`
topic is the offline index for optional capabilities.
The local developer/operator control plane is a standard inspection surface,
not part of the agent's domain behavior. Fresh generated projects install the
server extra through their `dev` extra; run `uv run cayu serve --dev` and open
`http://127.0.0.1:8000/cayu/`. The explicit `--dev` flag is for trusted local
use. A deployed control plane still requires configured authentication, and an
application that already owns FastAPI embeds Cayu explicitly with
`mount_cayu(..., path="/cayu")`. Never use `OpenAccess()` on a public listener.
Client-IP and forwarded-header checks are not authentication; public or deployed
mounts require `AuthenticatedAccess(...)` with application-owned authorization.
## Package ownership
Use the concept package when exploring a capability: `cayu.sessions` owns
session requests and stores, `cayu.tools` owns tool contracts and policies,
`cayu.context` owns context management, and `cayu.approvals` owns approvals and
user input. Other owners include `cayu.tasks`, `cayu.workflows`, `cayu.memory.base`,
`cayu.knowledge`, `cayu.budgets`, `cayu.snapshots`, and `cayu.delivery`.
```python
from cayu.applications import CayuApp
from cayu.agents import AgentSpec
from cayu.messages import Message
from cayu.sessions import RunRequest
from cayu.tools import Tool, ToolSpec
```
Root imports such as `from cayu import CayuApp` remain supported. Package
`_exports.py` files identify the defining modules; matching `__init__.pyi` files
provide static declarations. `cayu.runtime` coordinates execution. The prerelease
migration removes legacy module aliases; use current concept paths for new
imports and generated code. Same-build recovery remains supported; old import
paths and cross-build execution-profile continuity are not compatibility promises.
The [complete source ownership and migration map](https://github.com/cayu-dev/cayu/blob/v0.9.1/docs/public-concepts.md)
includes specialized modules and retained root-level concepts. This package's
installed source and export manifests are authoritative for its version.
## Coding composition
For a repository-coding application that needs the same explicit capabilities
on day one, use `cayu new NAME --preset coding`. The opt-in project assembles
existing public tools and stores in the canonical `tools/`, `policies/`,
`environments/`, `operations/`, `knowledge/`, `prompts/`, and `agents/` modules:
bounded file and `rg` search operations, Git change review, local artifacts,
reviewed durable knowledge, a bounded background reviewer with result recovery,
and human input. Root `composition.py` remains only a compatibility import.
It requires `git` and `rg`, creates a clean initial Git commit, and ships one
credential-free smoke for the complete composition. Its trusted-host local
workspace and runner are not a sandbox. Keep the default scaffold for jobs that
do not need these capabilities, and do not combine the coding composition with
the multi-user service template.
This is a restriction on combining generated templates, not a prohibition on
hosting a coding application behind an application-owned API. For that advanced
composition, use `cayu guide authoring#coding-product-host`; it does not inherit
the service template's authentication or verification just by adding routes.
Selecting this composition chooses implementations; it does not grant authority.
Its exposure policy separately controls model-visible tools, and its ordinary
tool policy, approval policy, and runtime gates independently authorize calls.
For a public or multi-user product, start with
`cayu new NAME --preset service`. That template keeps customer authentication,
tenant-qualified application storage, public identifiers, allow-listed product
responses, and the operator-only `/cayu/` mount in one maintained service
factory. Its deployment check proves only the declarative factory posture; the
generated assembled-ASGI security suite proves the product behavior. Arbitrary
host-owned routes remain outside Cayu's verification boundary.
Online, the repository's
[examples index](https://github.com/cayu-dev/cayu/blob/v0.9.1/examples/README.md)
is a secondary catalog of runnable source examples.
A tool-backed slice is optional. Add one only when the agent needs a real
capability outside the model. Prefer a narrow domain tool; when command
execution is necessary, use an explicit runner and enforcing command/tool
policy.
## Coding product host
Start with one generated coding project and retain its actual scaffold metadata.
Do not overlay a second generated service project, relabel its preset, or expose
the operator control plane as a customer API. Build the product host explicitly
using public Cayu APIs and the following existing owners:
| Boundary | Application home and implementation responsibility |
| --- | --- |
| Construction | `app.py` constructs and registers the same application graph in each process; it does not start workers, migrate stores or perform model calls. |
| Durable state | `configuration/` constructs shared session/task/artifact backends. Application-owned business reservations need their own durable transaction owner; a hash of an intake request is not an atomic reservation. |
| Spending authority | `policies/` configures matching budget scopes, explicit pricing and conservative reservations against a shared durable ledger. A per-operation causal limit is not a deployment-wide total cap; verify the actual run identity and refusal before provider dispatch with `cayu guide references#cost-control`. |
| Product intake | `operations/` authenticates the customer, obtains tenant/subject from that authentication, captures the admitted source and configuration, and reserves stable public/product/session/task identities under an idempotency key. Resolve reads by authenticated tenant and public ID; return an allow-listed product projection, not raw Runtime records. |
| Task handoff | After reserving identity, use `CayuApp.create_task(TaskCreate(...))`. Reservation and task insertion can commit separately: retry the same reservation and task identity, read back an ambiguous insertion, and reject conflicting contents rather than allocating replacement work. |
| Worker lifetime | A separate `operations/` role uses `run_task_worker` with `TaskQuery` for its phase. Reconstruct the original reservation from the store-owned claim and validate its current ownership before executing. Use `complete_managed_task` after independent acceptance; do not create another lease or retry engine. |
| Coding result | `workflows/` coordinates the maintained coding product; `domain/` checks the admitted revision, permitted changes, required checks and independent verifier against retained artifact evidence. `CodingProductArtifactRepository` provides request/publication readback. Model text, successful project tests or task completion alone are not independent acceptance. |
| Operator host | Mount the control plane with `cayu.server.mount_cayu` and explicit `AuthenticatedAccess`. Protect application operator routes separately; customer authentication neither grants operator access nor authorizes another tenant's resources. |
| Delivery | `integrations/` owns separately approved Git/GitHub handoffs. Keep patch readiness, exact reviewed change/destination approval, publication, hosted checks and final delivery distinct. No approval is implied by producing a patch. |
First test the assembled HTTP application with injected providers and stores:
authentication refusal, cross-tenant lookup, duplicate intake, conflicting retry,
independent acceptance and truthful pending/failed responses. Then exercise the
same application with its installed distribution and persistent deployment.
Keep source, Runtime/artifact state and delivery state separate; credentials
stay outside the admitted coding guest. Prepare schemas explicitly and run
homogeneous application versions with coordinated backup and restart.
Check the actual factory before building the deployment image: run `cayu check`
from the generated project with its intended configuration. Keep environment
reads, collection-building calls and other construction inside the synchronous
factory or runtime functions, not module import expressions. An importable module
can still violate the scaffold's import-side-effect contract. Preserve the
generated enforcing policies where possible; a custom policy returning "allow"
does not establish inspectable external-tool coverage. Resolve
`cayu guide diagnostics#external-tool-coverage-unknown` rather than suppressing
that error or treating HTTP startup as a passing deployment check.
For narrower coding authority, extend the generated concrete
`ParameterConstrainedToolPolicy` rules in `policies/` and wire that policy at
the existing construction seam. Preserve the existing path and selector rules;
append a `RequiredAllowlistRule("path", values=[...])` to restrict writes to
specific paths. Do not replace those rules with a custom delegating `ToolPolicy`
or subclass merely to add a restriction: inspection cannot prove that arbitrary
Python calls its underlying enforcement. An `execution_profile_identity` binds
behavior for reconstruction; it does not certify static policy coverage.
Give the changed policy its own explicit, versioned behavior identity and
advance that identity when its enforcement changes; do not reuse the original
generated policy's identity for different behavior.
The maintained `StructuredCommandToolPolicy` preserves known base coverage,
but cannot make an unknown custom base statically trusted. Run `cayu check`
again after composition changes, before deployment or worker dispatch.
### Queue reserved work
A business reservation may choose a future session ID, but that is not a native
task attachment. Leave `TaskCreate.session_id` unset when enqueueing ordinary
work: `run_task_worker` claims only unattached pending tasks. Setting that field
early makes the task ineligible even if the named session does not exist. Keep
the intended session ID in the durable business reservation; the claimed worker
resolves it there, and Runtime admission owns any native task/session attachment.
Never clear an existing attachment merely to make a task claimable.
For example, after authenticating and resolving the original durable reservation:
```python
from cayu import TaskCreate
async def enqueue_reserved_coding_task(app, reservation):
return await app.create_task(
TaskCreate(
task_id=reservation.task_id,
type="coding_product",
assigned_agent_name="coding",
input={"public_id": reservation.public_id},
)
)
```
The worker must load that same reservation and validate its task, tenant, source
and execution authority before using its intended session ID. On ambiguous task
insertion, read back and compare the original task instead of choosing new IDs.
For a detached coding product, a worker handler can finish through this existing
managed boundary. Here `verify_product_for_claim` is an application-owned function
that must resolve the original reservation, run or recover the maintained product,
and independently validate its exact artifact and cleanup evidence. It is not an
agent-text parser or a substitute for an independent verifier:
```python
from cayu import complete_managed_task
async def handle_coding_task(app, claimed, worker_id):
verified = await verify_product_for_claim(app, claimed)
await complete_managed_task(
app.task_store,
claimed,
worker_id,
{
"product_run_id": verified.product_run_id,
"result_digest": verified.result_digest,
},
)
```
`run_task_worker` owns the heartbeat while this handler executes.
`complete_managed_task` uses its latest acknowledged lease and returns a durable
`Task`; it does not create a caller-keyed `TaskTerminalizationReceipt`. Do not
load a lease and construct a raw `TaskTerminalizationRequest` alongside that
heartbeat merely to obtain such a receipt. Extending the lease duration does not
remove the race. Use the original claimed task with the managed helper.
If completion's reply is lost, read the original task through `TaskStore.load_task`
and validate its type, session, completed status and exact result against the
saved reservation and independently verified product artifact. That durable
task/result is normal-completion evidence; it need not have a separately keyed
terminal receipt. Do not rerun coding, manufacture a receipt, or complete a still
claimed task from a read-only screen. Cancellation reconciliation uses a different
explicit receipt, as described below and in `cayu guide references#background-work`.
Run schema creation and validation with the same pinned Python runtime as the
API and workers, preferably from their deployment image. Matching Cayu versions
alone is insufficient: durable transcript indexes also bind Python's Unicode
tokenizer identity. A host-created database can therefore reject a container
using a different Python runtime. Preserve an incompatible database and its
evidence; do not rewrite its tokenizer metadata or erase accounting to pass
startup. Choose the matching runtime or separately initialize a new disposable
database with the intended runtime before a fresh trial.
For worker loss, use registered recovery through `cayu recovery plan` and
`cayu recovery execute` with the original application and admitted workspace.
A dead worker, expired lease or cancelled await does not prove an external
effect stopped. Require authoritative effect and invocation-release evidence
before reconciling the outer task. Unknown effects remain fenced; cleanup-only
recovery must not redispatch the model. Prove native resource disposal or a
supported retained recovery owner separately from session/task terminal status.
Do not label cancellation settlement as a verified repair.
Observe and reconcile the retained native allocation and command journal before
destructive disposal. Removing a guest first can destroy the only reconnectable
workspace or terminal-command receipt needed by registered recovery. Use the
registered resource owner and its allowed disposition; an unrelated Docker
cleanup script cannot substitute for native recovery settlement.
Recovery must retain the original invocation controls, not just the same agent
and workspace. In `ToolRoundRecoveryRequest`, omitted continuation controls use
the recorded invocation. Changing `max_steps` from eight to one still changes
the execution profile; it is not an observation-only switch. Ordinary manual
tool-round recovery may continue the model after settling the round. If execution
must not continue, select a supported interruption or failure disposition whose
owner can settle the retained effect and resources; do not invent tool success,
delete its recovery evidence, or relax profile validation. See
`cayu guide tool-effects#native-durable-recovery-evidence`.
Keep application-owned recovery diagnostics provisional rather than storing
`recovery_required` as an immutable final result. The application needs an exact
transaction that consumes the validated native terminal task/result or explicit
cancellation-reconciliation receipt, settles the original business reservation,
and releases its source fence only after effect settlement and required resource cleanup are positively
verified. Unresolved work retains its fence and a supported recovery owner.
Preserve prior diagnostics and reject changed task/session/source or evidence.
When consuming an explicit receipt, make replay of the same receipt idempotent.
Native session recovery alone does not perform this application transaction;
do not replace it with a scenario-specific script
that edits business rows or assumes process death proves quiescence.
This composition remains application-owned. A successful `cayu check` or a
declared extension is structural evidence, not proof of its authentication,
acceptance, deployment or recovery behavior. See `cayu guide references#server`,
`cayu guide references#background-work`, and `cayu guide tool-effects` for the
reusable boundaries; preserve the generated convention rather than adding a
parallel orchestration framework.
## coding-host-settlement-example
The installed example `cayu.guides.coding_host` extends the generated coding
application rather than implementing another recovery engine. Its SQLite
`BusinessStore` is application state, separate from the Runtime task/session
stores. It is a single-tenant, bounded local-host recipe, not a production
multi-host database adapter or an HTTP authentication layer.
From a generated coding project, wrap its normal factory result before starting
the API or task worker. This deployment must already have its reviewed priced
budget policy configured:
```python
from pathlib import Path
from app import build_coding_product_application
from cayu.guides.coding_host import BusinessStore, extend_generated_application
business = BusinessStore(Path(".cayu/coding-business.sqlite"))
configured = build_coding_product_application()
pricing = configured.app.budget_policy.limits[0].pricing
product = extend_generated_application(
configured, business=business, tenant="disposable-example",
pricing=pricing, cost_basis="observed",
)
```
The extension uses the generated, project-owned preparation method to reserve the
source before execution. Recovery checks the existing reservation and never
synthesizes missing intake provenance. Retain the complete original
`CodingProductTask`, including its instruction and controls, in the existing
authenticated intake/task payload; a native request fingerprint cannot reconstruct
that input. Keep the original product/task/session IDs.
The managed handler still owns ordinary task completion: record exactly
`{"product_run_id": publication.candidate.product_run_id,
"result_digest": publication.result_reference.digest}` using
`complete_managed_task`. Then call `product.settle_completed(task,
result_digest=..., pricing=..., cost_basis="observed")`. The same method handles
completion acknowledgement loss after reading the exact completed task; it never
reruns the handler. Patch readiness is not independent application acceptance,
human approval, or successful external delivery.
For cancelled work, first execute supported registered native recovery. The
example's `product.settle_cancelled` requires an authenticated `ResolutionActor`,
the original task, a stable reconciliation ID, pricing and an explicit cost basis.
Its default worker observer uses the existing maintained Docker-host owner:
run the trusted `maintenance.coding` worker with `maintenance_worker_id` from
`cayu.guides.coding_host_owner` and `CAYU_MAINTENANCE_WORKER_OWNER=docker`.
Pass `await maintenance_worker_id("maintenance.coding")` as the native
`run_task_worker` worker ID. This observer's supported container command is
`cayu worker coding --shutdown-grace-seconds 30`; its trusted service image needs
`/usr/local/bin/docker` and authorized access to the local Docker socket. A
different supervisor needs its own reviewed generation observer, not a boolean
or an inference from PID absence.
It inspects the exact container lifetime on the local daemon; no missing-container
or lease-expiry inference substitutes for a stopped generation. The native
inspector must independently prove invocation release, and the maintained
file/check effect validator rejects unknown/custom effects. This recipe neither
disposes guests nor authorizes recovery actions that native planning refuses.
Keep the source fenced when any evidence is unavailable.
This example settles patch-ready completion and owner-lost cancellation, not
arbitrary failed tasks or unsupported custom effects. Those remain explicitly
reserved for the application's operator/recovery owner; do not reset the task or
use the internal transaction method as a substitute for positive evidence.
The complete native reconciliation request is saved before its store call.
Retries reuse its actor, evidence, timestamps and idempotency key. The application
then records the native receipt and result in the same transaction that releases
its source reservation. A lost reply at either boundary is reconciled without a
new task or model invocation. Do not expose `BusinessStore.prepare/settle` directly
as HTTP operations: they are internal application transaction primitives.
Both terminal paths use `project_result`. Select `synthetic` only in explicitly
controlled fixtures; missing usage never implies free work. Cost is a recorded
estimate under the identified price book, not complete billing. Historical reads
use `business.read(reservation)` and remain valid after later source changes.
Before a **new delivery**, use `require_current_source` under the application's
exclusive source/delivery owner: it compares a bounded byte-level observation to
the authenticated retained result, not just Git status. Keep that owner and the
delivery's own exact approval checks through publication. This observation alone
does not authorize any Git or GitHub action.
The reservation pins the complete price book and cost basis. Reconciliation
rejects a changed pricing/basis configuration rather than relabeling a synthetic
trial as observed provider spending after restart.
## 2. Use the project factory
A Cayu project declares a synchronous factory in `pyproject.toml`:
```toml
[tool.cayu]
factory = "app:build_app"
eval_target = "evals.agent:build_eval"
```
Calling the factory constructs a fresh, process-scoped `CayuApp`. The app is
not a global registry or cross-process singleton. Durable stores coordinate
state between scripts, consoles, servers, workers, and tests. Importing project
modules must not construct the app, connect to services, migrate storage,
start workers/recovery/schedulers, or invoke a model or tool.
The factory may expose optional dependency-injection arguments for tests as
long as a normal zero-argument call remains valid. Tests should inject
`ScriptedModelProvider` and in-memory stores through those public seams.
The separate `eval_target` returns an eval plan and lets `cayu eval run` use the
project's default suite without treating the application factory as an eval.
## 3. Inspect before changing
Run from the project root or any nested directory:
```bash
cayu inspect --json
cayu check --json
```
`inspect` builds the configured factory once and returns the versioned,
redacted application manifest. It describes configuration and static
resolution; it does not invoke a provider, tool, environment factory, worker,
watcher, session, or recovery path. Capability fields distinguish declared and
resolved configuration from process availability and live verification. Agent
entries expose `has_system_prompt` so prompt presence is inspectable, but the
manifest deliberately omits prompt content and implementation bodies. The
application fingerprint is structural: prompt absence versus presence changes
it, while editing one non-empty prompt into another does not. Prove prompt
behavior through runtime tests and evals rather than treating the fingerprint
as a content digest.
Read-only describes Cayu's inspection phase, not arbitrary code inside the
project-owned factory: `inspect` must call that factory, so keep factory boot
effects limited to constructing the application graph.
After factory construction, `check` evaluates only that manifest. It performs
no live probes and does not mutate the application, stores, or source tree.
Exit status `0` means no finding at the selected threshold, `1` means findings,
and `2` means discovery, import, factory, or invocation failure. Each finding
gives a stable code, machine path, observed parameters, correction, and
verification command.
## 4. Add a tool-backed slice only when needed
Do not generate a tool for the first model-only agent unless the requested job
actually needs a capability outside the model. When it does, attach the first
tool tracer bullet to the existing starter:
```bash
cayu generate tool assess_submission --agent reviewer --effect none --dry-run --json
cayu generate tool assess_submission --agent reviewer --effect none
```
For an additional agent with its own tool-backed slice, plan before applying:
```bash
cayu generate slice reviewer --tool assess_submission --effect none --dry-run --json
cayu generate slice reviewer --tool assess_submission --effect none
```
The planner does not import the app or write files. It reports exact proposed
contents and verification commands. Apply mode creates independent files and
changes only the delimited machine-owned import/registration regions in
`app.py`. Missing anchors, conflicting files, or different user content fail
without partial writes. Repeating a successful invocation is a no-op.
Generated tools declare `execution_profile_identity=ExecutionProfileBehaviorIdentity(...)`
in their class-level `ToolSpec`. Keep that declaration on every custom tool you
write, including read-only ones: sessions record the tool set's identities, so a
tool without one makes a paused or resumed session fail with
`ExecutionProfileMismatchError` (changed in `tool_implementations`) as soon as
tools change. Bump `behavior_version` when a tool's behavior changes. Cayu does
not invent a default identity, because a guessed identity would let changed
behavior resume a session recorded with the old one.
Generated code is a tracer bullet, not finished domain behavior. A generated
slice carries an explicit `AgentAuthoringState.UNFINISHED_GENERATED_TRACER_BULLET`
marker, while the scaffold's first-tool flow sets `_AUTHORING_STATE` to
`"unfinished_generated_tracer_bullet"`. In either case,
`cayu check --fail-on warning --json` rejects the agent as a completed
submission while its structural inspection, runtime test, and eval remain
runnable.
Replace the domain system prompt, tool schema and body, runtime test inputs and
assertions, and trajectory eval behavior and assertions. Only then clear the
marker. For `cayu generate tool`, change `_AUTHORING_STATE` to `None` in the
generated agent-config region. For `cayu generate slice`, remove the
`authoring_state` argument and unused `AgentAuthoringState` import from the
generated agent module. Cayu deliberately trusts that explicit state instead
of parsing arbitrary Python, prompts, or test source; clearing it is an author
claim, not runtime-verified proof of domain correctness.
Generated slices define each tool name once in the tool module and reuse that
constant in the `ToolSpec`, agent instructions, `workflow_tool_names`, runtime
test, and eval. Preserve that single source when renaming a tool. For
hand-authored agents, declare every exact tool name that machine-owned workflow
instructions expect through `AgentSpec.workflow_tool_names`; do not maintain an
independent list in prose or tests.
For a complete credential-free composition of durable work, per-session local
environments, guarded file/command tools, failure recovery, and
application-owned verification, see `examples/durable_file_workflow`. Its
`verified.py` runs the same job as a contract-bound task with an independent
verifier and bounded attempts; see `cayu guide verified-work`. “File worker”
describes that example's job; it is not an `AgentSpec` profile or a new runtime
concept.
## 5. Treat effects as a security contract
Every tool declares one effect:
- `none`: no externally meaningful durable mutation;
- `idempotent`: may mutate durable state, but a stable downstream idempotency
key or equivalent contract collapses replay;
- `external`: non-idempotent or outcome-ambiguous mutation that generic retry
must not assume is safe to repeat.
Use `cayu guide tool-effects` for the canonical decision table. Transport,
billing, observability, and a name such as "read" do not determine the effect.
Declaration is replay metadata, not authorization. External tools require an
enforcing `ToolPolicy`; use an approval policy when a person must decide.
Comments, prompts, UI confirmation, allowlists that are not consulted by the
runtime, and tests that bypass policy are not enforcement.
Define what happens when an effect starts but completion cannot be proven.
Never blindly retry an ambiguous external effect. Persist enough identity and
checkpoint state for an operator or recovery path to reconcile it.
### Report expected failures as failed results
When a tool fails in a way you expected, such as a record not found, input
rejected, or the service returning an error response, return
`ToolResult(content="what went wrong", is_error=True)`. The model sees the
failure and can retry, ask, or explain it, and Cayu records a failed call. A
failure returned as ordinary content, for example `{"error": ...}` with
`is_error` left false, looks like a success to Cayu and to loop policies.
Raising behaves differently depending on the declared effect. From a `none` or
`idempotent` tool, the exception also reaches the model as a failed result.
From an `external` tool, which is the default when no effect is declared, it
means the effect's outcome is unknown: Cayu interrupts the session for
reconciliation instead of continuing. Raise only when that is true.
### End each turn through a known tool
If every turn must end in a particular tool, such as a clarification question
or a recorded proposal, pass `RequireFinalTool([...])` in `loop_policies` on
each run and resume. When the model stops before one of those tools has
returned without error, it gets a reminder instead of the turn silently
completing with nothing to show. See `cayu guide order-support`.
### Keep model-controlled command selectors as data
Model-controlled command selectors are untrusted argv input. A value described
as a path, target, test node ID, or filter can still become a new option when
application code appends it to an otherwise fixed command. For example,
`--help` can exit zero without running a check, while an output option can write
outside the workspace. A zero exit status alone is therefore not proof that the
intended check ran.
An executable allowlist does not authorize its argument protocol. For every
allowed command, define and validate the exact selector grammar owned by the
application. A file-selector recipe should:
1. reject empty values, NULs, leading-option forms, absolute paths, traversal,
platform separators outside the supported grammar, and unsupported syntax;
2. parse compound forms such as a test path plus node IDs before validating
each component;
3. resolve the path against the authorized workspace and prove containment;
4. construct argv as a sequence with no shell interpolation; and
5. insert `--` only when the target executable's documented, tested semantics
treat it as an end-of-options delimiter at that exact position.
For a tool that supports only Python test files and simple pytest node IDs, an
application-owned validator can look like this:
```python
from pathlib import Path, PurePosixPath
_NODE_ID = re.compile(r"[A-Za-z0-9_.\[\]-]+")
def pytest_selector(raw: str, *, workspace: Path) -> str:
if not raw or "\0" in raw or raw.startswith("-") or "\\" in raw:
raise ValueError("unsupported test selector")
path_text, *node_ids = raw.split("::")
raw_path_parts = path_text.split("/")
path = PurePosixPath(path_text)
if (
path.is_absolute()
or any(part in {"", ".", ".."} or part.startswith("-") for part in raw_path_parts)
or path.parts[0] != "tests"
or path.suffix != ".py"
or any(not _NODE_ID.fullmatch(node_id) for node_id in node_ids)
):
raise ValueError("unsupported test selector")
root = workspace.resolve()
candidate = (root / Path(*path.parts)).resolve()
candidate.relative_to(root)
if not candidate.is_file():
raise ValueError("unsupported test selector")
return raw
```
The fixed prefix and validated selectors can then be passed directly to the
runner without a shell. This example deliberately makes no claim that adding
`--` is valid for every pytest invocation; confirm the installed executable's
contract before doing so. Filters passed as values to an application-owned
option need their own closed grammar rather than reuse of the path validator.
Classify process outcomes using that executable's contract. Keep rejection,
unavailable or non-executable command, timeout, check failure, and zero tests
executed distinct from verified success. Preserve a structured cannot-run reason
such as not found, permission denied, invalid executable format, or another OS
launch error. Report whether the check used full discovery or an intentionally
selected subset, including the exact validated selectors; a passing selected
check verifies only that subset.
Inventory writes made by representative success and adversarial runs, including
caches and reports, and compare those observed effects with the tool's declared
`ToolEffect`. Keep the process outcome and effect comparison orthogonal: a
failed check that also writes remains a failed check with a separate effect
mismatch. These controls do not replace container or microVM isolation when the
command executes untrusted repository code.
## 6. Put state in the right place
- Transcript/session state belongs in a `SessionStore`.
- Durable work ownership and results belong in a `TaskStore`.
- Curated/retrievable knowledge belongs in a knowledge store; project skills
and instructions remain human-readable files.
- Mutable working files belong in a `Workspace`.
- Stable uploads and outputs belong in an `ArtifactStore`.
- Commands run through a `Runner`; a local runner is not a sandbox.
- Secrets come from an explicit vault/provider configuration and must not enter
manifests, diagnostics, events, prompts, generated plans, or repository files.
Use an environment only when tools need these execution capabilities. Bind and
finalize workspaces explicitly. When recovery or reconnect matters, verify the
same identity and naming contract used by the original run.
Treat workspace IDs as opaque identities owned by the selected workspace.
Never reconstruct an ID from an environment name, filesystem path, hash, or
another environment builder's naming convention. For an explicitly registered
static environment, obtain it from
`app.get_environment(name).environment.workspace.id` after checking that the
workspace exists. Inside tools, use `ToolContext.workspace_id` and the active
`ToolContext.workspace`; factory-created or bound workspaces may not exist until
the session has materialized them. An environment name selects execution
capabilities; it is not the workspace's identity.
Bind application-owned file inventories and receipts to that actual workspace
identity and validate it before accepting their authority. Runtime cannot infer
the semantics of arbitrary application metadata: a string named `workspace_id`
inside that metadata does not establish a Runtime-validated binding. Fail with
an explicit identity-mismatch diagnostic instead of silently treating every
declared file as unavailable. Prove the contract through a native run with an
independently assigned workspace ID, including a different environment builder
or evaluation wrapper when the application supports one.
Environment selection is opt-in. Pass `default=True` to
`register_environment(...)` or `register_environment_factory(...)` only when
unnamed `RunRequest`s should use that environment. Otherwise leave it
non-default and set `RunRequest.environment_name` explicitly. Registering the
first environment never makes it the default implicitly.
### Continue after a process crash
Use `app.resume(ResumeRequest(...))` for the customer's next message on an
existing session. With native stores, continuation recovers a `running` or
`interrupting` session when its recorded executor is provably gone on this
host, or its execution lease has expired. It records the takeover as
`session.run_fenced` before continuing. Approval, user-input and provider-operation
resolution use the same admission check before their existing recovery protocol.
`SessionExecutionInProgress` means the executor is live or abandonment cannot be
established. Show that the conversation is still in progress and retry later;
do not turn this exception into an unconditional recovery call. Liveness uses
the configured lease (60 seconds by default), independently renewed during
silent model/tool calls; a process proved dead on this host ends it early.
Inactivity alone does not establish abandonment. A process that is alive but
cannot renew its lease, for example because it lost the store or blocks its
event loop longer than the lease, loses ownership and is fenced, so keep
blocking work off the event loop.
Sessions created by older writers without an execution owner need explicit
operator recovery. First stop or otherwise prove the old executor has stopped,
then call
`app.recover_incomplete_session(IncompleteSessionRecoveryRequest(session_id=...))`
and inspect its result before continuing. Keep the original executable profile
available for recovery; a changed tool/provider identity still requires explicit
profile adoption.
Recovery does not rerun a tool whose call was interrupted, and it cannot show
the model that call's original arguments. For `NONE` and `IDEMPOTENT` tools, the
model receives a failed result saying the outcome is unknown and that calling the
tool again for the same operation is safe, and the conversation continues. An
idempotent tool should therefore recognize a repeated request for an operation it
already started, by business identity rather than exact arguments, and return the
original outcome, for example by reusing the operation key it saved before the
first attempt. An uncertain `EXTERNAL` effect emits
`tool.effect.outcome_unknown` and interrupts for reconciliation. Use the service's receipt or idempotency key to
establish the outcome; never infer that the write failed because its process died.
## 7. Prove behavior through public seams
The default credential-free proof is:
```bash
cayu inspect --json
cayu check --json
pytest
cayu eval run
```
Tests should exercise `CayuApp.run` or `run_to_completion` with a
`ScriptedModelProvider`, not private methods or a fake replacement runtime.
Trajectory evals should assert domain behavior plus important runtime events:
tool calls, approval interruption, artifacts, child sessions, usage, or final
state as appropriate.
Run `cayu check --json` to compare each agent's declared
`workflow_tool_names` with the tools registered for that same agent in the
public manifest. Unknown, stale, renamed, removed, or agent-mismatched names are
authoring errors. This check reads the explicit workflow contract; it does not
guess tool references from arbitrary natural-language prompt text.
`ScriptedModelProvider` can prove runtime handling of predetermined calls, but
it cannot prove prompt comprehension, model tool choice, or live-provider
behavior. A scripted test can also pass while the real model sees nothing
useful: a custom tool's result reaches the model only through its `content`,
never its `structured` data. Assert on what the model reads with
`model_facing_text(provider.requests[i])` (one line per text part, peer
content, tool call and tool result `content`) or
`model_facing_tool_result(result)`:
```python
from cayu import model_facing_text
seen = model_facing_text(provider.requests[1])
assert "tool audit_csv: 3 blank rows in data.csv" in seen
```
Keep manifest-backed prompt/tool alignment evidence separate from a
scripted trajectory. Optional live-provider evidence may exercise comprehension
and tool choice, but remains credential-gated and is not required for hermetic
CI.
Report evidence in four separate layers:
1. static inspection and structured checks;
2. hermetic runtime tests and evals;
3. real process-boundary checks using the built wheel;
4. optional credential/infrastructure-gated live checks.
State exactly which commands ran and which were skipped. Successful imports,
construction, mocks, scripted providers, or a local runner do not prove live
provider access, sandbox isolation, network egress, or deployment readiness.
Before deploying a tool declared `NONE`, use the bounded workspace check in
`cayu guide tool-effects` when workspace mutation is part of its risk boundary.
The check is explicit and behavioral; `cayu check` never executes tools. Treat
an unchanged workspace as scoped evidence only, and use domain-specific tests
for databases, external services, idempotency, and effects outside that first
supported observer.
## 8. Shape-specific reminders
- **Conversation:** preserve transcript/recovery semantics; omit tasks and
environments unless behavior needs them.
- **Research/documents:** make source inputs and artifact outputs explicit;
eval citations and document decisions, not only final prose.
- **Coding/repositories:** use a repository binding, isolated workspace/runner,
narrow command policy, and human confirmation before commit/push/PR actions.
- **Operations:** start with `cayu guide durable-operations`; model stable action
identity, idempotency, ambiguity, approvals, verification, and restart recovery
before adding autonomous effects.
- **Durable workflows:** keep deterministic orchestration in application code
and durable state; use Cayu tasks and workflow helpers where needed. Use
model steps only where judgment is required. Use `WorkflowBase.execute(...,
execution_deadline=ExecutionDeadline.after(seconds))` for a Runtime finish-by boundary.
Inspect `await ctx.remaining_seconds()` to reserve application-selected finalization
time; children and resumed executions retain the original effective expiry. See the
[execution deadline contract](https://github.com/cayu-dev/cayu/blob/v0.9.1/docs/runtime-contracts.md#execution-deadlines).
- **Multi-agent:** justify each role, bound delegation, persist lineage, and eval
both child behavior and parent synthesis.
Finish by rerunning inspection, checks, focused tests, the relevant eval, and
any explicitly available process/live checks. Report limitations rather than
substituting weaker evidence.
## Workspace references and inventories
Use `WorkspaceReferenceBinding` for application-owned references, inventories, or
receipts. Obtain it from the actual registered workspace with
`workspace.reference_binding()`, or from the admitted workspace inside a tool
with `ctx.workspace_reference_binding()`. Do not reconstruct an ID from an
environment name, path, or naming convention.
```python
from pydantic import BaseModel
from cayu import WorkspaceReferenceBinding
class Inventory(BaseModel):
binding: WorkspaceReferenceBinding
paths: tuple[str, ...]
inventory = Inventory(binding=workspace.reference_binding(), paths=("note.txt",))
# Store alongside your application data, including in durable JSON metadata.
serialized = inventory.model_dump_json()
restored = Inventory.model_validate_json(serialized)
# Inside Tool.run(ctx, args), before using paths or inventory claims:
ctx.require_workspace_binding(restored.binding)
```
`require_workspace_binding` checks the workspace actually admitted for that tool
invocation, after factory materialization and workspace binding. A sync binding
can select a target different from the registered source: create the inventory
from that target or from the active context. Source ownership does not transfer
merely because files were copied. Evaluation wrappers and alternate execution
adapters use the same API on their actual workspace objects.
A mismatch raises `WorkspaceReferenceBindingError` with
`code == "workspace_binding_mismatch"`. A context without a live Runtime-bound
workspace raises `workspace_binding_unavailable`; setting `ctx.workspace_id` or
putting an ID in metadata cannot supply it. Catch the diagnostic and reject or
rebuild the reference before consuming its claims. A deserialized `ToolContext`
cannot validate a reference; validate in a new admitted invocation.
The versioned JSON binding composes `WorkspaceIdentity` (ID and observing adapter)
with an incarnation `generation`. It is distinct from `WorkspaceBinding`, which
connects a workspace and runner, and from file or workspace revision observations.
The default generation is scoped to the workspace object's lifetime. Reopening
application JSON preserves the original binding exactly; it does **not** rebind
it. Reconstructing even the same adapter at the same path with the same ID yields
a mismatch. Rebuild the inventory from the newly selected workspace after
recovery when using the default implementation.
A durable workspace adapter may override `Workspace.reference_binding()` to
return an adapter-owned binding that survives reconnect. It must independently
persist and verify the workspace incarnation, preserve all binding fields for
that incarnation, rotate the generation on replacement/reset, and fail explicitly
when continuity cannot be established. Never recover a generation from the
inventory being checked or derive it from names, paths, or file contents. An
adapter wrapper may delegate to its underlying workspace only when it represents
the same incarnation. Runtime does not infer such equivalence. Built-in adapters
currently use the conservative object-lifetime default.
This is an ownership assertion, not authentication of application data. It does
not establish file existence, content integrity, freshness, read permission, or
verified claims; an inventory binding neither certifies its claims nor grants
access. Continue to use workspace reads, revision checks, policy enforcement, and
application verification for those separate questions. Treat serialized bindings
as application data subject to your normal integrity controls.
Run `python examples/workspace_reference_binding.py` for a synthetic example
requiring no model credentials. It accepts the original inventory and reports
`workspace_binding_mismatch` for a replacement with the same ID, path, and bytes.
# Context and memory
Context is the model input for one invocation. Session history is the recorded sequence of work. Memory is an application-selected source of information that can outlive one invocation. A durable session does not automatically turn every past fact into useful memory.
## Choose the source of each fact
| Need | Owner | Example |
| --- | --- | --- |
| Current request | Run input | A synthetic invoice to analyze |
| Recorded work | Session store | Tool calls, interruptions and results |
| Stable instructions | Agent prompt | Which evidence counts as an answer |
| Domain facts | Knowledge or tools | The current product catalog |
| Reusable experience | Memory | A verified preference with provenance |
| Model input size | Context policy | Selected history and bounded recall |
Keep the catalog in a domain service rather than teaching the model that a previous lookup is permanently current. Store a preference only when your application has authority to retain it. Treat recalled text as evidence with a source, rather than an instruction that can authorize tools.
## Bound the model input
Choose a context policy for your agent. Context compaction changes the model-facing projection; it does not erase durable history. Preserve the evidence needed to resume pending work and understand earlier decisions. Test both a short interaction and a long session that reaches the configured threshold.
Prompt caching reuses provider work on matching input. It does not expand the context window or replace memory. A compaction strategy can affect cache reuse, so compare provider usage and runtime events rather than assuming that fewer visible messages always cost less.
## Add memory deliberately
Use the generated project's `memory/` and `knowledge/` seams. Define scope, eligibility, byte/item limits, freshness and provenance before adding recall. Make writes explicit and test rejected or outdated candidates as well as successful recall.
A useful regression case starts a second interaction, retrieves the retained fact, and proves that an unrelated session cannot gain authority from it. Regrading an old trajectory answers a different question from executing the changed agent again.
Read the [context API](https://cayu.dev/docs/reference/python/context/), [memory API](https://cayu.dev/docs/reference/python/memory/) and [worked learning path](https://cayu.dev/docs/learn/).
# Durable operations lifecycle
This guide ships with the Cayu package, so the installed version is always available offline with `cayu guide durable-operations`. [Release source](https://github.com/cayu-dev/cayu/blob/v0.9.1/src/cayu/guides/durable-operations.md).
For a complete installed-package support agent with restartable clarification and
external approval receipts, run `cayu guide order-support`.
Use this quickstart when an agent must observe a system, diagnose it, propose a
change, obtain authority, act, verify the result, and survive process restarts.
It is a paved composition of existing public Cayu APIs, not a second
orchestration engine. For individual contracts, keep using `cayu guide
references` and `cayu guide tool-effects`.
## Lifecycle map
| Phase | Durable/public Cayu seam | Application responsibility |
| --- | --- | --- |
| Observe and diagnose | ordinary model turns and `ToolEffect.NONE` tools | Return bounded evidence; distinguish unknown from healthy. |
| Propose | an application-owned durable proposal record, followed by a model tool call held by a proposal-binding `ToolPolicy` | Store bounded, non-secret review fields and put the same stable domain `action_id` in the proposed action. |
| Authorize | `PendingActionQuery` plus the application proposal record, then `ToolApprovalRequest` | Authenticate the operator, authorize them for the exact review fields, and resolve the durable approval/round/call IDs with typed provenance. |
| Act once | Cayu's durable tool-round ledger plus the tool's effect contract | Use `ToolContext.idempotency_key` or the stable domain `action_id` at the downstream system. |
| Verify | a separate read-only tool call and its durable result/event | Observe post-action state; report pass, fail, or inconclusive. |
| Inspect/recover | pending actions and `recover_incomplete_session` | Reconcile durable evidence before continuing; do not equate retry with recovery. |
Use `SQLiteSessionStore` and `SQLiteTaskStore` for a durable single-host
application. Use a deployment-appropriate shared store for multiple active
processes. In-memory stores are for hermetic tests, never production ownership.
The `CayuApp` object remains process-local; every replacement process rebuilds
it against the same stores.
Reconstruction also requires compatible identities for service-backed tools,
custom policies, and environments. See `cayu guide durable-service-tools` for
those declarations, explicit knowledge bindings, and a fresh-process example.
The lifecycle skeleton below runs in one process with fake application stores;
reopening its session database alone does not prove reconstructed tool behavior.
If dispatch can commit before its success acknowledgement becomes durable, use
the focused `cayu guide tool-effects#act-once-recovery` protocol within the Act
once and Inspect/recover phases. It adds durable uncertainty and bounded
reconciliation dispositions; it is not a second lifecycle.
## Artifact-write cancellation settlement
`ArtifactStore.put_bytes()` uses the same durable uncertainty discipline. Once
a local thread or remote request may have started, caller cancellation and
caller-owned timeout do not prove that it stopped. A conforming store classifies
the invocation as exact committed, positively absent, or
reconciliation-required. The third outcome is bounded diagnostic evidence with
a stable operation/artifact identity; it is not `ArtifactMetadata`, cannot be
passed to `read_bytes()`, and never authorizes publication to a model.
Built-in stores attach `ArtifactWriteSettlementEvidence` to the authoritative
`CancelledError` or operational failure. Use
`artifact_write_settlements(error)` for direct exception diagnostics, or install
an `ArtifactWriteSettlementObserver` around task creation when an operator also
needs a late final record. Generated and deterministic IDs are fixed before
dispatch. Reconcile a deterministic retry against exact bytes and immutable
metadata; never delete a final backend key merely because an acknowledgement or
readback was unavailable.
Third-party stores call `register_artifact_write_operation(...)` after fixing
the artifact ID and before the first possible mutation. Update the returned
registration's phase, keep it open while an opaque thread or remote request may
still run, and call `registration.record(...)` with the final classification.
When a caller-boundary candidate precedes retained work, pass `final=False` and
close the registration only from the retained operation's terminal callback.
This lets Cayu's runtime snapshot an active candidate before it cancels a timed-
out projection policy; registering only after cancellation is too late. Keep an
explicit operation ID unique while an observer retains its active or recorded
evidence; duplicate registration fails before dispatch, and `drain()` permits
deliberate reuse after removing the old evidence.
## Runnable public-API skeleton
This credential-free program performs no real external effect. Its proposal and
action tools write only to fake maps so the complete proposal, authorization,
one action receipt, verification, pending-action inspection, and recovery path
can be tested deterministically. In production, both maps must be durable,
tenant-qualified application stores. Copy the architecture, not the fake
storage or effect. All imports below are from the public `cayu` package.
```python
from __future__ import annotations
from pathlib import Path
from tempfile import TemporaryDirectory
from cayu import (
AgentSpec,
CayuApp,
IncompleteSessionRecoveryAction,
IncompleteSessionRecoveryRequest,
Message,
ModelStreamEvent,
PendingActionKind,
PendingActionQuery,
ResolutionActor,
ResolutionActorSource,
RunRequest,
ScriptedModelProvider,
SQLiteSessionStore,
SQLiteTaskStore,
Tool,
ToolApprovalDecision,
ToolApprovalRequest,
ToolContext,
ToolEffect,
ToolPolicy,
ToolPolicyDecision,
ToolPolicyRequest,
ToolPolicyResult,
ToolResult,
ToolSpec,
)
ACTION_ID_PATTERN = r"^change-[0-9]{4}$"
MAX_ACTION_ID_LENGTH = 11
ALLOWED_TARGET = "demo"
ALLOWED_REASON = "observed_drift"
def validate_action_id(value: object) -> str:
if (
not isinstance(value, str)
or len(value) > MAX_ACTION_ID_LENGTH
or re.fullmatch(ACTION_ID_PATTERN, value, flags=re.ASCII) is None
):
raise ValueError("action_id is not allowed")
return value
def proposal_input_schema() -> dict:
return {
"type": "object",
"properties": {
"action_id": {
"type": "string",
"pattern": ACTION_ID_PATTERN,
"maxLength": MAX_ACTION_ID_LENGTH,
},
"target": {"type": "string", "enum": [ALLOWED_TARGET]},
"reason": {"type": "string", "enum": [ALLOWED_REASON]},
},
"required": ["action_id", "target", "reason"],
"additionalProperties": False,
}
def validate_proposal(args: dict) -> dict[str, str]:
"""Fail closed on anything outside this application's review vocabulary."""
if set(args) != {"action_id", "target", "reason"}:
raise ValueError("proposal fields are not allowed")
action_id = validate_action_id(args.get("action_id"))
if args.get("target") != ALLOWED_TARGET or args.get("reason") != ALLOWED_REASON:
raise ValueError("proposal vocabulary is not allowed")
return {"action_id": action_id, "target": ALLOWED_TARGET, "reason": ALLOWED_REASON}
def require_authorized_operator(
actor: ResolutionActor,
review: dict[str, str],
) -> ResolutionActor:
"""Represent a product handler's fail-closed authorization decision."""
if actor.tenant != "demo-tenant":
raise PermissionError("operator is outside the proposal tenant")
roles = actor.claims.get("roles")
if not isinstance(roles, list) or not all(isinstance(role, str) for role in roles):
raise PermissionError("operator roles must be an exact JSON string array")
if "operations-approver" not in roles:
raise PermissionError("operator lacks the required role")
if review["target"] != ALLOWED_TARGET:
raise PermissionError("operator is not authorized for this target")
return actor
class ProposalStore:
"""A fake application-owned review projection for this test only."""
def __init__(self) -> None:
self.records: dict[str, dict[str, str]] = {}
class FakeSystem:
"""A fake system with one synchronous operation-record commit point."""
def __init__(self) -> None:
self.operations: dict[str, dict] = {}
self.timeline: list[str] = []
async def apply_once(self, action_id: str, target: str) -> dict[str, str]:
self.timeline.append("apply_started")
await asyncio.sleep(0) # Make an ordering race observable in this test.
receipt = {"action_id": action_id, "target": target, "status": "applied"}
operation = {"target": target, "receipt": receipt}
existing = self.operations.get(action_id)
if existing is not None and existing != operation:
raise ValueError("action identity conflicts with the durable operation")
# One assignment is the fake transaction. A real downstream system must
# durably reserve the identity before its effect, then reconcile and
# publish a terminal receipt without claiming generic exactly-once safety.
self.operations[action_id] = operation
self.timeline.append("apply_finished")
return receipt
def observe_target(self, action_id: str) -> str | None:
self.timeline.append("verify")
operation = self.operations.get(action_id)
return None if operation is None else operation["target"]
class RecordProposal(Tool):
spec = ToolSpec(
name="record_proposal",
description="Record bounded non-secret fields for operator review.",
input_schema=proposal_input_schema(),
parallel_safe=False,
effect=ToolEffect.IDEMPOTENT,
)
def __init__(self, proposal_store: ProposalStore) -> None:
super().__init__()
self.proposal_store = proposal_store
async def run(self, ctx: ToolContext, args: dict) -> ToolResult:
try:
record = validate_proposal(args)
except ValueError:
return ToolResult(
content="proposal rejected by the application vocabulary",
structured={"recorded": False},
is_error=True,
)
existing = self.proposal_store.records.get(ctx.session_id)
if existing is not None and existing != record:
return ToolResult(
content="proposal identity conflicts with the durable record",
structured={"recorded": False},
is_error=True,
)
self.proposal_store.records[ctx.session_id] = record
return ToolResult(content="proposal recorded", structured=record)
class ReviewedActionPolicy(ToolPolicy):
"""Bind approval to a previously recorded, application-validated proposal."""
def __init__(self, proposal_store: ProposalStore) -> None:
self.proposal_store = proposal_store
async def authorize(self, request: ToolPolicyRequest) -> ToolPolicyResult:
if request.tool_name in {"record_proposal", "verify_change"}:
return ToolPolicyResult(decision=ToolPolicyDecision.ALLOW)
if request.tool_name != "apply_change":
return ToolPolicyResult(
decision=ToolPolicyDecision.DENY,
reason="Tool is outside the reviewed operations lifecycle.",
)
recorded = self.proposal_store.records.get(request.session.id)
if recorded is None:
return ToolPolicyResult(
decision=ToolPolicyDecision.DENY,
reason="No pre-existing review record for this session.",
)
try:
proposed = validate_proposal(request.arguments)
except ValueError:
return ToolPolicyResult(
decision=ToolPolicyDecision.DENY,
reason="Action is outside the application review vocabulary.",
)
if proposed != recorded:
return ToolPolicyResult(
decision=ToolPolicyDecision.DENY,
reason="Action does not match the reviewed proposal.",
)
return ToolPolicyResult(
decision=ToolPolicyDecision.REQUIRE_APPROVAL,
reason="Application-validated proposal requires human authority.",
)
class ApplyChange(Tool):
spec = ToolSpec(
name="apply_change",
description="Apply one explicitly authorized change.",
input_schema=proposal_input_schema(),
parallel_safe=False,
# Replays collapse through the stable domain action_id contract below.
effect=ToolEffect.IDEMPOTENT,
)
def __init__(self, proposal_store: ProposalStore, system: FakeSystem) -> None:
super().__init__()
self.proposal_store = proposal_store
self.system = system
async def run(self, ctx: ToolContext, args: dict) -> ToolResult:
try:
proposed = validate_proposal(args)
except ValueError:
return ToolResult(
content="action rejected by the application vocabulary",
structured={"applied": False},
is_error=True,
)
if self.proposal_store.records.get(ctx.session_id) != proposed:
return ToolResult(
content="action does not match the reviewed proposal",
structured={"applied": False},
is_error=True,
)
action_id = args["action_id"]
try:
receipt = await self.system.apply_once(action_id, args["target"])
except ValueError:
return ToolResult(
content="action identity conflicts with the durable operation",
structured={"applied": False},
is_error=True,
)
return ToolResult(content="change receipt", structured=receipt)
class VerifyChange(Tool):
spec = ToolSpec(
name="verify_change",
description="Observe post-action state for one stable action identity.",
input_schema={
"type": "object",
"properties": {
"action_id": {
"type": "string",
"pattern": ACTION_ID_PATTERN,
"maxLength": MAX_ACTION_ID_LENGTH,
}
},
"required": ["action_id"],
"additionalProperties": False,
},
effect=ToolEffect.NONE,
)
def __init__(self, system: FakeSystem) -> None:
super().__init__()
self.system = system
async def run(self, ctx: ToolContext, args: dict) -> ToolResult:
try:
action_id = validate_action_id(args.get("action_id"))
except ValueError:
return ToolResult(
content="verification identity rejected by the application vocabulary",
structured={"verified": False},
is_error=True,
)
# This is a fresh post-action observation, not the execution receipt.
observed_target = self.system.observe_target(action_id)
verified = observed_target == ALLOWED_TARGET
return ToolResult(
content="verification passed" if verified else "verification inconclusive",
structured={
"action_id": action_id,
"observed_target": observed_target,
"verified": verified,
},
is_error=not verified,
)
async def scenario(database: Path) -> None:
provider = ScriptedModelProvider(
[
[
ModelStreamEvent.tool_call(
id="proposal-call",
name="record_proposal",
arguments={
"action_id": "change-0001",
"target": "demo",
"reason": "observed_drift",
},
),
ModelStreamEvent.completed({"finish_reason": "tool_calls"}),
],
[
ModelStreamEvent.tool_call(
id="action-call",
name="apply_change",
arguments={
"action_id": "change-0001",
"target": "demo",
"reason": "observed_drift",
},
),
ModelStreamEvent.tool_call(
id="verify-call",
name="verify_change",
arguments={"action_id": "change-0001"},
),
ModelStreamEvent.completed({"finish_reason": "tool_calls"}),
],
[
ModelStreamEvent.text_delta("Change change-0001 is verified."),
ModelStreamEvent.completed({"finish_reason": "stop"}),
],
]
)
proposal_store = ProposalStore()
system = FakeSystem()
record_proposal = RecordProposal(proposal_store)
action = ApplyChange(proposal_store, system)
verify = VerifyChange(system)
app = CayuApp(
session_store=SQLiteSessionStore(str(database)),
task_store=SQLiteTaskStore(str(database)),
enable_logging=False,
)
app.register_provider(provider, default=True)
app.register_agent(
AgentSpec(
name="operator",
model="scripted-model",
workflow_tool_names=("record_proposal", "apply_change", "verify_change"),
),
tools=[record_proposal, action, verify],
# Proposal recording and verification are allowed; the consequential
# action is the only operation that pauses for human authority.
tool_policy=ReviewedActionPolicy(proposal_store),
)
# Observe/diagnose/propose. Execution stops before ApplyChange.run.
_pause_events = [
event
async for event in app.run(
RunRequest(
session_id="ops-demo", # stable across process restarts
agent_name="operator",
messages=[
Message.text(
"user",
"Observe the demo, diagnose drift, and propose a safe correction.",
)
],
)
)
]
assert system.operations == {}
pending = await app.session_store.query_pending_actions(
PendingActionQuery(session_id="ops-demo")
)
assert not pending.issues and len(pending.actions) == 1
proposal = pending.actions[0]
assert proposal.kind is PendingActionKind.TOOL_APPROVAL
# Public pending-action projections intentionally omit executable arguments.
assert proposal.arguments is None
# The product handler joins the pending runtime identity to its own bounded
# review projection. Never infer authorization from the redacted runtime
# projection alone, and never put secrets in the application review record.
review = proposal_store.records[proposal.session.id]
assert review == {
"action_id": "change-0001",
"target": "demo",
"reason": "observed_drift",
}
# Safe to call after a replacement process starts. Recovery recognizes that
# this session still needs its existing approval; it does not run the tool.
recovery = await app.recover_incomplete_session(
IncompleteSessionRecoveryRequest(session_id="ops-demo")
)
assert IncompleteSessionRecoveryAction.PENDING_APPROVAL in recovery.actions
assert system.operations == {}
# This represents a trusted server-side handler after authentication and an
# application authorization check over the exact bounded review record.
# Direct SDK access is a trusted boundary: do not expose this method to an
# untrusted client or accept caller-asserted roles without authentication.
authenticated_operator = ResolutionActor(
subject="operator@example.test",
tenant="demo-tenant",
source=ResolutionActorSource.REQUEST,
claims={"roles": ["operations-approver"]},
)
authorized_operator = require_authorized_operator(authenticated_operator, review)
# Authorization is bound to the exact durable runtime identity. Use these
# IDs from the current pending record; do not regenerate or guess them, and
# stamp the authenticated resolver into the durable audit trail.
_resume_events = [
event
async for event in app.resolve_tool_approval(
ToolApprovalRequest(
session_id=proposal.session.id,
approval_id=proposal.approval_id,
tool_round_id=proposal.round_id,
tool_call_id=proposal.tool_call_id,
decision=ToolApprovalDecision.APPROVE,
reason="reviewed exact proposal",
resolved_by=authorized_operator,
)
)
]
# The action ran once, then the separate verifier ran, then the model ended.
assert system.operations == {
"change-0001": {
"target": "demo",
"receipt": {
"action_id": "change-0001",
"target": "demo",
"status": "applied",
},
}
}
# Action and verification came from one approved model round. The yielding
# action is an ordering barrier, so the read cannot race ahead of its effect.
assert system.timeline == ["apply_started", "apply_finished", "verify"]
assert len(provider.requests) == 3
assert not (
await app.session_store.query_pending_actions(
PendingActionQuery(session_id="ops-demo")
)
).actions
# A replacement process can inspect the terminal durable state.
reopened = SQLiteSessionStore(str(database))
state = await reopened.load_state("ops-demo")
assert state is not None and state.status.value == "completed"
with TemporaryDirectory() as temporary:
asyncio.run(scenario(Path(temporary) / "cayu.db"))
```
Run the skeleton as a test before replacing the scripted provider or fake tool.
For a real agent, persist the application review projection and action receipts
outside the process, preserve the `action_id` from proposal through action, and
put receipt/verification evidence in structured tool results. Keep
observations, diagnoses, review fields, and verification evidence bounded and
non-secret. Cayu persists the runtime transcript, checkpoints, events, policy
decision, resolver provenance, and tool-round identity; the application must
authenticate and authorize the resolver, and the external system must still
provide its own idempotency or receipt contract.
## Recovery decision table
Inspect `PendingActionQuery` first and call
`recover_incomplete_session(IncompleteSessionRecoveryRequest(...))` to classify
an incomplete session from durable evidence.
| Evidence | Safe disposition |
| --- | --- |
| Approval pending; no start evidence | approve or deny the existing exact proposal. |
| Non-effectful work incomplete | resume/recompute only through the public recovery API. |
| Terminal tool receipt is durable | let recovery reconcile/publicize that receipt; do not call the effect directly. |
| External action started but no trustworthy terminal receipt | require operator/downstream reconciliation; never automatically replay. |
| Verification failed or is inconclusive | retain evidence and require a new diagnosis; do not invent success. |
Cayu also exposes `ToolApprovalRecoveryRequest` and `ToolRoundRecoveryRequest`
for manual reconciliation when pending-action inspection identifies uncertain
tool evidence. Supply a terminal outcome only after an operator has obtained
trustworthy external evidence. A recovery request records that evidence; it is
not permission to guess that an effect succeeded.
The local operator surface uses these same durable APIs: run `cayu session` for
CLI inspection, or `cayu serve --dev` on a trusted local machine and open
`/cayu/`. Production control-plane access requires `AuthenticatedAccess(...)`.
Do not create a parallel approval database or expose private checkpoint IDs and
arguments in a product API.
## Unsafe shortcuts
- **Approval after execution:** authorization must pause the proposed tool call
before the effect. A prompt saying "ask first" is not enforcement.
- **Approval without review evidence:** public pending-action projections omit
executable arguments by design. Join their durable IDs to a bounded,
application-owned proposal record, then authenticate and authorize the human
against those exact fields before resolving the approval. Record the proposal
in an earlier tool round: Cayu authorizes an entire round before executing any
tool, so a proposal and consequential action emitted together must fail closed.
- **Caller-asserted authority:** direct SDK resolution is a trusted boundary.
Gate it in an authenticated server-side handler and pass `ResolutionActor`
provenance; a `reason` string is not identity or authorization.
- **Regenerated identities:** never generate a new action, approval, round, or
call ID while resolving an existing proposal. Resolve IDs from the current
pending-action record. Keep one stable domain `action_id` through proposal,
receipt, verification, and downstream idempotency.
- **Process-local production state:** a global `CayuApp`, dict, lock, or in-memory
store does not coordinate replacement workers. Rebuild the app over durable
stores and put effect idempotency in durable downstream state.
- **Blind replay:** Cayu does not promise that an arbitrary external side effect
is exactly once. If a process can die after starting an effect but before a
terminal receipt is durable, classify the outcome as uncertain and reconcile
it. Never turn "started without terminal evidence" into automatic retry or
assumed success. `TaskStore.terminalize_task(...)` makes Cayu's own durable
task completion/failure replay-safe after acknowledgement loss; its receipt
proves only that task state commit. It does not prove that a provider, tool,
webhook, payment, or other external effect happened exactly once.
- **Verification by narration:** a successful action receipt is not post-action
verification. Use a separate read-only observation and retain pass, fail, or
inconclusive evidence tied to the same stable `action_id`.
For restart-safe notification of questions, approvals, and manual-recovery gates,
see `cayu guide human-attention`. Notification acceptance never grants execution.
# Workspaces, runners and environments
A workspace owns file access. A runner owns process execution. An environment combines resources and lifecycle for a session. The model requests tools; the application chooses the environment and its authority.
## Start with one bounded workspace
For repository work, use the maintained coding preset:
```sh
uvx cayu new repo-agent --preset coding
```
The generator checks required Git, `rg` and secure local-workspace primitives before creating the project. Read the generated instructions for your host and selected adapters. Keep workspace construction in `environments/`, tools in `tools/`, and their admission rules in `policies/`.
Choose the root explicitly. Test traversal, symlinks, oversized reads and commands outside the allowed toolchain. Host-process Python tools remain trusted application code; a local workspace is not an operating-system sandbox for arbitrary Python.
## Add isolation at the execution boundary
Use a suitable runner and environment when the agent must execute untrusted commands. Configure the image, resource limits, network policy, credentials and shutdown behavior before exposing a terminal tool. An environment ID does not itself authorize access to another environment.
Record artifacts and useful results before cleanup. After a crash, a process may be gone while an external container or workspace still exists. Recovery must reconcile its recorded lifecycle; cancellation alone does not prove that cleanup completed.
## Verify the lifecycle
Run a small harmless command, inspect its exit status, produce one bounded artifact, interrupt execution, and confirm the selected environment's cleanup evidence. Repeat with allocation failure and delayed cleanup. Keep remote provider qualification separate from deterministic application tests.
Reference: [environments](https://cayu.dev/docs/reference/python/environments/), [workspaces](https://cayu.dev/docs/reference/python/workspaces/), [runners](https://cayu.dev/docs/reference/python/runners/) and [resources](https://cayu.dev/docs/reference/runtime-guarantees/).
# Files, artifacts and knowledge
An attachment is input. An artifact is a retained result. A workspace file belongs to an environment. Knowledge is domain information exposed through an application-owned store or capability. Decide which role a document has before wiring it into a prompt.
## Accept bounded input
Validate media type, byte count, total request size and attachment count. Install `cayu[files]` for file-related integrations that require its dependencies. Choose extraction behavior for each supported format; an uploaded filename does not prove its content is safe or correctly parsed.
Keep provenance with extracted content. A sentence inside a document can be evidence about the document, but it cannot grant permission to execute a tool or disclose another session's files.
## Retain output before reporting success
Use the artifact interface to give results a stable identity. Test that the reported file exists, that its bytes match the expected result, and that the caller can retrieve it under the intended access policy. A model saying it wrote a report is insufficient evidence.
For workspace changes, inspect the actual diff. For a domain record, verify the owning transaction or receipt. Keep temporary work separate from results that must survive the environment's cleanup.
## Expose knowledge with a clear scope
Use the generated `knowledge/` seam. Define which facts may be read, when pending writes become visible, and how the application confirms a mutation. Test stale, missing and unauthorized records, not only successful search.
Read the [artifacts API](https://cayu.dev/docs/reference/python/artifacts/), [knowledge and storage API](https://cayu.dev/docs/reference/python/storage/) and [memory guide](https://cayu.dev/docs/build/context-memory/).
# Human-attention notifications
This guide ships with the Cayu package, so the installed version is always available offline with `cayu guide human-attention`. [Release source](https://github.com/cayu-dev/cayu/blob/v0.9.1/src/cayu/guides/human-attention.md).
Use this integration when a person must answer a question, approve a tool, or
reconcile a manual-recovery gate after leaving the live stream. Runtime owns the
pending execution state and validates resolution. Your application owns recipient
scope, channel credentials, durable delivery, reminders, and routing. The agent
only calls `ask_user` or encounters its configured approval/recovery gate; it does
not call a separate notification tool.
The runnable, credential-free companion is
[`examples/human_attention/`](https://github.com/cayu-dev/cayu/tree/v0.9.1/examples/human_attention/).
It uses SQLite for Runtime state and a separate SQLite database as a durable test
destination. Replace that destination with a service that durably commits before
acknowledging. Keep vendor adapters outside Runtime core. The broader operational
lifecycle remains `cayu guide durable-operations`.
## Supported entry points
```python
from cayu import HumanAttentionRequest, PendingActionQuery
page = await app.session_store.query_pending_actions(
PendingActionQuery(limit=100)
)
for action in page.actions:
notification = HumanAttentionRequest.from_pending_action(action)
if notification is None:
# A delegated parent is navigation only. Inspect its child within your
# authorized scope. A legacy/custom row without incarnation is unavailable.
continue
current = await app.get_human_attention_state(notification.reference)
if current.state == "active":
await destination.accept(notification) # return only after durable commit
```
`destination` is application-owned. The example's `Inbox.accept` makes one atomic,
unique insert, and `DurableAttentionSink.emit` commits event hints before returning.
`get_human_attention_state` is a read-only SDK surface that returns bounded state
and reason fields; it never claims delivery, retries tools, answers, or grants an
approval. Direct SDK callers must authorize access to the referenced session.
These are supported discovery contracts:
| Entry point | Contract |
| --- | --- |
| `SessionStore.query_pending_actions(PendingActionQuery(...))` | Bounded authoritative current actions, issues, and pagination |
| `PendingActionRecord.attention_id` | Stable logical attention identity, or null when no safe actionable identity exists |
| `PendingActionSession.instance_id` | Session incarnation, read in the same store snapshot as the action |
| `HumanAttentionRequest.from_pending_action(action)` | Fixed content-free summary plus exact SDK correlation reference |
| `CayuApp.get_human_attention_state(reference)` | Current action or positive terminal evidence; absence alone is unavailable |
| `EventSink.emit(event)` | At-least-once refresh hint delivery, acknowledged only after durable destination acceptance |
| `CayuApp.recover_persisted_event_side_effects(limit=...)` | Bounded recovery of committed event fan-out |
The optional server's protected `GET /api/pending-actions` includes `attention_id`
and the existing public resolution/navigation references. HTTP clients use those
public `input_id`, `approval_id`, `round_id`, and `tool_call_id` fields with the
corresponding protected resolver. They must not decode public event IDs into
execution authority. SDK `HumanAttentionReference` values belong inside the
trusted consumer/resolver boundary; HTTP responses use the server's normal
public-ID projection instead. Do not reconstruct an SDK reference from arbitrary
callback data or a public event payload.
## One underlying action
Deduplicate within a configured Runtime-store namespace by `attention_id`, not by
the display row `id`, event sequence, question text, or session ID alone. The
attention ID binds session ID, durable session incarnation, kind, and exact pause
identity. Manual recovery also binds its round/call identity. Deleting and creating
a session with the same name creates a different attention ID. Store namespaces
are deployment-owned: never collapse unrelated Runtime databases into one dedup
namespace, and keep identity keying stable across consumers/restarts.
For user input, both committed `session.awaiting_user_input` and
`session.interrupted` prompt refresh. The first can arrive before the interrupted
boundary is discoverable; the latter is the durable paused boundary. Both lead to
one attention ID. A missed early observation is repaired by later hints and
periodic queries, not by manufacturing an action from the event.
A `delegated_action` row represents a parent waiting on a child. It has no
attention ID and does not duplicate child question/approval content. Follow
`delegated_action.child_session_id` within authorized scope, query that child's
current pending action, and use the child's attention ID and exact resolver.
Nested delegation follows the same rule. An all-session authorized consumer will
discover the child directly. A parent-only enrollment must explicitly include an
authorized child scope or show the navigation request to an operator; it cannot
assume permission to inspect or answer the child.
## Safe notification content
The default summaries are exactly “User input required.”, “Tool approval
required.”, and “Manual recovery required.” The projection contains identities,
not questions, choices, arguments, checkpoints, transcripts, prompts, or arbitrary
exception messages. The demo's inspection output shows only the attention ID,
kind, summary, and notification state.
Keep detailed inspection access-controlled. Use `app.inspect_human_review(...)`
with a configured `HumanReviewPolicy` and verified `HumanReviewContext` when a
human needs permitted question/approval content. Recipient authorization and
policy-controlled disclosure are distinct from sink delivery. Do not copy private
`HumanReviewSource`, quarantined arguments, or generic raw event payloads into a
notification. Known-secret redaction alone cannot establish safe disclosure of
unknown deployment data. Application-owned IDs and links also need appropriate
scope; use the server's public session link projection for external navigation.
## Durable delivery and repair
1. Configure a persistent SessionStore and the same destination sink in every
producer/recovery process. The durable handoff protects configured fan-out
after commit. An `emit` return means the destination has committed durable
work, not that a network request was launched or a background coroutine exists.
2. Let the sink enqueue only a bounded refresh hint. The example deduplicates
immutable callbacks by `(session_id, event_id)` in a deployment-specific inbox.
Notifications deduplicate separately by `attention_id`. Delivery can repeat
after acceptance but before acknowledgement; callbacks may arrive out of order.
3. On consumer startup and periodically, enumerate authoritative pending actions.
The example explicitly enrolls **all current actions**, including those that
predate consumer enrollment. An application that intentionally excludes older
work must persist an enrollment boundary and document that policy; filtering
only future events otherwise silently misses existing pauses.
4. Follow `next_cursor` with unchanged filters. `limit` is bounded at 200.
`has_more`, `issues`, byte-limit errors, repeated/malformed cursors, store errors,
or a consumer page budget mean incomplete inspection. Never mark unseen work
resolved because it was absent from one page. Session updates can move rows
during pagination; repeat full scans for eventual repair.
5. Recheck `get_human_attention_state(reference)` before dispatching delayed work.
A late opening event cannot recreate a settled request. The destination keeps
terminal states monotonic with a conditional update, so a concurrent stale
consumer cannot reopen one. A state can change immediately after a read; a
notification is an invitation to inspect, never permission to execute.
6. Reconcile already-enrolled references independently of the global scan. A
deleted session, a different incarnation, truncated query, store outage, or
missing retained terminal evidence is `unavailable`, not resolution. Keep the
notification/evidence and retry later. Do not pretend the canonical store is
available by treating the notification database as pause authority.
The example caps work per invocation with `max_pages` and leaves event hints
unprocessed after an incomplete scan. Each destination write is independently
committed and idempotent. A consumer crash after a notification insert but before
acknowledgement is safe: another consumer repeats the same logical acceptance.
Notification failure never clears the pending action or rolls back a committed
pause. Polling complements durable fan-out and repairs enrollment or configuration
gaps; it does not grant exactly-once external delivery.
Refresh on approval request/approved/denied/expired events, input awaiting and
interrupted events, `session.delegated_action.updated`, checkpointed/resumed/
completed/failed session events, and completed/blocked/failed tool events. This is
a wake-up set, not a second lifecycle reducer. A `session.resumed` event does not
prove an answer completed; execution may still be uncertain. Do not subscribe to
or emit health changes through the same sink to repair its own failures.
## Interpreting state
| State | Evidence and consumer behavior |
| --- | --- |
| `active` | The exact incarnation-bound pending action is currently queryable; inspect again before deciding |
| `resolved` | Exact user-input/approval closure or matching definite manual-recovery evidence (including an operator-confirmed failed outcome); suppress future reminders |
| `cancelled` | Confirmed denied approval or blocked manual recovery; no execution grant is implied |
| `superseded` | Runtime's exact user-input supersession evidence, including an operator interruption; it is not an answer |
| `expired` | Runtime committed approval expiry and closure; the consumer did not invent a deadline |
| `unavailable` | Read failed, incarnation/session changed, query incomplete, or terminal evidence is absent; preserve and retry |
The inspector bounds terminal-event inspection at 100 records and returns
`unavailable` when that bound prevents proof. It does not expose receipt bodies or
source events. User-input closure and supersession use the existing exact durable
receipts/evidence. A committed manual-recovery failure resolves the attention
request even though the tool failed. Synthetic failures marked outcome-unknown or requiring manual
reconciliation remain unavailable. Manual recovery without a matching definite
result remains unknown. No user-input expiry is introduced. Approval expiry still
gates only the first grant: recovery of an approval granted in-window can continue after its
wall-clock expiry. An expired timestamp alone does not let a notification service
cancel authorized recovery; the resolver owns that decision.
## Answer through Runtime
A delivery receipt acknowledges transport only. It cannot resume a session.
After authenticating and authorizing the person, refresh the current action and
use the existing exact-action API:
- Question: `app.resolve_user_input(UserInputResponse(...))` or
`POST /api/user-input/resolve`.
- Approval: `app.resolve_tool_approval(ToolApprovalRequest(...))` or
`POST /api/tool-approvals/resolve`.
- Manual recovery: inspect its gate, obtain the independently verified outcome,
and use `recover_user_input`, `recover_tool_approval`, or `recover_tool_round`
with the corresponding typed recovery request. Never blindly retry the tool.
When human review is configured, pass the current review reference; notifications
cannot replace that authorization/content binding. The CLI demo treats its local
operator as trusted. It is not an internet-facing authentication handler.
## SDK and optional-server recovery
SDK-only deployments must own a scheduled loop that invokes bounded
`recover_persisted_event_side_effects(limit=...)` sweeps, waits between sweeps, and
keeps failures observable. The CLI `consume` command performs one bounded sweep
and one bounded reconciliation invocation. Run it on a deployment-owned schedule.
Respect existing durable retry deadlines; do not spin on failing deliveries.
For the optional server, construct `create_server(app, config=ServerConfig.protected(...))`
with the persistent store and sink. It owns startup and periodic handoff recovery.
Run the consumer independently against the same authorized store. The example's
`server.create_demo_server` factory demonstrates this setup and closes its store
on shutdown. Protected pending-action/resolution routes use the configured auth;
the open liveness route is not an inbox or notification channel.
Recovery stops automatically at dead-letter exhaustion. Inspect bounded delivery
rows with `SessionStore.list_persisted_event_side_effect_deliveries(...)` and fix
the destination before operator reconciliation. On Runtime versions with the
operational-health surface, use `get_persisted_event_side_effect_health()` and the
protected `/api/event-side-effects/health` endpoint for aggregate backlog/retry
pressure; this integration does not require it. Preserve dead-letter evidence and
reconcile external acceptance before any manual repair. Do not delete the session
or rerun providers/tools to clear an alert.
## Runnable proof
From a checkout with development dependencies installed:
```sh
attention_state=$(mktemp -d)
uv run python -m examples.human_attention.app pause "$attention_state"
uv run python -m examples.human_attention.app consume "$attention_state"
uv run python -m examples.human_attention.app inspect "$attention_state"
uv run python -m examples.human_attention.app answer "$attention_state"
uv run python -m examples.human_attention.app consume "$attention_state"
uv run python -m examples.human_attention.app late-hint "$attention_state"
```
Every command is a fresh process. There is one active notification before the
answer and one resolved notification afterward; the late opening hint leaves it
resolved. Use a fresh directory with `pause --kind tool_approval`, then `approve`
or `deny` instead of `answer`, to exercise approval handling.
Fault injection is intentional and limited to this fixture:
- `pause --crash-before-delivery` exits 17 after the pause boundary commits but
before that sink delivery. A fresh `consume` discovers the pending action even
while the crashed producer's delivery lease remains live.
- `pause --fail-after-accept` commits the destination hint and loses its
acknowledgement. The original action remains available.
- `consume --crash-after-accept` exits 18 after the notification insert. Repeating
`consume` or running concurrent consumers leaves one logical notification.
- `interrupt` supersedes a question through Runtime; the next `consume` records
`superseded`, not `resolved`.
Focused contract tests and the actual fresh-process proof are separate:
```sh
uv run pytest tests/core/test_human_attention.py tests/core/test_foreground_child_pending_actions.py
uv run pytest tests/examples/test_human_attention_example.py
```
# Build agents
Build your domain-specific harness with the framework’s components and defaults. Its integrated runtime executes that harness and records progress. Start with one model-only agent and a clear observable job. Add a capability when the job requires it. The [authoring guide](https://cayu.dev/docs/build/authoring/) is the complete supported implementation loop.
| Requirement | Guide |
| --- | --- |
| Choose a model service | [Providers](https://cayu.dev/docs/build/providers/) and [reasoning](https://cayu.dev/docs/build/reasoning/) |
| Validate a structured result | [Structured output](https://cayu.dev/docs/build/structured-output/) |
| Call application code | [Tools](https://cayu.dev/docs/build/tools/) and [tool effects](https://cayu.dev/docs/build/tool-effects/) |
| Pause for a person's decision | [Approvals and input](https://cayu.dev/docs/build/approvals/) and [human attention](https://cayu.dev/docs/build/human-attention/) |
| Reconstruct service-backed tools | [Service tools](https://cayu.dev/docs/build/service-tools/) |
| Perform durable external changes | [Durable operations](https://cayu.dev/docs/build/durable-operations/) |
| Shape context and knowledge | [Context and memory](https://cayu.dev/docs/build/context-memory/) |
| Queue work or coordinate steps | [Tasks and workflows](https://cayu.dev/docs/build/tasks-workflows/) |
| Use files, commands or network access | [Environments](https://cayu.dev/docs/build/environments/) and [files and artifacts](https://cayu.dev/docs/build/files/) |
| Connect externally exposed tools | [MCP](https://cayu.dev/docs/build/mcp/) |
| Add search, fetch or browser capabilities | [Web and browser evidence](https://cayu.dev/comparison/#capability-browsers) |
| Coordinate cooperating agents | [Tasks and workflows](https://cayu.dev/docs/build/tasks-workflows/) and [collaboration evidence](https://cayu.dev/comparison/#capability-collaboration) |
| Deliver repository changes | [Git and GitHub evidence](https://cayu.dev/comparison/#capability-verification) |
| Complete work against independent acceptance | [Verified work](https://cayu.dev/docs/build/verified-work/) |
| Build a product UI | [Application UI](https://cayu.dev/docs/build/application-ui/) |
Use [Python reference](https://cayu.dev/docs/reference/python/) for exact types and signatures. The installed `cayu guide authoring#cayu-map` selects the smallest version-matched local reference for a capability.
# Connect MCP tools
MCP supplies a protocol for discovering and calling capabilities. Connecting a server does not replace Cayu's tool admission, execution, credential or environment policies.
## Plan the boundary
1. Select the transport and trusted server identity.
2. Discover its tools and inspect their schemas.
3. Admit only the capabilities your application requires.
4. Classify effects and apply authorization before execution.
5. Provide scoped credentials through application configuration.
6. Bound call duration and result size, then retain useful evidence.
Treat descriptions and results from an external server as external content. A result that asks the agent to call another tool is not authority to do so.
## Verify failure behavior
Test a missing server, invalid arguments, malformed results, timeouts and cancellation. Distinguish discovery failure from execution failure; avoid retrying an external mutation merely because its result was not observed. A receipt or idempotency check belongs to the application that owns that effect.
Configure integration construction in `integrations/`, tool registration in `tools/`, and policy in `policies/`. Use [MCP API](https://cayu.dev/docs/reference/python/mcp/) for the exact release-matched constructors and transport options, and [durable service tools](https://cayu.dev/docs/build/service-tools/) for external mutations.
# Cayu providers
This guide ships with the Cayu package, so the installed version is always available offline with `cayu guide providers`. [Release source](https://github.com/cayu-dev/cayu/blob/v0.9.1/src/cayu/guides/providers.md).
Cayu focuses on OpenAI, Anthropic, OpenRouter, Google, AWS, and Vertex AI
endpoints. Other services that expose OpenAI Chat Completions work through
`ChatCompletionsProvider`.
Provider selection is explicit. `CAYU_PROVIDER` is only a scaffold convenience
for `openai`, `anthropic`, `openrouter`, `cayu-gateway`, and `openai-subscription`; it is not the
complete Cayu provider surface. Credentials authenticate but never select one.
## Primary integrations
| Service | Cayu provider | Setup |
| --- | --- | --- |
| OpenAI Platform | `OpenAIProvider()` | `OPENAI_API_KEY`; use an OpenAI model ID |
| Anthropic API | `AnthropicProvider()` | `ANTHROPIC_API_KEY`; use an Anthropic model ID |
| OpenRouter | `ChatCompletionsProvider(name="openrouter", api_key_env="OPENROUTER_API_KEY", base_url="https://openrouter.ai/api/v1")` | `OPENROUTER_API_KEY`; require an explicit `vendor/model` slug |
| Cayu Gateway | `GatewayProvider(base_url="https://YOUR_GATEWAY/v1")` | `CAYU_GATEWAY_API_KEY`; explicit endpoint and model |
| Google AI Studio | `ChatCompletionsProvider(name="google", api_key_env="GEMINI_API_KEY", base_url="https://generativelanguage.googleapis.com/v1beta/openai")` | Use a Gemini API model ID |
| Amazon Bedrock | `BedrockProvider(region_name=...)` | Install `cayu[aws]`; use AWS credentials and a Bedrock model or inference-profile ID |
| Anthropic on Vertex AI | `VertexProvider(project_id=..., region=...)` | Install `cayu[vertex]`; use Google credentials and a Vertex Claude model ID |
| OpenAI subscription | `OpenAISubscriptionProvider()` | Run `cayu auth openai login`; local development and evaluation only |
Google AI Studio automatically uses Gemini usage accounting. For Gemini through
another OpenAI-compatible Vertex or gateway endpoint, pass
`usage_dialect=UsageDialect.GEMINI` explicitly.
## OpenAI subscription
`OpenAISubscriptionProvider` experimentally runs agents against the Codex backend
with the developer's own ChatGPT sign-in: `cayu auth openai login`, or add
`--headless` for the device-code flow. It is for the subscription holder's local
development and evaluation only, not production, customer-facing or multi-user
services, credential sharing, resale, or bypassing plan limits. Use the OpenAI
Platform API in production. Model availability follows the subscription;
generated projects select `gpt-6-luna`, and `CAYU_MODEL` overrides it. Sign-in
credentials stay in the trusted Cayu process and never reach runners or sandboxes.
## Explicit fallback targets
Use `RunRequest.failover=ModelFailoverPolicy(...)` with explicit provider/model pairs
in `fallbacks`. Register all candidates; `max_total_attempts` bounds each model step.
Local retries run first. Only typed retryable service rejection before accepted
output/effects permits fallback; cancellation, deadlines and ambiguity do not.
All candidates must support required tools, files, output and thinking features.
Resume retains selection; changing the chain requires profile adoption. Budgets,
deadlines and root authority do not reset; actual target pricing/usage still applies.
Tool auxiliary inference stays separate; custom stores must attest atomic selection.
See `examples/provider_failover.py` for events and a network-free native-store example.
## Thinking effort compatibility
Use `ThinkingConfig` for typed reasoning settings. Supported values, model/transport
restrictions, precedence, and backend acceptance boundaries are in
`cayu guide thinking` ([compatibility matrix](https://cayu.dev/docs/build/reasoning/)). `xlow` is not a verified
native value; Runtime does not equate it with `minimal` or `low`.
## Recoverable OpenAI background responses
Long OpenAI Responses calls can opt into provider-owned background execution:
```python
from cayu import AgentSpec, CayuApp
from cayu.providers import OpenAIProvider
app = CayuApp()
app.register_provider(OpenAIProvider(background=True), default=True)
app.register_agent(AgentSpec(name="assistant", model="gpt-5.6"))
```
This is a provider-registration choice, not a per-request OpenAI option.
Cayu sends `background: true`, `stream: true`, and `store: true`, records the
OpenAI response ID before accepting later output, and can retrieve or resume
that same response after worker loss. `ModelRequest.options["openai"]` cannot
override these fields. The default `OpenAIProvider()` remains synchronous.
Enable this only after reviewing the deployment tradeoffs:
- OpenAI background responses have higher time to first token than synchronous
responses.
- A non-ZDR project stores the response at OpenAI so it can be retrieved and
resumed. OpenAI documents a 30-day Responses application-state retention
period and says data for `store=true` responses is retained for at least 30
days. OpenAI forces `store=false` under Zero Data Retention, but background
mode still stores response data on disk for roughly ten minutes for polling.
Confirm that behavior satisfies the application's retention policy before
enabling it.
- Cayu currently enables this mode only for the global `api.openai.com` base
URL and rejects region-specific OpenAI domains. OpenAI separately documents
that `background=true` is unavailable on its EU regional route. Validate the
account, model, project policy, processing location, and storage location for
the intended production deployment.
- OpenAI does not document exact recovery of a lost create acknowledgement from
Cayu's idempotency key. If the response ID was never made durable, Cayu reports
`ambiguous_submission` and does not submit the request again automatically.
This capability reconnects one provider operation. It is distinct from
server-side conversation chaining (`previous_response_id`) and from Cayu's
durable transcript. `OpenAISubscriptionProvider`, `ChatCompletionsProvider`,
Anthropic, Bedrock, Vertex, and custom adapters do not gain this capability.
See OpenAI's [data controls](https://developers.openai.com/api/docs/guides/your-data)
and [background mode](https://developers.openai.com/api/docs/guides/background)
guides for the current provider policy.
## OpenRouter
`cayu new APP --provider openrouter` generates the first-class preset. Set
`OPENROUTER_API_KEY` and an explicit `CAYU_MODEL=vendor/model`; Cayu deliberately
has no mutable router, free, or paid default. `CAYU_PROVIDER=openrouter` selects
the same preset in a neutral scaffold. Optional `OPENROUTER_HTTP_REFERER` and
`OPENROUTER_APP_TITLE` add attribution, while
`OPENROUTER_ROUTER_METADATA=enabled` retains only bounded routing evidence.
The upstream provider is reported by name (`upstream_provider`) when it is a
short plain name without a registered secret; other values become a digest. A
short secret that was never registered can still pass as a name; see
`docs/provider-error-diagnostics.md`.
Put routing controls in `AgentSpec.provider_options["openrouter"]`. Streamed
`reasoning_details` are concatenated in order and privately replayed unchanged,
value-for-value, across tool continuations; malformed state fails before tools execute.
Raw OpenRouter `usage.cost` evidence stays separate from Cayu PriceBook estimates.
Native structured-output support remains model/upstream dependent.
## Cayu Gateway
`cayu new APP --provider cayu-gateway` selects the Gateway adapter. Configure
`CAYU_GATEWAY_API_KEY`, `CAYU_GATEWAY_BASE_URL` (HTTPS, including `/v1`), and
`CAYU_MODEL`. A neutral scaffold can select it with `CAYU_PROVIDER=cayu-gateway`.
The SDK takes the endpoint explicitly:
```python
from cayu import AgentSpec, CayuApp, GatewayProvider
provider = GatewayProvider(base_url="https://YOUR_GATEWAY/v1")
app = CayuApp()
app.register_provider(provider, default=True)
app.register_agent(AgentSpec(
name="assistant",
model="YOUR_MODEL",
provider_options={"cayu_gateway": {"max_completion_tokens": 2048}},
))
```
The provider uses ordinary Chat Completions streaming, tools, reasoning, and
native JSON schema output when supported by the selected Gateway model. Options
belong under `provider_options["cayu_gateway"]`. `await provider.get_models()`
reads the models visible to the key; it does not change an agent's model or limits.
Completed model events retain the response `id` and raw `usage`, including token
counters and Gateway-reported `cost`, `cost_currency`, and `cost_status`. Cost is
a decimal USD string when reported. A pending or unavailable cost is `null`,
which is distinct from a reported zero. For an available response ID,
`await provider.get_generation(request_id)` reads current status and usage using
the same key (which needs receipt-read scope). Lookup does not replay output,
resubmit inference, reserve funds, or settle a charge. Close the provider with
`await provider.aclose()` when finished with it.
The usage dashboard and `/api/usage/rollup` show up to 100 latest completion-time
reported-cost observations within the requested session filters and time window.
They are separate from PriceBook estimates, are not summed into a bill, and do
not represent current wallet balances. Truncation is explicit; narrow the filters
to inspect a smaller window. Memory, SQLite, and PostgreSQL support this projection;
custom stores may report it as unavailable. Generation lookup can return a later
reported amount but does not rewrite historical completion events or automatically
refresh these observations.
Gateway owns prices, balances, reservations, spending caps, and settlement.
Runtime's PriceBook estimates and `max_estimated_cost` remain local execution
safeguards; they are separate from the reported charge and do not promise a strict
financial ceiling. Interrupting or deleting a Runtime session does not release a
Gateway hold. A lost connection can leave remote execution and cost unknown;
response IDs are retained in completed model events. The normal provider privacy
boundary omits untrusted error IDs, so an interrupted response may leave no ID
available for lookup. The adapter disables automatic retries
of failed inference requests because a new POST can create a new charge.
### Optional catalog-priced local budgets
Load a price snapshot at application startup, before accepting work:
```python
from decimal import Decimal
from cayu import BudgetLimit, BudgetPolicy, BudgetReservation
prices = await provider.price_book()
app.budget_policy = BudgetPolicy(limits=(BudgetLimit(
scope="app",
max_estimated_cost=Decimal("1.00"),
pricing=prices,
reservation=BudgetReservation(max_input_tokens=8192, max_output_tokens=2048),
),))
```
This uses ordinary Runtime reservations and estimate-based settlement. The helper
is optional: inference does not fetch prices automatically. It makes one authenticated
`/v1/models` lookup and returns an independently usable `PriceBook`; applications
may compose its `prices` with other providers' entries in their own price book.
It never reads generation receipts, sends a charge ceiling, or changes Cloud limits.
Prices match exact model IDs under `cayu_gateway`. Nano-USD per unit is converted
to USD per million tokens as `nano_usd / per_units / 1000`, using decimal arithmetic
and rounding repeating fractions upward. Input, output, cache-read and cache-write
components map to the corresponding `PriceTier` fields. Reasoning tokens already
count as output in Runtime, so output uses the higher of the output and reasoning
rates, without adding a second reasoning charge. Each schedule's `Provenance.source`
records the exact Gateway `price_id`; its URL identifies the catalog and its `as_of`
is `unspecified`, as is the book's `generated_at`: the catalog supplies no
authoritative timestamp. Unchanged catalog pricing retains the same book and budget
identity across refresh and restart. Applications may log fetch time separately;
it is not a promised price-validity window or part of pricing identity.
Models with nonzero request/tool-call charges, unknown dimensions, unsupported
currency/version, malformed or duplicate components, or missing input/output rates
are omitted. Absent cache rates remain absent. Explicit zero token rates remain valid
zero prices. Duplicate/invalid model identities reject the catalog; an empty catalog
or one with no usable prices raises `ValueError`. No zero-price placeholder or default
catalog fallback is installed. An omitted model fails ordinary reserving-budget
admission before dispatch. Keep those budgets configured; omitting a budget is not a
substitute for handling a failed price lookup.
To refresh, explicitly call `await provider.price_book()` again, then install a
complete replacement `BudgetPolicy` as above (preserving your other limits).
Fetching does not mutate the old book, existing reservations, or application policy.
If fetching/validation fails, no replacement occurs; decide whether to retain the
previous estimate or suspend new admissions. Existing sessions retain their recorded
execution profile; changing their pricing follows normal explicit profile-adoption
rules, not automatic migration. Catalog prices can change after loading: local
estimates and reservations are not a guaranteed supplier-charge ceiling.
## Compatible Chat Completions
Fireworks, Baseten Model APIs, OpenCode Go, and other compatible endpoints work
through Cayu's generic adapter. Register it and route the agent to its name:
```python
from cayu import AgentSpec, CayuApp, ChatCompletionsProvider
provider = ChatCompletionsProvider(
name="fireworks",
api_key_env="FIREWORKS_API_KEY",
base_url="https://api.fireworks.ai/inference/v1",
)
app = CayuApp()
app.register_provider(provider, default=True)
app.register_agent(
AgentSpec(
name="assistant",
model="accounts/fireworks/models/YOUR_MODEL_ID",
provider_name="fireworks",
system_prompt="Help the user.",
)
)
```
Change the provider name, base URL, API-key environment variable, and model ID
together:
| Service | Base URL and credential | Model ID |
| --- | --- | --- |
| Fireworks | `https://api.fireworks.ai/inference/v1`; `FIREWORKS_API_KEY` | `accounts/fireworks/models/...` |
| Baseten Model APIs | `https://inference.baseten.co/v1`; `BASETEN_API_KEY` | Baseten catalog model ID |
| OpenCode Go | `https://opencode.ai/zen/go/v1`; `OPENCODE_API_KEY` | Raw API ID such as `grok-4.5`, never `opencode-go/...` |
| Together AI | `https://api.together.ai/v1`; `TOGETHER_API_KEY` | Together catalog model ID |
| Mistral AI | `https://api.mistral.ai/v1`; `MISTRAL_API_KEY` | Mistral catalog model ID |
| Ollama | Commonly `http://127.0.0.1:11434/v1`; placeholder key | Pulled model name |
| vLLM | Commonly `http://127.0.0.1:8000/v1`; server key or placeholder | Served model name |
For local HTTP endpoints such as Ollama or vLLM, pass `allow_http=True`;
`OpenAIProvider` and `AnthropicProvider` accept the same opt-in for local
Responses or Messages servers. OpenCode
Go models span multiple protocols: use `ChatCompletionsProvider` for its Chat
Completions models and the matching Cayu protocol adapter for its other models.
Always use the raw API model ID.
Set `AgentSpec.provider_name` when routing explicitly, or register one provider
with `default=True`. Keep credentials out of `AgentSpec`, model IDs, and source
files. `cayu check` reports missing or ambiguous provider routes without calling
a provider.
# Thinking effort compatibility
This guide ships with the Cayu package, so the installed version is always available offline with `cayu guide thinking`. [Release source](https://github.com/cayu-dev/cayu/blob/v0.9.1/src/cayu/guides/thinking.md).
Use public `ThinkingConfig(effort="max")` on `AgentSpec.thinking`,
`RunDefaults.thinking`, `RunRequest.thinking`, `ResumeRequest.thinking`, or
`StepRunOptions.thinking`. Request/step overrides take precedence over agent
settings, which take precedence over application defaults. These values survive
JSON serialization and durable approval/dispatch recovery without normalization.
The exact vocabulary is `none`, `minimal`, `low`, `medium`, `high`, `xhigh`, `max`.
`xlow` remains invalid: the audit found no verified native `xlow` contract in these
adapters. It is not translated to `minimal` or `low`. Effort names are not a
cross-provider scale; `xhigh`, `high`, and `max` are distinct requests.
`enabled=True` enables the configuration, not necessarily model reasoning.
`effort="none"` explicitly requests native non-reasoning behavior; `minimal`
still permits reasoning. Both require `enabled=True`. `enabled=False` retains
its historical best-effort behavior (Anthropic disables, OpenAI/compatible Chat
Completions omit the typed control). Combining disabled thinking with any effort,
or combining effort with `max_tokens`, fails validation. Omitting effort preserves
existing defaults and legacy budget behavior. Typed effort overwrites raw effort;
unrelated raw siblings such as `reasoning.summary` and `output_config.format`
remain intact. No adapter downgrades or drops an explicit typed effort.
For an opt-in failover chain, `ModelProvider.preflight_thinking(model=...,
thinking=...)` validates each candidate before initial session creation and the
actual selected request before attempt reservation and staging. The hook must be
side-effect-free: no authentication, network request, or provider probing. It
receives a detached, validated `ThinkingConfig` (or `None`) and must reject
unsupported controls without echoing request input. The base implementation
accepts only `None`; custom providers must explicitly declare configured-control
support. Transparent wrappers must delegate the hook. Bundled adapters reuse the
local compatibility rules below; success does not prove unknown backend support
or turn best-effort controls into portable guarantees. Ordinary nonrouted runs
retain their existing validation paths.
Provider-contract audit: **2026-09-05**. “Declared” below means documented wire
support, not tested backend availability or evidence of internal reasoning work.
| Adapter / transport | Typed effort contract |
| --- | --- |
| OpenAI Responses (streaming, buffered construction, background create) | Declared `reasoning.effort`; exact model restrictions below. |
| OpenAI Chat Completions | Declared `reasoning_effort`; same model effort restrictions, with Responses-only models rejected. |
| Compatible Responses and Chat Completions (including OpenRouter) | Forward the exact wire value. Unknown model/deployment/router compatibility is unresolved; acceptance must be established at the selected backend. |
| OpenAI subscription Responses | Same Responses wire construction and local model checks; subscription entitlement and backend acceptance are unverified independently of Platform support. |
| Anthropic Messages and Vertex Anthropic Messages | `thinking.type=adaptive` plus exact `output_config.effort`; model restrictions below. Vertex model/region availability remains backend-dependent. |
| Google AI Studio via Chat Completions | Exact `reasoning_effort`, subject to the Google rows below. This adapter does not emit native Gemini `thinking_level`. |
| Bedrock Converse | No implemented typed effort mapping; any explicit effort fails locally. Native `additionalModelRequestFields` do not establish typed effort support. |
| Custom providers | Their adapter owns compatibility and validation; this matrix makes no support claim. |
The following known model rules apply to exact IDs, `-latest` aliases, and dated
snapshots (`-YYYY-MM-DD`, `-YYYYMMDD`, or Vertex `@YYYYMMDD`). These are bounded
incompatibility rules, **not a model allowlist**. Unknown identifiers are accepted
locally within the adapter vocabulary and sent unchanged. Recognized IDs retain
these restrictions even through a compatible gateway; arbitrary deployment IDs
and vendor-prefixed router slugs do not establish native-model identity.
| Model IDs | Allowed explicit effort |
| --- | --- |
| `gpt-5`, `gpt-5-mini`, `gpt-5-nano` | `minimal`, `low`, `medium`, `high` |
| `gpt-5-pro` | `high`; Responses only |
| `gpt-5.1` | `none`, `low`, `medium`, `high` |
| `gpt-5.1-codex` / `gpt-5.1-codex-max` | `low`, `medium`, `high` / additionally `xhigh`; Responses only |
| `gpt-5.2`, `gpt-5.4`, `gpt-5.5` | `none`, `low`, `medium`, `high`, `xhigh` |
| `gpt-5.6`, `gpt-5.6-sol`, `gpt-5.6-terra`, `gpt-5.6-luna` | `none`, `low`, `medium`, `high`, `xhigh`, `max` |
| `gpt-6-astra` | `low`, `medium`, `high`, `xhigh`, `max` |
| `o1`, `o3`, `o3-mini`, `o4-mini` | `low`, `medium`, `high` |
| `gpt-4o`, `gpt-4o-mini`, `gpt-4.1`, `gpt-4.1-mini`, `gpt-4.1-nano` | No typed effort |
| Claude Opus 4.6, Sonnet 4.6, Mythos Preview | `low`, `medium`, `high`, `max` |
| Claude Opus 4.7/4.8/5, Sonnet 5, Fable 5/5.1, Mythos 5/5.1 | `low`, `medium`, `high`, `xhigh`, `max` |
| Claude Opus 4/4.1/4.5, Sonnet 4/4.5, Haiku 4.5, 3.7 Sonnet, 3.5 Sonnet/Haiku | No adaptive typed effort. Opus 4.5's manual-thinking effort contract is not the adaptive mode selected by `ThinkingConfig.effort`. |
| Unknown Anthropic model | `low`, `medium`, `high`, `xhigh`, `max` forwarded; model acceptance unresolved. `none` and `minimal` fail locally. |
| `gemini-3.1-pro-preview`, `gemini-3.1-flash-lite-preview`, `gemini-3-flash-preview`, `gemini-2.5-pro` | `minimal`, `low`, `medium`, `high` |
| `gemini-2.5-flash`, `gemini-2.5-flash-lite` | `none`, `minimal`, `low`, `medium`, `high` |
Google documents its own compatibility mappings: `minimal` becomes `low` on
3.1 Pro, while 2.5 maps `minimal`/`low`, `medium`, `high` to budgets of 1,024,
8,192, 24,576. Cayu forwards the effort string; those translations occur at
Google, not in Runtime. Do not combine it with Google's raw thinking level/budget.
Local incompatibility raises a bounded `ValueError` before authentication or model
network dispatch, with allowed values or an adapter-selection remedy. It includes
no model input, credentials, URLs, or request bodies. Upstream rejection continues
through the provider's credential-safe API error path; it never triggers an effort
fallback. A successful fingerprint or payload build proves local validity and
requested/emitted settings only. Keep requested identity, emitted request identity,
and backend-reported identity separate.
Compatible Responses unit tests establish request construction with mocked
transports. Confirm support at the selected backend with a bounded live probe
that checks the emitted request and successful response. A successful response
does not prove internal reasoning allocation.
Audit sources: OpenAI [Chat Completions schema](https://developers.openai.com/api/reference/resources/chat/subresources/completions/methods/create),
[reasoning guide](https://developers.openai.com/api/docs/guides/reasoning),
[GPT-5](https://developers.openai.com/api/docs/models/gpt-5),
[GPT-5 Pro](https://developers.openai.com/api/docs/models/gpt-5-pro),
[GPT-5.1](https://developers.openai.com/api/docs/models/gpt-5.1),
[Codex-Max](https://developers.openai.com/api/docs/models/gpt-5.1-codex-max),
[GPT-5.2](https://developers.openai.com/api/docs/models/gpt-5.2),
[GPT-5.4](https://developers.openai.com/api/docs/models/gpt-5.4),
[GPT-5.5](https://developers.openai.com/api/docs/models/gpt-5.5),
[Luna](https://developers.openai.com/api/docs/models/gpt-5.6-luna),
[model catalog](https://developers.openai.com/api/docs/models);
Anthropic [effort](https://platform.claude.com/docs/en/build-with-claude/effort) and
[thinking modes](https://platform.claude.com/docs/en/about-claude/models/extended-thinking-models);
Google [OpenAI compatibility](https://ai.google.dev/gemini-api/docs/openai).
# Durable service-backed tools
This guide ships with the Cayu package, so the installed version is always available offline with `cayu guide durable-service-tools`. [Release source](https://github.com/cayu-dev/cayu/blob/v0.9.1/src/cayu/guides/durable-service-tools.md).
For a complete installed-package support agent with restartable clarification and
external approval receipts, run `cayu guide order-support`.
Use this recipe when a custom tool wraps an application-owned service and must
resume a saved session in a replacement process. For the complete proposal,
approval, action, verification, and recovery lifecycle, keep using
`cayu guide durable-operations`. This guide supplies the component identity and
knowledge bindings needed when reconstructing that application.
## Two separate bindings
| Configuration | What it establishes | What it does not establish |
| --- | --- | --- |
| `SQLiteSessionStore` or another persistent session store | Durable conversation, checkpoints, and pending actions | Equivalent tool, policy, or environment behavior after rebuilding the app |
| `ExecutionProfileBehaviorIdentity` | An explicit versioned declaration of component behavior | Persistence, authorization, or downstream idempotency |
| `CayuApp(knowledge_store=store)` | Application-level knowledge configuration | An ordinary tool's `ctx.knowledge_store` |
| Injected bound store, or selected environment's store and scope | The knowledge service the tool can actually query | Authority outside the supplied scope |
A direct `tool.run()` test misses runtime registration and admission. Test a
real `CayuApp`, persist a session, and reconstruct the app in a second OS
process. An undeclared opaque service object may make the reconstructed
execution profile incompatible even when its Python class and tool name match.
## Declare component identity
```python
from cayu import ExecutionProfileBehaviorIdentity
reader_identity = ExecutionProfileBehaviorIdentity(
name="handbook-reader",
behavior_version="1",
implementation_version="1",
)
```
Place this declaration on `ToolSpec.execution_profile_identity`. A custom
policy returns its own declaration from the
`ToolPolicy.execution_profile_identity` property. A registered custom
environment declares its own `EnvironmentSpec.execution_profile_identity`.
Declare each behavior-bearing component, not just the tool:
```python
from cayu import EnvironmentSpec, ToolPolicy
class HandbookPolicy(ToolPolicy):
@property
def execution_profile_identity(self):
return ExecutionProfileBehaviorIdentity(
name="handbook-policy", behavior_version="1", implementation_version="1"
)
# Implement authorize(request) for your application's actual authority rules.
environment_spec = EnvironmentSpec(
name="handbook",
execution_profile_identity=ExecutionProfileBehaviorIdentity(
name="handbook-environment", behavior_version="1", implementation_version="1"
),
)
```
Keep the logical name stable. Change `behavior_version` when externally
observable semantics or relevant non-secret configuration changes; change
`implementation_version` for every implementation deployment, even when its
public contract remains the same. Encode configuration revisions in these
versions; the identity has no arbitrary configuration field. Do not put secrets,
credentials, object addresses, random UUIDs, process IDs, or request IDs here.
Ordinary service data can evolve under an unchanged retrieval contract.
This declaration asserts equivalent behavior across reconstruction. It does not
prove equivalence or make incompatible changes safe. On a legitimate profile
change, start a new session or follow the explicit execution-profile adoption
contract in `cayu guide anatomy`; never automatically adopt on mismatch, disable
checks, or reuse an old version to conceal changed behavior. Operation
idempotency keys and durable approval/round/call IDs serve different purposes;
see `cayu guide tool-effects` and `cayu guide durable-operations`.
## Inject the supplied knowledge store
Constructor injection is the smallest ordinary-tool pattern. Validate a
required dependency before registering the tool or issuing a provider request:
```python
from cayu import KnowledgeQuery, KnowledgeStore, Tool, ToolEffect, ToolResult, ToolSpec
class HandbookReader(Tool):
spec = ToolSpec(
name="lookup_handbook",
description="Search the authorized handbook.",
input_schema={"type": "object", "properties": {}, "additionalProperties": False},
effect=ToolEffect.NONE,
execution_profile_identity=reader_identity,
)
def __init__(self, store: KnowledgeStore):
if not isinstance(store, KnowledgeStore) or store.bound_access_scope() is None:
raise ValueError("HandbookReader requires the supplied, bound knowledge store")
super().__init__()
self.store = store
async def run(self, ctx, args):
found = await self.store.search(KnowledgeQuery(text="shipping", namespace="handbook"))
payload = found.model_dump(mode="json")
return ToolResult(content=json.dumps(payload), structured=payload)
```
Register `HandbookReader(the_supplied_scoped_store)` on your agent.
Use the actual supplied service. Do not construct an empty replacement store or
silently return empty evidence when a required resource is missing. A namespace
filter narrows retrieval; it is not authorization. The hosting application
derives the bound scope from trusted principal/session ownership. Keep tenant
or organization restrictions, labels, visibility, and other scope constraints;
never accept model-selected tenant identity or broaden the scope to fix a miss.
Inject a store bound to that authority at the appropriate application lifetime.
## Bind through an environment
When tools should consume environment resources, explicitly bind and select the
environment instead. Given the same supplied bound `store`:
```python
from cayu import Environment
environment = Environment(
environment_spec,
knowledge_store=store,
knowledge_access_scope=store.bound_access_scope(),
)
```
Call `app.register_environment(environment)` and select
`environment_name="handbook"` in `RunRequest`.
Inside the tool, require `ctx.knowledge_store` and `ctx.knowledge_access_scope`,
then call `ctx.knowledge_store.search(query, access_scope=ctx.knowledge_access_scope)`.
An explicit scope must match the store's bound scope. Application-level
knowledge configuration alone does not populate these context fields; without
a selected environment binding they may be `None`. Built-in knowledge tools
use this environment path too; see `cayu guide references#knowledge`.
## Prove reconstruction
The repository example `examples/durable_service_tools/app.py` is credential-free
and uses public APIs with SQLite sessions and a scripted provider. Run `start`
and `resume` against one fresh directory in separate OS processes. It verifies
that the supplied store is searched, current scoped evidence reaches the
provider, forbidden records stay excluded, and the earlier conversation survives.
Use `--wiring environment` on both commands to exercise the environment path.
The example also supplies a versioned custom policy and a small SQLite receipt
tool. Its `pause`, `approve` or `deny`, and `repeat` phases exercise durable
approval reconstruction and completed-resolution behavior. This local fixture
proves only its own atomic insert/idempotency contract, not generic exactly-once
delivery to external services. Product handlers still authenticate and authorize
the resolver against exact review evidence as shown in `cayu guide durable-operations`.
Negative checks matter: a changed tool, policy, or environment version must fail
before a protected effect or provider request; an opaque undeclared tool must
remain incompatible across reconstruction. For a real service, also test
unknown external outcomes and reconciliation using `cayu guide tool-effects`.
## Reading admission errors
`app.run` creates a new session. For an ordinary next conversational turn:
```python
from cayu import CayuApp, Message, ResumeRequest
async def next_turn(app: CayuApp, existing_id: str) -> None:
async for event in app.resume(ResumeRequest(
session_id=existing_id,
messages=[Message.text("user", "Next turn")],
)):
print(event.type)
```
An existing session may instead be running, awaiting approval/input, or require
recovery. Inspect its status and pending actions before choosing the corresponding
resolution/recovery API (`cayu guide references#sessions`). Ordinary resume does not resolve
an approval.
`ExecutionProfileMismatchError.differences` reports bounded class-level categories:
`opaque_identity` means at least one compared class has process-local identity;
`other_or_unknown` means the available evidence cannot diagnose the change.
An opaque category is evidence about identity strength, not proof that reconstruction
caused the mismatch. Declare stable behavior and implementation identities from the
first run. Adding one later does not repair an existing opaque baseline.
The persisted profile format retains aggregate digests and identity strength, not
individual member identifiers or declared versions. No durable evidence extension is
introduced here. Consequently errors cannot distinguish a changed declared version
from a member addition/removal, nor name the member responsibly. Inspect the persisted
execution-profile decision, its changed classes, and the application's declarations.
At a clean resume boundary, real behavior changes require a new session or explicit
profile adoption. Pending model or tool recovery is not an adoption boundary. Never
reuse an old version declaration to conceal a change. Adoption rejection and migration
requirements remain distinct outcomes.
### Retained sessions blocked by changed registrations
Startup isolates each incompatible interruption-cascade root, keeps its checkpoint
and unknown outcomes intact, and continues with later roots and the interrupted pass.
The stale interrupting root is planned before mutation. A hard blocker is reported
without attempting repair; a typed execution-profile rejection or a manual
model-completion recovery requirement after planning is isolated too. Store failures,
invalid cursors and unclassified errors still fail startup.
Missing registrations and invalid cascade markers are reported with fixed blocker codes.
`await app.get_startup_recovery_status()` returns the latest process-local sweep. It
keeps the integer return value of `resume_pending_interruption_cascades` and separately
reports completion, scheduled roots, total blocked roots, and at most 100 blocked session
IDs with blocker codes. No prompts, arguments, exception text, checkpoints, or component
configuration appear in the result. Truncation is explicit; inspect an individual session
with the ordinary recovery planner. Repeated startups plan blocked roots read-only and
do not append duplicate rejection evidence.
The same result is available at `GET /api/recovery/startup` for `create_server`, or
`GET /cayu/api/recovery/startup` with the default mount. Configure authenticated server
access and restrict it to operators; authentication alone does not provide tenant
isolation. This noninteractive command prints JSON from the running process:
```bash
curl --fail-with-body --silent --show-error \
--header "Authorization: Bearer ${CAYU_OPERATOR_TOKEN}" \
"${CAYU_OPERATOR_BASE_URL}/api/recovery/startup"
```
A supported recovery path is to restore the complete compatible application registration
in a separate operator process against the same store, with the original declared provider,
tool and policy implementations. Quiesce other owners first. Do this only when those
implementations remain available and correct; a historical opaque process identity cannot
be recreated by adding a stable declaration later. Use the restored factory to produce
and review a fresh exact plan, then execute its allowed decisions:
```bash
cayu recovery plan previous_agent:build_app --session SESSION_ID \
--inactive-for-seconds 0 --output retained-plan.json
cayu recovery execute retained-plan.json --target previous_agent:build_app \
--execution-id restored-registration-1
```
The recovery receipt attributes the action to the explicit execution ID. It does not
adopt the changed profile or dispatch unknown external effects automatically. If the
restored plan requires an explicit model/tool outcome decision, inspect its evidence and
supply a decisions JSON file with `--decisions`; do not edit stored events or fabricate
success. If a compatible registration cannot be restored, leave the retained evidence
intact and operate healthy sessions under the new registration.
## Human pauses and elapsed limits
`RunLimits(max_elapsed_seconds=900, scope="run")` allows 900 seconds of active
run time. Durable approval and user-input waits do not consume that allowance.
The runtime retains the pause interval and its first resolution timestamp in
existing checkpoint writes; a continuation or restart preserves token, call and
cost accounting. Work after the first decision still consumes the elapsed allowance.
Use `scope="session"` when the elapsed bound should include wall-clock human waiting,
and use approval expiry when a particular grant should expire.
Histories written by older releases retain their original elapsed accounting.
A recorded `limit_reached` approval skip can be reconciled through normal
`CayuApp.resume(ResumeRequest(session_id=..., messages=[Message.text("user", "Continue")], limits=...))` without
editing stored events. Supply the session's compatible registration and the intended
limits. A valid skip remains not executed; resume never fabricates a start or
re-dispatches the skipped effect. Conflicting started or terminal evidence remains
an explicit recovery error. See the runtime contract's run elapsed-time semantics
for the exact interval and provenance rules.
## Attributing runtime time around a tool
`CayuApp` keeps a bounded, process-local timing view by default. It contains
durations, counts, byte sizes, registered tool names and public identifiers.
It never includes arguments, results, prompts, SQL, or exception messages.
Read it after consuming the run; the newest records come first:
```python
async def print_timing(app, session_id):
rounds = await app.inspect_recent_tool_round_timing(session_id, limit=20)
preparation = await app.inspect_recent_model_step_preparation_timing(session_id, limit=20)
for record in (*rounds, *preparation):
print(record.model_dump_json())
```
The private session id or a public session reference already in the local
buffer can select these observations. This read does not access the database
or grant execution/recovery authority. The buffer is shared across sessions
and defaults to 64 records, with at most 128 calls per round. Configure bounds
with `RuntimeTimingConfig(recent_capacity=..., max_calls_per_round=...)` passed
as `CayuApp(runtime_timing=...)`; `calls_truncated` reports omitted calls.
`RuntimeTimingConfig(enabled=False)` disables collection and delivery.
A `ToolRoundTiming` has aggregate `phases` plus `calls`, each with the same
twelve phase names. Shared round work, such as planning the complete approval
set and committing the transcript, belongs to the round rather than being
charged repeatedly to each call. A zero per-call value therefore does not
mean that the shared operation did not run.
| Phase | Measured work |
| --- | --- |
| `authorization` | Tool policy, approval planning, exposure checks and final dispatch reauthorization. |
| `admission` | Pending-round/policy checkpointing and capacity reservation, including reservation waits. |
| `started_persistence` | Appending `tool.call.started` and delivering its durable side effects. |
| `effect_state` | Durable tool-effect intent, transition and reconciliation records, including the intent written before dispatch and recovery's per-call reconciliation. |
| `execution` | The application's tool invocation through effect completion; process isolation includes dispatch transport. |
| `result_processing` | Terminal hooks, result validation/redaction and terminal event preparation. |
| `staging` | Preparing, validating and durably staging a terminal checkpoint, sealing its secret snapshot, refreshing staged outcomes and recording hook completion on the stage. |
| `sibling_wait` | From this call's stage until the last sibling call stages its terminal. Zero for a single-call round and for the last call to finish. |
| `publication_queue_wait` | From the end of `sibling_wait` until this call's publication starts: the rest of dispatch, sealing the round, the pre-publication read and publication of earlier calls in model order. |
| `publication` | Pinning publication timing, verifying that checkpoint, appending the terminal event and delivering its durable side effects. |
| `round_commit` | The round's closing write: preparing and atomically publishing the transcript, checkpoint and round receipt, including exact replay after a lost acknowledgement and delivery of its events. For a paused attempt, the approval or user-input pause write; for an approved or answered continuation, its approval/input close. |
| `unattributed` | Remaining round orchestration outside the named phases, such as interruption checks and pending-round reads. |
Durations use a monotonic clock and exclude nested phase intervals. Generator
consumer backpressure is excluded from the active round phases. The two waits
are measured between runtime marks instead, so they can include time the
consumer takes with the round's yielded events. Each phase also reports
`first_started_at` and `last_completed_at`, the wall-clock bounds of its observed
entries; a phase entered more than once can have a window longer than its
duration. Parallel call durations and the two waits overlap other calls' work:
do not sum them to infer elapsed round wall time. Use `duration_seconds` for
observed round wall time. For a live round with serial dispatch, the active
phases (all except the two waits) add up to it, apart from consumer time
between yielded events. An approved or answered continuation does not charge
its remaining dispatch orchestration to `unattributed`, and some continuation
dispatch paths record only their staging, so its phases can add up to less
than its duration. Each local round execution or recovery attempt emits one
record: interrupted, cancelled and approval-paused attempts are `incomplete`,
an approved or answered continuation emits its own record for the same round,
and publication after process loss is `recovered`. Closing a round that was
interrupted before its live runner started is neither. Missing phases are
zero; Cayu does not reconstruct execution time from old receipts.
A foreground subagent tool's `execution` includes the child session's elapsed
time. The child's rounds and model steps report their own records under the
child session, and their phases are not subtracted from the parent call.
`ModelStepPreparationTiming` covers the gap from the last locally observed
round commit to the next `model.started` in the same run epoch. `handoff` is
the part before the model step starts preparing; it includes consumer
backpressure on the round's final events and session-loop scheduling.
`context_policy`, automatic `recall` and `counting` are then measured
separately, and `preparation` is the remaining time inside the model step
before the provider request starts. A commit is used once: a later step with
no round in between, such as a structured-output repair, starts at its own
preparation, as does the first model step. Database costs are measured only
while an observed phase is active.
Each phase reports `store_transaction_count`, `store_lock_wait_seconds`,
`store_execution_seconds`, `store_commit_seconds` and `store_bytes_written`.
These describe the maintained native store connection paths, including
session, task, budget and knowledge work and context propagated into SQLite
worker threads. Memory/custom stores have no
native database cost measurements. SQLite counts explicit/implicit driver
transactions and autocommit reads; PostgreSQL counts actual transaction
starts, including reads. Commit time excludes no-op commits. Bytes are bound
write payload sizes, including serialized JSON, rather than physical disk
pages, WAL bytes or replication traffic. No SQL or parameter values leave
the accumulator.
Lock wait includes local lock/read-pool or PostgreSQL pool admission. SQLite
`BEGIN IMMEDIATE` and PostgreSQL `FOR UPDATE` acquisition latency is also
reported there and in SQL execution time. That database acquisition latency
includes query/network work: it is not an exact database-engine lock-wait
counter. Lock/SQL/commit counters describe components of elapsed phase time;
they are not additional durations to add to it.
To export observations, override `EventSink.emit_timing(record)` or supply
`CayuApp(timing_sinks=[...])` with an async `emit_timing` method. Event sinks
that do not override `emit_timing` receive nothing and start no delivery
worker. `LoggingEventSink` emits the content-free JSON at DEBUG only when
constructed with `log_runtime_timing=True`; the default application logger
does not. Delivery is non-durable, best effort, outside invocation authority,
and bounded by a 128-record queue plus a one-second timeout per sink by
default. Slow/full queues drop observations; sink errors increment
`app.runtime_timing_status().failed_deliveries`. They never fail the workload,
append failure events, or retry durable sink receipts.
`await app.flush_runtime_timing()` waits for queued observations, and
`await app.close_runtime_timing()` stops delivery at shutdown after one flush
bounded by the sink timeout; records it could not deliver count as dropped.
The mounted and standalone Cayu servers close timing delivery on shutdown. An
application can be used from successive `asyncio.run()` calls: queued records
move to the running loop. The records and recent view do not survive restart.
`OpenTelemetryEventSink` exports round and preparation spans with their
observed start and end, and one `cayu.tool.phases` child per call under that
call's `execute_tool` span. The per-call span starts at the call's first
observed phase and ends at its last, normally the end of its publication; each
phase event is stamped at the phase's first entry and carries its duration and
store costs. Because the call's span includes authorization before
`tool.call.started` and staging/publication after the effect, it can extend
beyond its parent. The `execute_tool` span ends at the attested
effect-completion timestamp and carries the effect-completed, terminal-staged
and publication-started timestamps as events, so the latter two fall after
its end. If a timing record arrives before the sink has seen the session or a
call's tool span, the sink holds it (at most 1,000 records) until they appear
or the session ends; `close_runtime_timing()` exports any still held under the
best known parent. A call whose tool span never arrives stays under the round
span rather than becoming a trace root.
For example, suppose `execution` takes 50 ms, staging reports three commits
and 300 ms commit time, publication reports two commits and 200 ms, and
round commit reports one commit and 100 ms. Those 600 ms are runtime
persistence cost. If the first of two serial calls also shows 2 s of
`sibling_wait`, that is the second call running; it does not mean the first
tool or its result append took 2 s. A large `publication_queue_wait` on a
later call is the earlier calls' publication and the round's sealing, which
their own `publication` and `unattributed` phases account for. Compare
transaction counts and commit time before optimizing the application tool
or changing the storage medium.
Collection adds no durable event, checkpoint or receipt write per phase.
When timing is disabled, or outside a measured phase, the SQLite connection
returns the driver's own cursors, so rows are not iterated in Python. The
provider-free benchmark can be rerun noninteractively. Pass another checkout's
`src` directory, such as the merge base, as the no-timing baseline:
```console
uv run --no-sync python maintenance/benchmark_runtime_phase_timing.py --backend sqlite --iterations 20
uv run --no-sync python maintenance/benchmark_runtime_phase_timing.py --backend sqlite --iterations 20 --rounds 5 --baseline-src ../cayu-main/src
uv run --no-sync python maintenance/benchmark_runtime_phase_timing.py --backend memory --iterations 20 --rounds 5 --baseline-src ../cayu-main/src
```
The benchmark uses no timing/event/logging sink and a no-op tool; production
provider/tool latency will change the relative overhead.
One local macOS run on a shared, loaded machine compared 100 samples per
configuration (5 fresh worker processes of 20 runs each) against the merge
base on `main`, which has no timing collection. SQLite medians were 0.848 s on
`main`, 0.838 s with timing disabled and 0.840 s enabled. Memory-store medians
were 0.722 s, 0.724 s and 0.720 s. Every difference is within about 1.2% in
either direction, below the run-to-run noise of this measurement, so it shows
no measurable overhead rather than a speedup. These small local point
estimates do not predict overhead on a different machine or storage mount.
# Structured output without credentials
This guide ships with the Cayu package, so the installed version is always available offline with `cayu guide structured-output`. [Release source](https://github.com/cayu-dev/cayu/blob/v0.9.1/src/cayu/guides/structured-output.md).
Use `StructuredOutputSpec` on `RunRequest` when the runtime must validate a JSON
value and repair invalid model output. The provider-neutral tool strategy is the
default. A successful tool-strategy run normally has an empty `final_text`; read
`outcome.structured_output.output` instead.
This complete example uses public imports only and exercises an invalid first
attempt followed by a valid repair:
```python
from cayu import (
AgentSpec,
CayuApp,
Message,
RunRequest,
ScriptedModelProvider,
StructuredOutputSpec,
run_to_completion,
scripted_structured_output,
)
provider = ScriptedModelProvider(
[
scripted_structured_output({"wrong": "shape"}),
scripted_structured_output({"invoice_total": 42.5}),
]
)
app = CayuApp(enable_logging=False)
app.register_provider(provider, default=True)
app.register_agent(AgentSpec(name="invoice_analyst", model="scripted-model"))
request = RunRequest(
agent_name="invoice_analyst",
messages=[Message.text("user", "Extract the invoice total")],
max_steps=2,
structured_output=StructuredOutputSpec(
name="invoice",
max_retries=1,
json_schema={
"type": "object",
"properties": {"invoice_total": {"type": "number"}},
"required": ["invoice_total"],
"additionalProperties": False,
},
),
)
outcome = asyncio.run(run_to_completion(app, request))
assert outcome.ok
assert outcome.final_text == ""
assert outcome.structured_output is not None
assert outcome.structured_output.output == {"invoice_total": 42.5}
assert outcome.structured_output.attempt == 2
```
`scripted_structured_output` owns Cayu's reserved submission-tool name, argument
envelope, and model finish reason. Tests and evals should not reproduce that
wire protocol.
The full event stream remains available on `outcome.events`. A successful
validation emits `structured_output.validated` with `name`, `step`, `attempt`,
`max_retries`, `valid`, `errors`, and the redacted validated `output`. The typed
`StructuredOutputResult` wrapper distinguishes valid JSON `null` from a run
that produced no validated structured output.
# Tasks, workflows and collaboration
A task records a job and its ownership. A session records an agent's execution. A worker claims and advances work. A workflow declares application coordination. These identities can relate to each other while retaining different lifecycle rules.
## Choose the execution shape
| Shape | Use it for | Next step |
| --- | --- | --- |
| Direct run | A request that the caller will consume now | [Execution model](https://cayu.dev/docs/concepts/execution-model/) |
| Dispatcher | Continuing existing durable sessions | [Workers](https://cayu.dev/docs/operate/workers/) |
| Fresh-task worker | A queued job that needs a new session | [Task API](https://cayu.dev/docs/reference/python/tasks/) |
| Foreground child | A parent waiting for a specialist result | [Sessions](https://cayu.dev/docs/concepts/sessions/) |
| Background child | Independent work with durable result delivery | [Collaboration API](https://cayu.dev/docs/reference/python/collaboration/) |
| Workflow | Explicit roles, evidence and completion rules | [Workflow API](https://cayu.dev/docs/reference/python/workflows/) |
## Keep the claim identity
A fresh-task worker uses the claim it received when attaching the session. Do not reconstruct a lease from just the task ID. When the supervised handler owns completion, use `complete_managed_task` or `fail_managed_task` with that original claim; the managed boundary coordinates heartbeat generations and terminal mutation.
A human-input or approval pause can leave a task attached to its interrupted session. That is not a fresh unowned task. Recovery and the pending action's owner determine how execution continues.
## Design collaboration around evidence
Give each role a narrow job and explicit result schema. Decide whether the parent must wait, what happens when a child fails, and which tool effects each role can request. A parent-visible delegated-action reference helps find the child that owns an approval; it cannot approve on the child's behalf.
Test child interruption, delayed result delivery, stale ownership and parent cancellation. A multi-agent answer that looks plausible does not prove that all required roles ran or that their evidence was retained.
# Choosing a ToolEffect
This guide ships with the Cayu package, so the installed version is always available offline with `cayu guide tool-effects`. [Release source](https://github.com/cayu-dev/cayu/blob/v0.9.1/src/cayu/guides/tool-effects.md).
`ToolEffect` tells Cayu what replay can do to externally meaningful durable
state. Classify the operation's behavior, not its implementation, transport,
price, or name.
- `NONE`: replay creates no externally meaningful durable mutation and needs no
downstream idempotency identity.
- `IDEMPOTENT`: the operation may mutate state, but a stable downstream
idempotency contract or key collapses repeated execution.
- `EXTERNAL`: the operation has a non-idempotent or outcome-ambiguous external
effect that generic retry must never assume is safe to repeat.
## Canonical decisions
| Operation behavior | ToolEffect | Why |
| --- | --- | --- |
| Pure computation | `NONE` | Replay creates no externally meaningful durable mutation. |
| Stable local file, database, search, or public HTTP read, including a paid or logged read | `NONE` | Transport, billing, and observability do not turn a read into a mutation. |
| Consuming read, dequeue, or one-time-token invalidation without a stable replay contract | `EXTERNAL` | The call mutates durable state and replay may consume again. |
| Read that creates a durable snapshot or artifact without a stable replay contract | `EXTERNAL` | Snapshot creation is a durable mutation and replay may create another snapshot. |
| Write or other mutating operation with a stable downstream idempotency key or equivalent replay contract | `IDEMPOTENT` | The downstream system collapses repeated execution through a stable operation identity or equivalent idempotency contract. |
| Ordinary file write, email, payment, or other mutation without such a contract | `EXTERNAL` | Replay may repeat the mutation. |
| Mutating request whose outcome is unknown after a timeout or disconnect and has no stable replay contract | `EXTERNAL` | The caller cannot prove whether the first mutation committed or safely collapse replay. |
An ordinary public HTTP GET used for remote research is therefore `NONE`. It
does not become `IDEMPOTENT` merely because it crosses a network, is logged, is
rate-limited, or costs money. Conversely, a method or tool named "read" is not
necessarily `NONE`: dequeueing work, consuming a one-time token, or creating a
durable artifact snapshot mutates state.
When behavior is uncertain, use `EXTERNAL` until the application can name and
test a stable downstream replay contract.
The classification describes replay safety; it does not mean Cayu will
automatically retry the tool.
## Act-once recovery
Use this focused protocol with the canonical `cayu guide durable-operations`
lifecycle when an external mutation may commit before its success acknowledgement
reaches Cayu. A timeout, cancellation, disconnect, worker crash, or
commit-then-raise failure can leave the local outcome ambiguous. A missing local
receipt is not proof that the external mutation did not commit.
Keep one stable operation identity through every transition:
```text
proposed -> authorized -> intent_recorded -> completed
\-> outcome_unknown
-> completed | safe_to_retry | manual_review
```
- `proposed` identifies one bounded mutation and target.
- `authorized` records authority for that exact proposal. Authority must come
from policy and an authenticated product boundary, not from this state name.
- `intent_recorded` is a durable, atomic record written before dispatch. It is
evidence that dispatch may occur, not evidence that the effect completed.
- `completed` requires a trustworthy terminal receipt tied to the same operation
identity, plus separate fresh post-action verification.
- `outcome_unknown` means dispatch may have committed but no trustworthy terminal
receipt is locally durable. Stop and reconcile before any retry.
- `safe_to_retry` is valid only when an authoritative downstream query proves the
original mutation did not commit. Recheck authorization before redispatch.
- `manual_review` is required when reconciliation is unavailable, inconclusive,
contradictory, or cannot bind its evidence to the same operation identity.
When a runtime-classified tool timeout becomes an unknown external outcome, the
`tool.effect.outcome_unknown` event retains bounded `failure_evidence` with a
`timeout` classification. This diagnostic contains neither tool arguments nor
result content and does not authorize retry or prove the external operation stopped.
An invalid tool result is not a reconciliation receipt. Its raw response fields,
including a field named `receipt_id`, are not copied into the default unknown-effect
event. Reconcile the retained logical call using the registered adapter's lookup
or a separately supplied receipt accepted by that adapter's validator.
Interruption after the runtime persists a preparation but before dispatch consumes
it is different from an unknown external outcome. Recovery atomically settles the
exact unconsumed preparation as `tool.call.failed`, with `executed=false` and
`outcome_unknown=false` in the structured result. The effect record retains no
dispatch identity or external receipt. This settlement competes with dispatch on
the same durable comparison; it never reruns the tool. Recovery plans offer
`automatic_repair` for a prepared call when ownership and registration checks
permit it. Executing that exact plan can settle an ordinary round, retaining its
completed siblings, without calling a model or tool. Re-plan after the receipt
and inspect the remaining blockers before proceeding.
Targeted grants still require their normal continuation owner after preparation
settlement. Approval and answer requirements are unchanged. Use normal resume
or the existing approval/user-input resolution entrance when continuation is
needed; automatic repair does not authorize new research or an external retry.
A terminal receipt and fresh verification answer different questions. The
receipt is downstream evidence about the attempted operation. Verification is a
new read of the resulting state. Neither can be replaced by the durable intent,
and narration by the model is neither receipt nor verification.
The following credential-free contract uses fake process-local maps only to make
the crash window deterministic. Production intent, outcomes, and reconciliation
evidence must be durable and tenant-qualified. The fake downstream system commits
the mutation and then raises before returning its success acknowledgement. The
recovery path queries that system by the same operation identity and never calls
`dispatch` again.
```python
class CommitThenRaiseSystem:
"""Fake downstream system that loses one success acknowledgement."""
def __init__(self) -> None:
self.committed: dict[str, str] = {}
self.dispatch_count = 0
self.operation_ids_seen: list[str] = []
def dispatch(self, operation_id: str, *, target: str) -> dict[str, str]:
self.operation_ids_seen.append(operation_id)
self.dispatch_count += 1
if operation_id in self.committed:
raise AssertionError("logical operation was dispatched twice")
self.committed[operation_id] = target
raise ConnectionError("lost success acknowledgement after commit")
def reconcile(self, operation_id: str) -> dict[str, str] | None:
self.operation_ids_seen.append(operation_id)
target = self.committed.get(operation_id)
if target is None:
return None
return {
"operation_id": operation_id,
"target": target,
"status": "committed",
}
def verify(self, operation_id: str) -> dict[str, object]:
self.operation_ids_seen.append(operation_id)
target = self.committed.get(operation_id)
return {
"operation_id": operation_id,
"target": target,
"verified": target is not None,
}
def run_commit_then_raise_contract() -> tuple[dict, CommitThenRaiseSystem, list]:
operation_id = "change-0001"
timeline = [
("proposed", operation_id),
("authorized", operation_id),
("intent_recorded", operation_id), # persist before dispatch
]
record = {
"operation_id": operation_id,
"target": "demo",
"state": "intent_recorded",
"receipt": None,
"verification": None,
}
external_system = CommitThenRaiseSystem()
try:
external_system.dispatch(operation_id, target=record["target"])
except ConnectionError:
record["state"] = "outcome_unknown"
timeline.append(("outcome_unknown", operation_id))
# A replacement worker loads this record and reconciles before any retry.
receipt = external_system.reconcile(operation_id)
if receipt is None:
record["state"] = "manual_review"
timeline.append(("manual_review", operation_id))
return record, external_system, timeline
record["receipt"] = receipt
if (
receipt.get("operation_id") != operation_id
or receipt.get("target") != record["target"]
or receipt.get("status") != "committed"
):
record["state"] = "manual_review"
timeline.append(("manual_review", operation_id))
return record, external_system, timeline
verification = external_system.verify(operation_id)
record["verification"] = verification
if (
verification.get("operation_id") != operation_id
or verification.get("target") != record["target"]
or verification.get("verified") is not True
):
record["state"] = "manual_review"
timeline.append(("manual_review", operation_id))
return record, external_system, timeline
record["state"] = "completed"
timeline.append(("completed", operation_id))
return record, external_system, timeline
```
For a real `safe_to_retry` result, require positive downstream evidence that the
first mutation did not commit; absence from an eventually consistent read is not
enough. Retain the original operation identity and durable uncertainty even when
an operator must finish reconciliation manually.
### Fault checklist
- **Duplicate delivery:** the same operation identity cannot describe different
targets, and recovery does not redispatch a merely uncertain operation.
- **Restart after intent:** a replacement worker sees `intent_recorded` or
`outcome_unknown` and reconciles before doing more work.
- **Concurrent workers:** use one transactional compare-and-set owner for each
transition; a process-local lock is not durable fencing.
- **Timeout or cancellation:** if it can occur after dispatch, record
`outcome_unknown`; do not translate it into failure-safe retry.
- **Commit-then-raise:** assume the mutation may have committed even though the
tool returned an exception.
- **Lost success acknowledgement:** query the downstream system using the same
operation identity instead of creating a new attempt identity.
- **Unavailable reconciliation:** retain uncertainty and require `manual_review`.
This is an act-once-or-stop application protocol. It does not create generic
exactly-once execution for arbitrary external systems. `ToolEffect` classifies
replay risk; it does not authorize execution. Policy, authenticated approval,
durable ownership, and downstream reconciliation remain separate controls.
## Inspect a receipt-reconciliation target
For an external call interrupted while resolving a user-input pause, include the
original `UserInputResponse` as `ToolEffectReconciliationRequest.user_input_response`.
The runtime compares the answer and continuation digests with the retained pause
authority before admitting recovery. Re-supply the same answer, structured data,
artifacts, metadata, actor, and review reference; a receipt does not authorize a
different answer. The response's session and task identities must match the receipt
request. Ordinary and approval-owned calls do not accept this field. Continuation
retains the paused invocation's frozen configuration; an explicit receipt
`max_steps` must agree with it. The existing user-input closure receipt proves
consumption, so identical reconciliation replay does not run the model again.
Incomplete-session recovery can report `pending_tool_effect`. It retains the
unknown call and finishes any pending session interruption without invoking the
tool or receipt validator. Inspect that call and submit an explicit receipt or
lookup request to continue; this recovery action is not a completed tool result.
After receipt selection, an abandoned reconciliation stream still needs its
explicit continuation. For ordinary tool rounds, recovery plans report
`tool_effect_continuation_required`
and do not offer generic repair or manual outcome replacement for that pending
round. Incomplete-session recovery retains the selected receipt and pending round;
replay the original reconciliation request to continue without revalidating the
receipt or executing the tool again.
For an existing external call, `CayuApp.inspect_tool_effect` supplies the exact
identity and version fields needed by `ToolEffectReconciliationRequest`. Use the
round and call identifiers from public runtime events:
```python
from cayu import ToolEffectReconciliationRequest
target = await app.inspect_tool_effect(
session_id,
tool_round_id=tool_event.payload["tool_round_id"],
tool_call_id=tool_event.payload["tool_call_id"],
)
request = ToolEffectReconciliationRequest(**target.model_dump(), lookup=True)
async for event in app.reconcile_tool_effect(request):
handle_event(event)
```
Inspection requires a public-authority alias codec shared by the app and store.
The memory store supplies an ephemeral keyring by default. Persistent deployments
must configure their store's keyring and keep it available across reconstruction;
inspection fails read-only when no codec is available.
The target uses field- and session-scoped aliases for the private idempotency key
and session incarnation. These are public references, not downstream keys to send
to an external system. A registered lookup receives the original stable key.
Inspection is read-only: it does not claim the session, invoke a reconciler,
reconnect an environment, or authorize execution. It can describe an already
terminal call, but only an eligible unresolved call may receive a new settlement.
Its versions may become stale; submission checks them rather than refreshing them.
Keep the original request for exact replay after acknowledgement loss. A newly
inspected target is not a replacement for that original request.
Once durable consumption is proven, exact replay returns the selected terminal
event without renewing task execution authority, even if continuation completed
the linked task. The original worker and handoff identities remain part of request
equality; changing them is a conflict, not a new authorization. Any recovery that
still requires execution retains the normal live task-authority checks.
For supplied receipts, request and receipt identity fields must use matching
representations. Resolving an alias does not authenticate external evidence:
the registered application validator must still verify the receipt. Never treat
a raw third-party response or an operator assertion as a validated receipt.
The selected `tool.call.completed` or `tool.call.failed` event includes a
versioned `receipt_evidence` envelope: receipt/schema identities, outcome, source,
observation time, content digest, and the registered integrity/resource fields.
The digest identifies the validated, redacted receipt stored by Cayu, not the raw
third-party response. This envelope is audit evidence, not an authorization token
or a replacement for application receipt validation. It is committed atomically
with the receipt-bound terminal outcome and retained on exact replay.
`ToolEffectReceipt.artifacts` preserves the original tool-result attachment
references through settlement and continuation. An image-only result can use
an empty `message`. The registered application validator must verify attachment
ownership and content evidence along with the exact call; adding a reference
does not authenticate it or grant artifact access. Attachment JSON is bounded
to 64 KiB within the receipt's overall 96 KiB limit. Empty attachment lists
preserve the content identity of existing receipts and reconciliation requests.
`tool.effect.reconciliation.started` is versioned attempt evidence, emitted after
exact request preflight and before application reconciliation. It records the
logical call, dispatch identity, intent/request hashes, expected versions, and
lookup mode, not the supplied receipt. It is neither receipt authentication nor
proof of an external mutation. Replaying an already selected reconciliation does
not emit another start; a new explicit attempt after interruption can do so.
The SDK stream yields this event before entering the reconciliation callback.
Closing the stream at that point leaves the effect unresolved without invoking
the callback; a later attempt still requires an explicit reconciliation request.
Admission stores the start event and its bounded attempt identity atomically in
the existing effect record, advancing that record's revision. Cancellation,
timeout, or stream closure does not erase this admission evidence. Inspect the
current target before making a new explicit request after an interrupted attempt.
An older observation cannot regain replay authority once a newer request has
been admitted.
If a new request or a native terminal outcome supersedes an admitted attempt,
that successor transaction also commits `tool.effect.reconciliation.conflict`
with `kind=reconciliation_superseded`. This event identifies the losing attempt
and successor using bounded digests; it does not assert that the old callback
returned or validated a receipt. The audit therefore does not depend on the old
caller remaining alive. Selecting an observation or receipt for the admitted
request itself consumes admission without creating a supersession conflict.
`tool.effect.receipt.validated` contains the bounded receipt evidence envelope,
without the result body or raw external response. Cayu commits it atomically with
the receipt-bound terminal result, then yields it before the terminal event.
Closing the stream at this validation event preserves the selected receipt and
resource versions. Resubmitting the exact original request resumes from that
selection without invoking the validator or protected tool again.
An application validator's `conflict` outcome emits a version1
`tool.effect.reconciliation.conflict` event with `kind=validator_rejected`.
It is committed with the unresolved observation, not a terminal tool result.
The effect stays `outcome_unknown`, retaining accepted partial resource evidence.
Exact request replay returns that observation and raises `ToolEffectConflict`
without another validator call. `not_found` and `unsupported` instead use
`tool.effect.reconciliation.observed` and also leave the effect unresolved.
## Native durable recovery evidence
Tools with an existing operation journal can implement the narrow
`DurableToolRecovery.reconcile_durable_tool_call` extension from
`cayu.tools.base`. It returns `DurableToolRecoveryEvidence` or `None`, not a
bare `ToolResult`. The evidence pairs a diagnostic result with one explicit
disposition:
- `confirmed`: the journal owner validated the complete call identity and has
positive terminal evidence. Ordinary session recovery can select that result
atomically with the external-effect record without calling the tool again.
- `not_started`: validated preparation evidence proves this journal's dispatch
did not start. This is not a generic retry authorization.
- `unresolved`: evidence is incomplete, invalid, conflicting, or ambiguous.
Its diagnostic result must not be interpreted as a terminal external outcome.
`None` means there is no usable journal evidence; it does not prove the effect
was absent. For an unresolved `EXTERNAL` call, only `confirmed` native evidence
may select a terminal result. The other dispositions retain the unresolved
call. Existing `NONE` and `IDEMPOTENT` recovery still use their diagnostic
result semantics. A caller-supplied result with identical text does not become
trusted journal evidence.
The registered journal implementation owns identity validation and disposition.
Recovery provides bounded observation and fenced journal settlement authority;
the extension must not dispatch the protected mutation. A stored state label
alone is insufficient when its result still describes an ambiguous operation.
Keep the retained allocation and journal available to this owner until the
selected result and required cleanup are durably settled. Deleting a Docker
guest before reconciliation can remove the only terminal receipt or workspace
that a fresh process can reconnect. Physical absence alone proves neither the
tool outcome nor native session/task settlement.
Manual `ToolRoundRecoveryRequest` is a continuation entrance, not a generic
observation-only cleanup API. Omitted controls restore the recorded invocation;
an explicit lower `max_steps` can still conflict with its exact finalization
profile. After the round is settled, model execution may continue. Use the
registered plan's supported failure/interruption disposition when no further
execution is authorized, and retain unresolved ownership if that disposition
cannot settle the effect. Do not weaken profile validation to make cleanup run.
## Check a `NONE` declaration before deployment
`verify_tool_effect(...)` is an explicit deployment-readiness test seam. It
invokes one registered tool against a bounded temporary Cayu workspace, then
reports the declared effect and any created, updated, or deleted paths:
```python
from cayu.testing.base import ToolEffectVerificationStatus, verify_tool_effect
evidence = await verify_tool_effect(
app,
agent_name="reporter",
tool_name="calculate_report_total",
arguments={"source": "input.json"},
workspace_files={"input.json": b'{"total": 42}'},
unobserved_systems=("reporting_database",),
)
assert evidence.status is ToolEffectVerificationStatus.CONSISTENT
```
For `NONE`, an unchanged workspace is `consistent`; any observed create,
update, or delete is a `mismatch`. This is scoped evidence, not proof that the
tool is universally pure. This first observer compares regular-file paths and
content only. Empty directories, symlinks, other non-regular entries,
permissions, timestamps, and filesystem metadata are outside its mutation
evidence, although every traversed entry counts toward the observation limit.
The result always names systems outside the boundary, including network
services, databases outside the workspace, artifact stores, runner execution,
process state, and host paths outside the temporary workspace. Add
application-specific systems through `unobserved_systems`.
The verifier supplies no runner, artifact store, vault, proxy, or knowledge
store and runs the tool directly without policy, approvals, hooks, events, or
the model loop. It rejects `ProcessIsolatedTool`; those tools must enter through
Cayu's runtime-owned process boundary so their hard deadline, containment, and
recovery contracts remain active. Build a fresh application with controlled
adapters for this test: the current Python process is not a security sandbox,
and tool-instance state is not observed. One cooperative asyncio deadline
covers workspace seeding, tool execution, both snapshots, and cleanup checks.
If it expires, the helper raises `TimeoutError` and returns no verdict because
observation did not complete. A tool or filesystem operation that blocks the
event loop can delay that failure; enforcing a hard wall-clock stop requires a
killable process boundary. Snapshots stop at configured traversed-entry and
regular-file caps, and bound per-file and total content bytes. Deadline and
observation-limit failures therefore fail closed.
`IDEMPOTENT` and `EXTERNAL` declarations require the explicit
`allow_effectful_execution=True` opt-in. They execute once and return `observed`,
which records workspace changes but does not claim replay safety. Use a
domain-specific test for the downstream idempotency or reconciliation contract.
`cayu check` remains structural and never invokes this verifier or application
tools.
## Keep other controls separate
`ToolEffect` does not authorize execution. Authorization and approval belong in
tool, command, and network policy. Billing, budgets, quotas, and rate limits are
cost-governance controls. Taint tracks information flow. Events and telemetry
provide observability and audit evidence.
A `NONE` tool can still be expensive, sensitive, denied by policy, or heavily
audited. An `IDEMPOTENT` tool is not automatically authorized. Classify replay
semantics here, then configure those orthogonal controls explicitly.
# Add a domain tool
A native tool is a registered Python capability that a model may request. It subclasses `Tool`, declares `ToolSpec`, and implements `async def run(ctx, args) -> ToolResult`.
## Generate a slice
In the generated project, inspect the available agents and generator options:
```sh
uv run --no-sync cayu inspect --json
uv run --no-sync cayu generate tool --help
```
Choose the existing agent name and a deliberate effect classification. `cayu generate tool NAME --agent AGENT --effect EFFECT` generates a schema, implementation, runtime test and eval. Replace the placeholder behavior and expected results before calling the slice complete. Read [generator CLI](https://cayu.dev/docs/reference/cli/generate/) for the exact accepted effect values and planning options.
## A complete read-only implementation
Save this as `lookup.py` to verify the direct tool contract without model credentials:
```python
from cayu import Tool, ToolContext, ToolEffect, ToolResult, ToolSpec
class LookupTool(Tool):
spec = ToolSpec(
name="lookup",
description="Look up a reviewed reference record.",
input_schema={
"type": "object",
"properties": {"query": {"type": "string"}},
"required": ["query"],
"additionalProperties": False,
},
effect=ToolEffect.NONE,
)
async def run(self, ctx: ToolContext, args: dict) -> ToolResult:
return ToolResult(
content=f"Found {args['query']}",
structured={"session_id": ctx.session_id},
)
result = asyncio.run(LookupTool().run(ToolContext(session_id="test-session"), {"query": "Cayu"}))
assert result.content == "Found Cayu"
assert result.structured == {"session_id": "test-session"}
print(result.content)
```
Run `python lookup.py`; the expected output is `Found Cayu`. A [downloadable copy](https://cayu.dev/docs/examples/lookup.py) is checked against the released wheel.
## What this proves
Calling the tool directly tests caller-supplied arguments and the implementation. To prove schema validation, policy authorization, resource admission and recorded events, invoke it through an actual Cayu session with a deterministic provider. Preserve that generated runtime test seam.
`ToolResult.structured` and artifacts are recursively read-only after construction. Use `model_dump(mode="json")` for ordinary JSON serialization. Report expected domain failures with an error result rather than success prose.
Read [tool effects](https://cayu.dev/docs/build/tool-effects/) before adding mutations and [service tools](https://cayu.dev/docs/build/service-tools/) before wrapping a durable service.
# Verified work
This guide ships with the Cayu package, so the installed version is always available offline with `cayu guide verified-work`. [Release source](https://github.com/cayu-dev/cayu/blob/v0.9.1/src/cayu/guides/verified-work.md).
Use verified work when a task may only count as done after an independent check
of durable evidence. The application freezes a `WorkContract`; a
`VerifiedTaskWorker` runs bounded attempts; a registered verifier decides each
proposal; and only an applied, accepted decision completes the task.
The credential-free reference is
[`examples/durable_file_workflow/verified.py`](https://github.com/cayu-dev/cayu/blob/v0.9.1/examples/durable_file_workflow/verified.py).
Its sibling `demo.py` shows the lighter alternative: application code checks the
artifact itself and completes an ordinary task. Choose verified work when the
check, its evidence, and the rejection history must be durable and attributable.
For the general observe/propose/act-once lifecycle, see
`cayu guide durable-operations`.
## Shortest deterministic journey
```text
create contract -> create bound task -> attempt 1 -> proposal -> verifier rejects with a gap
-> continuation on the same session -> attempt 2 -> proposal -> verifier accepts
-> resolver rebuilds the result -> decision applied -> task completed -> binding retired
```
From a repository checkout:
```sh
uv run python -m examples.durable_file_workflow.verified
uv run python -m examples.durable_file_workflow.verified --store sqlite
uv run pytest tests/examples/test_verified_file_workflow.py
```
The scripted model's first program forgets the trailing newline. The verifier
rejects that attempt with the gap `artifact.missing_trailing_newline`, the model
receives exactly that gap, repairs the program, and the second attempt is
accepted. The run also loses one acknowledgement after a durable write and
recovers without rerunning the model or the program. A worker that rewrites the
workspace copy of its input to match a forged output is rejected, because the
verifier reads the expected input from the task record instead.
## Five layers, five owners
Keep these separate. Each one proves less than people often assume.
| Layer | Owner | Proves | Does not prove |
| --- | --- | --- | --- |
| Structured-output validation | provider request and schema | a response has the expected shape | that any work happened or is correct |
| Completion proposal | `VerifiedTaskHandler.propose` | the worker claims one digest-bound result, with optional evidence | success; final model prose is never a proposal or a verdict |
| Verifier decision | the registered `DeterministicCompletionVerifier` | a verdict for every criterion and constraint, with cited evidence and gaps | anything about other attempts; it never mutates state |
| Result reconstruction | the registered `CompletionResultResolver` | the accepted result can be rebuilt from application evidence | correctness beyond the decision; Cayu only accepts a result whose digest equals the proposal's |
| Task ownership | Cayu decision application | the task completes once, through a durable receipt | that external side effects happened exactly once |
While a contract binding owns the session, ordinary `complete`, `fail`, and
`cancel` operations on the task are fenced, and queued steering is rejected.
Before its first attempt is admitted, a bound task can still be failed or
cancelled through the ordinary task APIs, but never completed; only an applied,
accepted decision completes it.
Acceptance retires the binding (`retired_contract_binding` on the lifecycle
receipt), after which the session behaves like any other.
## Contract anatomy
`CayuApp.create_work_contract(WorkContractDraft(...))` publishes an immutable,
fingerprinted contract. It carries:
- `objective`: what the work is for.
- `criteria`: ordered `WorkCriterion` entries (`ordinal` 1..n) that a verifier
must judge individually.
- `constraints`: `WorkConstraint` entries that must hold for any acceptance.
- `evidence_requirements`: `WorkEvidenceRequirement` kinds that satisfied
criteria and constraints must cite.
- `verifier` and `result_resolver`: exact references (id, version, and
configuration fingerprint; the verifier reference also names its kind). Register the matching adapters with
`register_completion_verifier` and `register_completion_result_resolver`
before any worker runs.
- `continuation_policy`: what a rejection means, and the attempt and
repeated-gap ceilings.
Create the task with `CayuApp.create_task(TaskCreate(task_id=..., work_contract=contract.reference()))`.
A bound task needs a caller-stable `task_id`. Ordinary task workers never run
bound tasks; they park one as `needs_attention` with
`verified_work_contract_runner_required`.
The handler is two read-only, retry-safe callbacks. `prepare` returns the
`RunRequest` for the first attempt; the worker assigns task, session, and lease.
`propose` reads the attempt's evidence and returns a `CompletionProposalCreate`
with the supplied `proposal_id` and `attempt_id`. In an uninterrupted run,
`prepare` runs once per task and `propose` once per attempt, but either may run
again during recovery, so neither may perform the domain operation.
## Deterministic verifiers first
Start with a deterministic verifier: plain code that reads application-owned
evidence and returns a `CompletionVerifierDecision`. It must be side-effect free,
declare a stable `execution_profile_identity`, and cover every criterion and
constraint exactly once, in contract order. Declare each decision-bearing
dependency that is not part of the adapter, such as where expected values come
from, as a `CompletionVerifierProfileComponentDeclaration` in
`execution_profile_components`. Cayu fingerprints them, so a dependency that
changes after registration, or between attempts and restarts, fails closed
before the verifier runs instead of silently changing how work is judged.
- Read expected values from application-owned state, such as the task record or
your own database, not from the worker's workspace. The worker's copy of an
input is evidence to check, not the reference to check against.
- That boundary holds only if the worker's execution cannot reach the
application's state either. The reference example runs tools locally with no
sandbox, so in its SQLite mode a worker program could edit the task database
directly. In production, run worker tools in a sandboxed environment that
cannot reach your stores or the verifier's inputs.
- Read worker-written files with bounded reads. The example uses
`LocalWorkspace.read_bytes(path, max_bytes=...)`, which runs off the event loop
and refuses non-regular files, so a pipe or an oversized file cannot stall the
verifier.
- An accepted decision marks every outcome `satisfied` and has no gaps.
- A satisfied outcome whose subject declares evidence requirements uses
`satisfaction_basis=evidence` and cites an available reference for every one.
- A non-accepted decision carries at least one `CompletionGap` for every
unresolved outcome and none for satisfied ones. Gaps must be unique and sorted:
criterion gaps before constraint gaps, then by subject ID, code, and evidence
requirement IDs. This is not contract order, so sort them explicitly.
- Gap `code` values are stable machine codes; the `summary` is for people and
does not affect repetition detection.
Provider-backed judging is optional, governed work, not a shortcut around this
contract. The `provider` verifier kind is reserved: a contract may name it, but
registering or running such a verifier is rejected today. When a
model judgment is genuinely needed, run it as its own budgeted, attributable
step, persist its output as evidence, and let a deterministic verifier decide
whether that evidence satisfies the criteria. Calibrate such judges with
`cayu guide evals-ai-quality`.
## Attempts, repeated gaps, and budgets
A rejected attempt is neither a failed task nor a completed one. It is a durable
decision about one proposal. What happens next is frozen in the contract. The
common outcomes are:
| Outcome | Task status | Status reason |
| --- | --- | --- |
| Accepted | `completed` | (none) |
| Rejected, `rejection_action=continue`, below every ceiling | stays `running` | a continuation attempt starts on the same session |
| Rejected, `rejection_action=interrupt` | `paused` | `work_contract_rejected` |
| Rejected at the `max_attempts` attempt | `needs_attention` | `work_contract_attempt_limit` |
| The same gaps recur `max_repeated_gap_count` times after their first rejection, before the attempt limit | `needs_attention` | `work_contract_repeated_gap_limit` |
| Verifier verdict `blocked` | `blocked` | `work_contract_blocked` |
| Verifier verdict `needs_review` | `needs_attention` | `work_contract_needs_review` |
| Elapsed-time or budget limit reached | `needs_attention` | `work_contract_elapsed_limit` or `work_contract_budget_limit` |
| Run paused for approval or user input, or stopped by a run limit | `needs_attention` | `work_contract_execution_interrupted` |
Handler, preparation, and execution failures, and cancellation through a task
group, have their own status reasons; none of them completes the task.
Budget and step ceilings come from the `RunRequest` that `prepare` returns: its
`limits`, `budget_limits`, and causal budget are frozen with the first attempt and
reused unchanged by every continuation. Each continuation is a new run, so what
the ceiling covers depends on its scope. `RunLimits` defaults to `scope="run"`,
which resets for every attempt: a four-tool-call run limit lets each attempt make
four calls. Use `scope="session"` for a ceiling across all attempts, and session,
causal, or app-scoped budget limits for spend across the whole task. The
elapsed-time ceiling is the worker's `max_elapsed_seconds`, measured from when
the task's preparation starts and tightened by any `RunRequest.execution_deadline`;
it is separate from `RunLimits.max_elapsed_seconds`. A verifier that cannot judge at all, for example because
the application's own input is missing, should return `blocked` rather than
`rejected`, because another attempt by the worker cannot fix it.
A continuation never edits the contract. The model's next user message is a JSON
document of type `cayu.verified-task-continuation.v1` carrying the rejected
`decision_id` and its gaps, so the next attempt acts on the verifier's
authoritative findings rather than on its own earlier prose. The repeated-gap
ceiling compares each rejection's gap fingerprint (its gaps' subjects, codes,
and evidence requirement IDs, plus the unresolved outcomes' statuses and reason
codes) with every earlier rejection of the same task. Summaries are excluded, so
rewording a gap does not reset the count. The example sets
`max_repeated_gap_count=1`, so the same gaps twice stop the task. No ceiling ever
fabricates success.
## Recovery and lost acknowledgements
Contracts, attempts, proposals, decisions, and receipts live in the task store.
A replacement process rebuilds `CayuApp` over the same stores, registers the
same verifier and resolver references, and starts a new `VerifiedTaskWorker`.
The worker discovers unfinished attempts from durable evidence:
- A committed proposal is not executed again; the model and its tools do not
rerun.
- A proposal receives at most one committed decision. A stored decision is
reused, but if a verifier claim expires before its decision is recorded, the
verifier runs again, so it must be deterministic and side-effect free.
- An accepted chain settles once. The resolver may likewise rerun after a lost
claim, and Cayu accepts only a result matching the proposal's digest.
The reference example simulates a reply lost after attempt 2's proposal commits.
After the restart, the model call count and the program's external-effect count
are unchanged, the verifier runs once for that attempt, the resolver runs once,
and exactly one `task.completion_result.resolved` event exists. Every proposal,
decision, and application receipt recorded before the restart is equal
afterwards, and calling `resolve_completion_result` again with the
decision application receipt's idempotency key returns the stored task without running the
resolver again.
Recovery does not make external effects exactly-once. Make the tools inside an
attempt idempotent or act-once (`cayu guide tool-effects`), and treat `propose`,
the verifier, and the resolver as read-only.
For durable stores, give `SQLiteSessionStore` a stable
`public_authority_alias_codec` (for example from
`public_authority_alias_codec_from_environment()`) so a restarted process can
verify workspace aliases written before it. The worker never closes stores. Use
it as an async context manager; if closing reports `VerifiedTaskWorkerDraining`,
alone or inside an exception group, keep the worker and its stores and retry
`aclose()` until it returns. A cancellation is reported as a cancellation, so
treat a cancelled worker's stores the same way the application treats any
cancelled operation that may still have work in flight. Close the worker before
`app.aclose()`, which waits for an attempt still executing before its stores can
close. A worker left running stops claiming once the app closes, and an attempt
cut off by shutdown records no terminal outcome, so recovery resumes it.
## The same concepts in other domains
| Domain | Objective | Criteria | Evidence | Deterministic verifier | Resolved result |
| --- | --- | --- | --- | --- | --- |
| Terms negotiation | agree terms with a counterparty and send them | terms within the principal's preferences; every required fact present; the correct approval recorded | the negotiated terms revision, its approval decision, and the durable send receipt | checks the revision against the preferences, and the approval and receipt against that exact revision; a draft or a model's claim is not completion | reference to the sent, approved terms |
| Utility-bill reconciliation | reconcile one billing period | every meter read matched; totals within tolerance | the bill version and the ledger entries it cites | recomputes totals from the cited versions | the reconciliation record |
| Bid readiness | publish a complete bid package | every required section present; required approval recorded | the immutable package version and the approval decision | checks sections and the approval against the package digest | the published package reference |
| Revision-bound code review | approve one exact revision | checks pass; required findings addressed | the commit digest, check results, and review notes for that commit | rejects when evidence names a different revision | the approved revision reference |
In each row the worker proposes, the verifier decides from evidence, the
resolver rebuilds the result from that same evidence, and Cayu owns the task
transition. Larger workflows that must apply an exact set of business actions
once each need a dedicated recipe on top of this foundation; this guide covers
one contract-bound task.
# Authority and effects
A tool schema defines accepted arguments. A tool's effect classification defines replay and mutation risk. A policy determines whether an invocation may proceed. A human approval grants authority for a bounded action. Each answers a different question.
## Use a protected action as an example
An order tool can accept `proposal_id` in a valid JSON object while still lacking permission to execute that proposal. A prompt saying “ask first” helps model behavior, but runtime policy must enforce the gate. A durable approval should bind the actual action and identities being authorized.
External content may supply facts without supplying authority. Treat a customer message, web page or tool result according to its origin. Keep application-selected tools, policy and credential boundaries outside model-controlled prose.
## Execution and infrastructure
Native Python tools are trusted application code running in the host process. Invocation policy does not sandbox their Python implementation. Tools that execute untrusted code require an appropriate runner and explicit execution boundary. Read [environments](https://cayu.dev/docs/build/environments/) for workspace, runner, artifact and credential roles.
## Verify the gate
Test a denied or paused request, an authorized request and a stale or mismatched approval. Independently inspect the downstream effect. The [human input guide](https://cayu.dev/docs/build/approvals/) and [durable operations guide](https://cayu.dev/docs/build/durable-operations/) explain the supported APIs.
# Durability and recovery
Durability preserves recorded facts across process failure. Consider an agent placing a replacement order: the service accepted the request, but the worker died before writing its final result. The durable conversation survives; the missing local result does not establish whether the service acted.
## Three kinds of evidence
| State | Next decision |
| --- | --- |
| A result was durably recorded and remains valid | Reuse it according to the configured replay contract |
| Work was not admitted or the service proves no effect | Continue under the applicable authority and retry policy |
| The external effect is unknown | Reconcile through a stable action identity or service receipt |
An idempotency key identifies the downstream action. A receipt demonstrates what the downstream service accepted. The runtime checkpoint establishes what Cayu recorded. A correct recovery path compares these rather than inventing a success or retrying blindly.
## Configure the boundary
Declare [tool effects](https://cayu.dev/docs/build/tool-effects/), choose appropriate stores, preserve stable service identities and make authorization reconstructible. The [durable operations guide](https://cayu.dev/docs/build/durable-operations/) composes observation, proposal, approval, action, independent verification and recovery.
Recovery may preserve a pause or reject incompatible evidence. It need not turn every stopped session into an automatically running one. Inspect the reported recovery result and unresolved work. Read [operating recovery](https://cayu.dev/docs/operate/recovery/) and try the [support-agent recipe](https://cayu.dev/docs/recipes/support-agent/).
# Execution model
Imagine a support agent investigating a damaged order. The user sends a message, the model requests an order lookup, the tool returns facts, and the model asks which item was damaged. That work has several identities.
```text
Session: durable conversation and state
Interaction: one admitted run or continuation
Model step: one decision in the model/tool loop
Provider attempt: a concrete try at that model call
Tool round: the calls requested by the accepted response
Tool call: one independently recorded invocation
```
A provider retry is another attempt; it does not become a second user request. Parallel tools share a round but have distinct call identities. Pausing for user input ends that interaction with durable state that a later continuation can address.
## Why identities matter
Use the session identity to retrieve the conversation. Use the round and call identities to inspect one pending tool approval or reconcile a result. Keep a business operation's stable idempotency identity when a downstream service must deduplicate effects. The runtime's execution identity and the downstream service's action identity serve different responsibilities.
If a provider request times out, inspect whether an attempt has a known terminal result before deciding what can safely retry. If a tool call mutates an external service, a missing local terminal event leaves an outcome to reconcile.
## Events and outcomes
`CayuApp.run(...)` produces an asynchronous event stream. Consume it incrementally for ongoing work. `run_to_completion(...)` consumes the same runtime path and returns a typed `RunOutcome`; it retains events and suits bounded runs.
Runtime failures can appear as terminal `session.failed` events. A stream iterator completing does not by itself mean success. Check the terminal state and relevant evidence. Read [sessions](https://cayu.dev/docs/concepts/sessions/) and [observability](https://cayu.dev/docs/operate/observability/) next.
# Concepts
Cayu is an open-source Python framework for building and running domain-specific long-horizon agents, with a durable runtime built in.
Your **harness** is the domain-specific system you assemble around the model: instructions, context, tools, policies, memory and verification. The integrated **runtime** executes that system and records its state. The resulting **agent** is your product for a defined job.
## The framework and its components
| Term | Role |
| --- | --- |
| Framework | Cayu's Python APIs, components and defaults for building and running agents |
| Harness | The domain-specific system assembled from those components |
| Runtime | The integrated execution subsystem for sessions, state, authority, workers, budgets and recovery |
| Agent | The resulting application that performs a defined job |
| Cloud | Optional deployment and shared operation of Cayu agents |
Use the framework for models and structured output, tools and integrations, context and memory, web/browser capabilities, multi-agent coordination, workspaces, verification and evals. The runtime executes those components within the stores, policies and execution boundaries you configure. See [Build agents](https://cayu.dev/docs/build/) for implementation paths and the [capability comparison](https://cayu.dev/comparison/#capabilities) for versioned public evidence.
## Keep ownership clear
| Owner | Responsibility |
| --- | --- |
| Application | User interface, customer identity, business authorization, domain rules and completion criteria |
| Cayu runtime | Sessions, model/tool execution, recorded events, checkpoints, limits and configured recovery |
| Model provider | Model inference and provider-specific capabilities |
| Downstream service | Its actual external effect and idempotency/receipt contract |
| Human or policy authority | The decision that authorizes a consequential action |
The operator dashboard exposes runtime state; your application owns the customer experience. Cayu Cloud is an optional operating workspace.
## Learn in order
Start with the [execution model](https://cayu.dev/docs/concepts/execution-model/), then [sessions and runs](https://cayu.dev/docs/concepts/sessions/), [durability](https://cayu.dev/docs/concepts/durability/) and [authority](https://cayu.dev/docs/concepts/authority/). The [runtime learning path](https://cayu.dev/docs/learn/) teaches these through concrete examples and checks your understanding.
When you need exact fields, use [Python reference](https://cayu.dev/docs/reference/python/). When you need an implementation path, use [Build agents](https://cayu.dev/docs/build/).
# Sessions and runs
A **session** holds a durable conversation, events and recovery state in the selected store. A **run** performs admitted work against an application graph. Each process constructs its own `CayuApp`; shared stores carry coordination across those processes.
## Choose the entrypoint
| Need | Entry point |
| --- | --- |
| A new immediate interaction | `CayuApp.run(RunRequest(...))` |
| Continue a stored conversation | `CayuApp.resume(ResumeRequest(...))` |
| A descendant with an independent future | `CayuApp.fork_session(...)` |
| Placement through configured dispatch | Dispatch API and dispatcher |
| Durable queued or scheduled work | Task store plus a worker |
| Model-selected bounded delegation | Subagent tool and child-session policy |
Resume retains the session's recorded history and admitted execution semantics. Changing an agent's current prompt does not silently replace that history. Use an explicit fork policy when a descendant must install a changed prompt or execution profile.
## Persistence and concurrency
The generated application configures durable local stores. A bare `CayuApp()` uses in-memory defaults suited to tests and bounded examples. Two apps constructed in separate processes share state only when configured against suitable shared stores.
For concurrent server and worker deployments, select a supported shared store and explicit process ownership. An in-memory object or module global cannot coordinate them. [Application anatomy](https://cayu.dev/docs/get-started/project-anatomy/) explains the boot and shutdown contract; [storage](https://cayu.dev/docs/operate/storage/) explains selection and migration.
## Inspect before continuing
Use the dashboard or [session CLI](https://cayu.dev/docs/reference/cli/session/) to retrieve events, transcript, status and pending actions. Resolve the exact pending action under its owning session. Replaying a message is not an approval, and retrying a run is not proof that an uncertain external action did nothing.
# Documentation map
This manual targets the latest published Cayu release, named in the [release manifest](https://cayu.dev/docs/release.json). Source-matched guides, statically generated public Python APIs, wheel-generated command help and default server schemas all use that release.
| Path | Reader goal | Starting point |
| --- | --- | --- |
| Get started | Create and test one conventional agent project | [Quickstart](https://cayu.dev/docs/get-started/quickstart/) |
| Concepts | Understand execution, state and authority | [Concepts](https://cayu.dev/docs/concepts/) |
| Build | Add capabilities at the correct owning seam | [Build agents](https://cayu.dev/docs/build/) |
| Evaluate | Decide what evidence establishes success | [Evaluate](https://cayu.dev/docs/evaluate/) |
| Operate | Serve, coordinate, inspect and recover work | [Operate](https://cayu.dev/docs/operate/) |
| Recipes | Follow one complete product job | [Recipes](https://cayu.dev/docs/recipes/) |
| Learn | Follow worked code from a first project to restartable approval | [Learning path](https://cayu.dev/docs/learn/) |
| Reference | Look up exact imports, schemas, flags and defaults | [Reference](https://cayu.dev/docs/reference/) |
## Source and proof
The [release manifest](https://cayu.dev/docs/release.json) pins the public source archive by SHA-256. Package-shipped guides are published from that archive; every generated Python definition links to its versioned source. CLI and OpenAPI generation construct the released package without launching a listener or contacting a model provider.
The downloadable lookup and structured-output examples use public imports. The support fixture uses the package's credential-free example across separate processes. Conceptual diagrams and exercises are labeled as teaching examples; their prose does not imply a live-provider qualification.
The generated [page index](https://cayu.dev/docs/pages.json) also exposes each page as plain Markdown. Detailed Python definitions are discoverable through the [API index](https://cayu.dev/docs/python-api.json), including optional exports omitted by wildcard imports.
# Evaluation in CI
Run deterministic tests on every change. Run live-provider evaluations when the change requires semantic evidence, using explicit credentials, concurrency and cost limits. Keep the application and judge configuration with the result so comparisons have a defined meaning.
## Establish the baseline
Create a representative suite, run it, inspect individual cases and approve the resulting immutable baseline. Include failure and authorization cases alongside successful requests. Stable case IDs identify the logical contract; revisions identify its contents.
Save candidate and baseline result exports, then compare them:
```sh
cayu eval compare baseline.eval-result.json result.eval-result.json \
--json --output comparison.json
cayu eval report result.eval-result.json --html --output report.html
```
Comparison exits `0` for a compatible result without regression, `1` for a compatible regression and `2` when the result cannot support a conclusive decision. Treat `2` as a check requiring attention. Changed evidence, authority, assertion semantics, judge configuration or trial policy can make two results incomparable.
## Retain the proof
Archive suite revision, candidate version, result JSON, comparison and report. Record whether the candidate was newly executed or a retained session was graded. Retain every repeated trial, including evaluator errors and unavailable evidence.
Use [first evaluation](https://cayu.dev/docs/evaluate/first-eval/) for authoring and launch, [judges](https://cayu.dev/docs/evaluate/judges/) for semantic quality, and [eval CLI](https://cayu.dev/docs/reference/cli/eval/) for all flags.
# Run your first evaluation
This guide ships with the Cayu package, so the installed version is always available offline with `cayu guide evals-first`. [Release source](https://github.com/cayu-dev/cayu/blob/v0.9.1/src/cayu/guides/evals-first.md).
Use this guide when you want to turn expected agent behavior into a reusable
regression suite. The ordinary path is entirely in Control Plane: the browser
authors bounded data, while the running Cayu application remains the only
source of provider, tool, environment, policy, and credential authority.
## Start the project
A conventional project created by `cayu new` already publishes one safe eval
target per registered agent. It also uses the project's normal SQLite or
PostgreSQL storage for suites, runs, results, and baselines.
For trusted local development:
```console
uv sync --extra dev
uv run --no-sync cayu check --fail-on warning --json
uv run --no-sync cayu serve --dev
```
Open `http://127.0.0.1:8000/cayu/evals`. `--dev` is loopback-only trusted-local
access. A deployed Control Plane needs its normal authentication and durable
store; do not expose `OpenAccess` publicly.
The Evals page names each operation that is ready and each missing dependency.
Fix a readiness diagnostic before running. The browser cannot create a missing
provider, credential, tool, environment, policy, or store.
## Create one deterministic suite
1. Choose **New evaluation**.
2. Give the suite and first case stable IDs and useful names. IDs identify the
logical contract; editing content creates a new immutable revision.
3. Replace the sample input with a representative user request.
4. Keep the default completed-root expectation. Add **Final output contains**
for one stable fact the answer must include. Prefer domain facts over exact
prose when wording may vary.
5. Leave **Trials per case**, **Required passes**, and **Maximum concurrency**
at `1` for the first run.
6. Choose **Check suite**. Review the canonical revision and every readiness
diagnostic, then choose **Save revision**.
7. If the target publishes compatible pricing, optionally enter a **Maximum
cost per trial**. Choose **Check launch** and review the maximum candidate
work, the accepted interruption threshold, and whether cost is hard-bounded
or observed-only. Choose **Run selected** only when that exposure is
acceptable.
The result separates candidate status, deterministic assertions, runtime
health, evidence availability, usage, and observed or unavailable cost. Open a
case and trial before trusting the aggregate status.
## Grow and reuse the suite
- **Duplicate** copies the active case and its expectations under a new stable
ID. Change only the stimulus or expected behavior you intend to vary.
- The checkboxes beside cases define an explicit launch subset. Select every
case for a full-suite run; clear unrelated cases for a focused run.
- Editing a loaded suite never mutates the saved revision. Check and save again
to create the next immutable revision.
- A repeated-trial policy retains every outcome. Required passes are not a way
to hide evaluator errors or unavailable evidence; those still fail closed.
After a representative passing result, choose **Approve baseline**. Future
compatible results compare against that exact result revision. Cayu permits a
new application release to compare, but changed cases, assertions, evidence,
execution authority, judge semantics, trial policy, or applicable pricing may
make results explicitly incomparable.
Download JSON for automation and standalone HTML for review. The same files are
accepted by the package CLI:
```console
cayu eval report result.eval-result.json --html --output report.html
cayu eval compare baseline.eval-result.json result.eval-result.json \
--json --output comparison.json
```
`cayu eval compare` exits `0` for a compatible comparison with no regression,
`1` for a compatible regression, and `2` when no conclusive decision is safe.
## Choose the next guide
- Use `cayu guide evals-ai-quality` when deterministic facts do not capture
correctness, groundedness, helpfulness, or tone.
- Use `cayu guide evals-production` to evaluate a retained production session,
a controlled multi-stage scenario, tool behavior, or memory use.
- Use the repository's `docs/evals.md` for complete SDK, schema, HTTP, storage,
and embedding contracts.
# Evaluate an agent
Start with the cheapest evidence that can decide whether the agent did its job. Use code for exact facts, runtime tests for execution contracts, and a model judge for semantic quality that code cannot reliably decide.
| Question | Evidence | Guide |
| --- | --- | --- |
| Does this tool return the right value? | Direct unit test | [Test layers](https://cayu.dev/docs/evaluate/testing/) |
| Does policy block a write? | Full runtime trajectory and unchanged domain state | [Authority](https://cayu.dev/docs/concepts/authority/) |
| Does the agent handle a representative request? | A fresh candidate run with stable assertions | [First evaluation](https://cayu.dev/docs/evaluate/first-eval/) |
| Is the answer grounded and useful? | Evidence-based rubric with a qualified judge | [Quality judgments](https://cayu.dev/docs/evaluate/judges/) |
| What happened in a retained session? | Historical trajectory evaluation | [Production evaluation](https://cayu.dev/docs/evaluate/production/) |
| Did a release regress? | Compatible baseline and candidate result | [CI workflow](https://cayu.dev/docs/evaluate/ci/) |
Keep evaluator failure separate from candidate failure. Missing evidence cannot establish a pass. A historical judgment does not execute changed code, and a deterministic provider does not establish live-model answer quality.
# Evaluate semantic quality with an AI judge
This guide ships with the Cayu package, so the installed version is always available offline with `cayu guide evals-ai-quality`. [Release source](https://github.com/cayu-dev/cayu/blob/v0.9.1/src/cayu/guides/evals-ai-quality.md).
Use an AI judge for behavior such as correctness, groundedness, usefulness, or
tone that cannot be reduced to a stable runtime fact. Keep deterministic checks
for completion, required tools, approvals, artifacts, budgets, and evidence.
An AI judgment complements those facts; it does not replace them.
## Declare judge authority
A generated project never infers judge authority from an API key or from the
candidate's route. Declare one provider, model, privacy policy, and same-model
decision in `pyproject.toml`:
```toml
[tool.cayu.evals]
price_book = "bundled-public"
[tool.cayu.evals.default_judge]
provider = "anthropic"
model = "claude-sonnet-4-6"
privacy_policy = "public-only"
allow_same_model = false
timeout_seconds = 120
max_input_tokens = 32768
max_output_tokens = 4096
max_total_tokens = 36864
max_estimated_cost = "0.1"
cost_currency = "USD"
```
The provider must already be registered by the application and authenticated
through its normal credential path. `public-only` permits final output and
bounded public reference truth. Use `public-and-transcript` only after deciding
that the judge may receive the retained transcript. Neither option permits
private reference content; specialized private references remain explicit
application-owned server configuration.
The explicit `bundled-public` selection uses Cayu's packaged, dated public-rate
snapshot for candidate and judge budgets. It is convenient for public API
pricing, but it is not a substitute for an application-owned book when your
gateway, region, or negotiated rates differ. Cayu does not infer or merge
pricing. The judge's cost ceiling requires the selected book and appears in the
published profile.
If the declared judge provider and model equal the candidate route,
`allow_same_model = true` is also required to make that judge selectable for
the candidate. The user must still deliberately choose **Add same-model AI
judge** in Control Plane. The result and report retain that label. An
independent model is usually a stronger quality signal, while same-model
judging can be useful for an inexpensive first pass.
Restart `cayu serve` after changing the declaration. Evals should show
**Project default judge** with the exact public provider/model, privacy,
token/time ceilings, and candidate-route relation. A missing or mismatched
provider fails configuration instead of silently choosing another route.
## Author a rubric
Open **Evals → New evaluation**, select the case, and choose **Add AI judge** or
**Add same-model AI judge**.
1. Give the assertion and rubric stable IDs.
2. Define one to eight criteria. Each criterion needs a stable ID, a short name,
a concrete description of good behavior, and a canonical weight. The weights
must sum exactly to `1`.
3. Set the pass threshold between `0` and `1`. Cayu computes the weighted total;
the model cannot choose the aggregation rule.
4. Choose **Final output only** unless the criterion genuinely needs retained
transcript evidence and the declared privacy policy permits it.
5. Add a public expected answer or expected facts when the task has trusted
reference truth. Reference material is sent only to the judge, never to the
candidate.
Write criteria that a reviewer could score consistently. Separate factual
correctness, groundedness, and task completion rather than asking for one vague
"quality" score. Do not place secrets, credentials, private customer data, or
instructions for the candidate in a public reference.
## Calibrate before gating
Choose **Check suite** so the server compiles the exact rubric, reference,
profile, and evidence selection. The calibration panel then uses that reviewed
assertion.
Enter a fixed candidate output, the task it answered, and human scores for each
criterion. **Check calibration** shows the exact judge work and cost exposure;
**Run calibration** may repeat judge calls over the same retained evidence. It
does not rerun candidate tools or sessions.
Use clearly good, clearly bad, and difficult boundary examples. If repeated
judge scores move materially on identical evidence, refine the rubric or use a
different judge before treating its threshold as a release gate.
## Interpret a judged result
Every trial retains:
- each criterion score and bounded, redacted explanation state;
- Cayu's exact weighted contribution and aggregate;
- the rubric, reference, judge profile, and implementation revisions;
- candidate and judge route relation;
- observed judge usage and priced or unavailable cost; and
- evaluator errors separately from candidate behavior.
A timeout, provider failure, malformed typed response, missing required usage,
or unavailable reference produces an evaluator error with no candidate score.
It is never converted to numeric zero or reported as a candidate failure.
Repeated candidate runs with judging measure end-to-end evaluation variability.
Only fixed-evidence calibration isolates judge variability. Keep those claims
separate in reports and release decisions.
Use `cayu guide evals-first` for suite and baseline basics and `cayu guide
evals-production` for production-session, scenario, tool, and memory workflows.
# Evaluate production sessions, scenarios, and memory
This guide ships with the Cayu package, so the installed version is always available offline with `cayu guide evals-production`. [Release source](https://github.com/cayu-dev/cayu/blob/v0.9.1/src/cayu/guides/evals-production.md).
Use this guide when a completed or failed retained session is the behavior you
want to understand. Cayu can score the evidence already captured, save it as a
baseline, and—when the session can be reconstructed under current published
authority—run a controlled fresh scenario through the same evaluator.
## Check a native process evaluation
For a run launched with `cayu eval run --processes ...`, use its recorded
`--process-directory`:
```bash
cayu eval status .cayu/evals/run-001 --sessions
cayu eval failures .cayu/evals/run-001 --json
cayu eval export .cayu/evals/run-001 --output run-001-receipts.zip
```
These commands inspect retained receipts and explicit read-only session links
without loading the target. Recorded results, provisional trial progress, and
recent session activity are separate facts. An admitted launch or a persisted
`running` session does not prove a live owner. Missing or truncated evidence is
reported explicitly; export never resumes work or promotes an incomplete run.
Use `--case CASE_ID` to focus failure inspection. The ZIP contains native
receipts and worker results; preserve application session stores separately.
## Evaluate a retained session
Open the session in Control Plane and choose **Evaluate**.
1. Review **Captured evidence** before adding expectations. Missing, redacted,
truncated, or unavailable evidence is not a failed assertion and must not be
replaced with a guessed value.
2. Give the suite and case durable names. Use **Add observed** only for behavior
that should remain true; incidental wording or a mistaken tool call does not
become correct merely because it happened once.
3. Choose the assertion kind that matches the claim: final output, tool called,
bounded tool arguments/result, process event/order, child status,
workspace/artifact structure, usage/cost, or memory attribution.
4. Choose **Preview score** after every edit. Preview rescans the retained
evidence with the same compiler and evaluator used for fresh runs.
5. **Save evaluation** records the exact captured result. **Approve baseline**
makes that immutable revision the default comparison only after human
review.
**Run on current app** is available only when the captured input can be
reconstructed safely. It uses the current registered agent, provider, tools,
environment, policies, and limits. A readiness message that names a missing
tool, environment, secret, fixture, approval path, or evidence field is a real
current dependency—not a request to fabricate it in the browser.
## Build a controlled multi-stage scenario
Use **Production scenario** from a captured session, or choose **Multi-stage
scenario** for a case in **New evaluation**.
- The initial event creates one fresh session.
- **Queue input** delivers ordered follow-up input on the next turn or when the
session is idle.
- **Resume input** supplies a typed user-input answer or an ordinary session
resume at the authored checkpoint.
- **Approval** requires a new decision for the named current tool occurrence.
A production approval is never replayed into the fresh trial.
- **Add part** can add structured JSON. A file part must refer to a retained,
digest-bound artifact requirement; a browser path or upload cannot create
server-side file authority.
Choose **Check readiness** before saving. The preview binds the exact current
target, execution profile, scenario revision, environment, fixtures, secrets,
effects, evidence policy, limits, trials, concurrency, timeout, and cost bound.
Prepare retained artifacts only through the displayed server-owned **Prepare
fixture** action. **Run scenario** admits that exact reviewed binding.
During execution, the Runs view shows queued, running, awaiting-input,
awaiting-approval, cancelling, and terminal trial phases. Approve or deny only
the displayed fresh request. Cancellation is a durable server request, not a
browser-only status change.
## Check tool and process behavior
Prefer the narrowest truthful assertion:
- **Tool arguments contain** and **Tool result contains** use bounded JSON
subsets. Result checks are available only for explicitly retained public-safe
fields or text.
- Process assertions use Cayu's closed event vocabulary. Use exact order only
when intervening events should make the case fail.
- Workspace and artifact checks are structural by default: declared path or
filename, media type, digest, and size. Raw workspace content is not portable
evidence.
- Usage and cost checks depend on complete runtime evidence and an applicable
price book. Unpriced cost remains unavailable rather than zero.
## Evaluate memory without overstating causality
Memory evaluation has three different questions:
1. **Structural exposure:** did reviewed items enter the candidate context, and
were they exposed to the provider? Add **Require memory exposure** and set
admitted-item and provider-exposure bounds. This needs complete attribution
evidence for the selected source lifecycle.
2. **Semantic use:** did the answer apply the relevant remembered facts
correctly? Add **Reference-backed judge**, replace the blank expected fact
with trusted evaluator-only truth, and calibrate the rubric. This judges the
output; it does not reveal private memory content to the candidate.
3. **Causal contribution:** did memory cause an improvement? An ordinary run
cannot answer that. Use the protected paired-intervention report only for a
validated campaign that controls the memory variant and retains comparable
trial evidence.
Session-scoped memory aliases are diagnostic labels, not stable case identity
across fresh runs. Results retain bounded source/lifecycle evidence and safe
immutable references, never raw private memory identity or content.
## Compare and automate
Captured, fresh simple, and scenario results share the same result, baseline,
report, and comparison contracts. Download JSON/HTML from Control Plane or use:
```console
cayu eval report result.eval-result.json --html --output report.html
cayu eval compare baseline.eval-result.json result.eval-result.json \
--json --output comparison.json
```
Read incompatibility reasons instead of overriding them. Changed assertions,
scenario material, memory evidence policy, execution/judge identity, trial
policy, or pricing may make two apparently similar runs unsafe to compare.
Use `cayu guide evals-first` for the smallest author-first workflow and `cayu
guide evals-ai-quality` for rubric, reference, calibration, and judge semantics.
# Test the right layer
Use a small test at the layer that owns the claim. Generated projects include a credential-free suite and a scripted eval so you can change the agent before configuring a live provider.
```sh
uv run --no-sync pytest -q
uv run --no-sync cayu eval run
```
## Four useful layers
| Layer | Exercise | What it proves |
| --- | --- | --- |
| Domain/tool unit | Known input directly to a tool or domain function | Schema and deterministic output |
| Scripted runtime | Public Cayu APIs with a scripted provider | Dispatch, effects, events and terminal behavior |
| Recovery integration | Separate processes using durable state | Ownership, continuation and mutation receipts |
| Live-model evaluation | A fresh run against a configured provider | Candidate behavior for those cases and settings |
For a write-capable tool, assert the actual record or file change. For a denial, assert the tool did not run and state stayed unchanged. For an approval pause, inspect the pending action and resolve its exact identity. Avoid asserting only that the final answer contains the word “approved.”
## Read the complete outcome
An async event stream can report `session.failed` without a Python exception becoming your caller's only signal. Consume the stream and inspect terminal state. For bounded scripts, `run_to_completion` gives a typed outcome with retained events.
Test timeouts, invalid provider output, rejected policy, user interruption and ambiguous external results. Keep assertions on stable domain facts rather than incidental phrasing or generated IDs.
Use the [testing API](https://cayu.dev/docs/reference/python/testing/) and [structured-output example](https://cayu.dev/docs/build/structured-output/) for deterministic repair tests.
# Build with a coding agent
Use your coding agent to develop a Cayu application. The coding agent edits software; the resulting Cayu agent performs the domain job at runtime. Keep those roles explicit in the implementation brief.
## Give the builder a concrete brief
```text
Create a Cayu application using cayu new.
Job: summarize a support request and identify missing order information.
Input: user text supplied to run.py.
Output: a summary plus the specific information still needed.
Start with one model-only agent. Read the generated AGENTS.md first.
Use the declared build_app factory and configuration seams.
Update the existing test and eval to represent the job.
Run inspect, strict check, tests and eval; report actual results.
Configure the live provider once I supply the selected provider and credentials.
```
Replace the job, inputs and acceptance criteria with your domain. Add authority requirements when the job includes spending, external mutations or human decisions. The builder needs enough context to distinguish a useful result from plausible prose.
## Discovery and the authoring loop
Ask the builder to read these version-matched sources inside the project:
```sh
uv run --no-sync cayu guide anatomy
uv run --no-sync cayu guide authoring#cayu-map
uv run --no-sync cayu inspect --json
uv run --no-sync cayu check --fail-on warning --json
```
The [capability map](https://cayu.dev/docs/reference/capabilities/) routes optional behavior to the smallest relevant contract. The small [machine-readable API index](https://cayu.dev/docs/python-api.json) leads to namespace and alphabetic symbol indexes. Follow a symbol's Markdown URL for its signature, fields and source. Package-shipped guides remain the local reference when the installed version differs from this site.
## Review evidence
Check the factory declaration, registrations and tests rather than accepting a completion message. A useful handoff records the changed behavior, test results, eval results and observed live-run outcome. Verify that deterministic fixtures test real runtime seams, and that a live-model claim actually came from a model run.
For a protected external action, require a test that proves denial or a durable pause before authorization, then independent evidence of the effect after authorization. Tests that assert only the tool's own success text cannot establish downstream state.
## Keep the project small
Add tools, environments, workers, workflows, memory and approvals as the job needs them. A model-only application is a valid starting point. Read [authoring](https://cayu.dev/docs/build/authoring/) for exact placement and [testing](https://cayu.dev/docs/evaluate/testing/) for proof through public seams.
# Installation
Cayu requires **Python 3.11 or newer**. Use a virtual environment for each application. The examples below use `uv`; ordinary Python virtual environments and `pip` work too.
## Generate a project with uv
Run this from the directory that should contain your application:
```sh
uvx cayu new myagent
cd myagent
uv sync --extra dev
uv run --no-sync cayu --version
```
The final command reports the installed Cayu version; the [release manifest](https://cayu.dev/docs/release.json) names the release these docs describe. The generator declares the application factory and supplies test and eval seams. The project's `dev` extra includes the local developer control plane.
## Install with pip
```sh
python -m venv .venv
source .venv/bin/activate
python -m pip install cayu
cayu new myagent
cd myagent
python -m pip install -e '.[dev]'
cayu --version
```
On Windows, activate the environment with `.venv\Scripts\Activate.ps1` in PowerShell. Keep every command in the same environment; a global `cayu` command can otherwise select a different release.
## Optional integrations
Install the extras used by your application. The base package provides the core runtime and provider-neutral contracts.
| Extra | Capability |
| --- | --- |
| `cayu[server]` | HTTP control plane and packaged dashboard |
| `cayu[server-settings]` | Server plus typed environment and dotenv configuration |
| `cayu[postgres]` | PostgreSQL stores |
| `cayu[oidc]` | OIDC bearer authentication for operators |
| `cayu[aws]` | AWS integrations including Bedrock and Lambda MicroVM |
| `cayu[vertex]` | Anthropic models through Google Cloud Vertex AI |
| `cayu[e2b]` | E2B runner and workspace |
| `cayu[browser]` | Browser integrations |
| `cayu[files]` | Image and PDF tooling |
| `cayu[console]` | Interactive Python console |
| `cayu[egress]` | Credential and network egress integrations |
For example, `python -m pip install 'cayu[server,postgres]'` installs the latest release with both integrations. Declare dependencies in the generated project's configuration and lock them with `uv sync` for reproducible deployments.
## Credentials and the first checks
Project generation, deterministic tests and the starter eval need no model credentials. A live run does. Configure the provider through the generated settings and [provider guide](https://cayu.dev/docs/build/providers/); keep credential values in environment or secret configuration.
Continue with the [quickstart](https://cayu.dev/docs/get-started/quickstart/).
# Cayu application anatomy
This guide ships with the Cayu package, so the installed version is always available offline with `cayu guide anatomy`. [Release source](https://github.com/cayu-dev/cayu/blob/v0.9.1/src/cayu/guides/application-anatomy.md).
A Cayu application has one explicit boot contract: a project declares an
application factory, and every process calls that factory to construct its own
application graph. The Python object is process-local. Configured durable stores
are the coordination boundary between processes.
## Project and factory
A **Cayu project** is a directory rooted by project configuration that declares a
synchronous application factory:
```toml
[tool.cayu]
factory = "app:build_app"
```
The target is a synchronous callable that accepts a zero-argument call and
returns a fresh `CayuApp`:
```python
from cayu import CayuApp
def build_app() -> CayuApp:
app = CayuApp()
# Register this process's providers, environments, tools, and agents.
return app
```
Optional dependency-injection arguments are useful public test seams, provided a
normal `build_app()` call remains valid. Importing the project module must not
call the factory, connect to external services, run migrations, start background
activity, or invoke models or tools.
## Process-scoped application graph
Each factory call returns a distinct, process-scoped `CayuApp`. The app is not a
runtime singleton, global registry, or durable coordination mechanism. A
console, server integration, worker integration, script, and test each construct
their own graph, even when all of them point at the same storage configuration.
Configured session, task, knowledge, artifact, watcher, budget, and other durable
stores carry shared state across processes. In-memory stores and Python object
identity do not. Registration and live resource ownership remain local to the
process that constructed the app.
## Resume versus anatomical succession
Reconstructing an app and calling `resume(...)` keeps the same durable session.
The current application graph supplies the registered tools and policies, but
the session's existing transcript remains authoritative—including its original
system prompt. Changing `AgentSpec.system_prompt` does not silently rewrite that
history.
When a new agent body must inherit conversation history but install its current
prompt anatomy, create an explicit descendant instead:
```python
from cayu import (
ExecutionProfileAdoptionIntent,
ForkExecutionProfileSelection,
ForkSessionRequest,
ForkSystemPromptPolicy,
ResolutionActor,
ResolutionActorSource,
)
events = [
event
async for event in app.fork_session(
ForkSessionRequest(
source_session_id="agent-v1-session",
session_id="agent-v2-session",
agent_name="agent-v2",
copy_checkpoint=False,
system_prompt_policy=ForkSystemPromptPolicy.CURRENT_AGENT,
execution_profile_selection=ForkExecutionProfileSelection.CURRENT_CHILD,
profile_adoption=ExecutionProfileAdoptionIntent(
idempotency_key="agent-v2-session-profile",
reason="Install the registered agent-v2 execution profile.",
requested_by=ResolutionActor(
subject="release-operator",
source=ResolutionActorSource.REQUEST,
),
),
)
)
]
```
This opt-in fork renders the current registered agent prompt and current
workspace instructions, atomically replaces inherited system messages, and
preserves the selected non-system history. It records a durable
`PromptAnatomyTransitionReceipt` containing source/child prompt SHA-256 digests
and transition identities, plus source/child model identities and the successful
portability check, never prompt text. Before descendant creation the source
checkpoint records a durable transition intent, so a process restart can
continue the exact request. The source transcript stays unchanged. Exact
retries reuse the caller-selected destination ID when supplied; otherwise Cayu
derives one opaque parent-and-request-scoped ID. Both forms converge on the same
descendant and receipt.
Selecting `CURRENT_CHILD` is an explicit execution-profile adoption request.
The application's `ExecutionProfilePolicy` must authorize the current
registration's decision-bearing authority before the child is created, including
environment, hooks, tool policy, runner, credential, network, and external-effect
authority that is not part of the structural profile fingerprint. A provider
change always projects and preflights the complete portable transcript, even when
the model name is unchanged. Omitting `execution_profile_selection` instead
inherits the parent's effective durable profile and does not consult mutable
current registrations to choose a different child baseline.
Prompt succession currently requires `copy_checkpoint=False` and a concrete
registered environment. This is deliberate: a checkpoint or an unmaterialized
environment factory can contain body-specific execution state that the runtime
cannot prove compatible with the new prompt anatomy. Ordinary forks continue to
inherit the source prompt by default.
For a body upgrade that hopes to keep the exact session ID, compare the
persisted transcript's `system_prompt_messages_sha256(...)` with
`await app.current_prompt_anatomy_sha256(...)`. Only an equal digest proves the
new application graph would install the same prompt anatomy; otherwise fork.
## Application lifecycle boundaries
Keep these five responsibilities conceptually separate so a host makes its
operational effects explicit:
| Boundary | Meaning | What it does not imply |
| --- | --- | --- |
| Application construction | Call the factory and compose the process-local graph. | Active work has started or another process shares the app object. |
| Resource acquisition | Open or attach owned database, network, sandbox, or host resources. | Schemas are migrated or active services are running. |
| Administrative initialization | Explicitly perform migrations, recovery selection, seeding, or maintenance. | A long-running service owns the process. |
| Active-service startup | Explicitly start a server lifespan, worker loop, watcher, scheduler, or other active integration. | Other processes share this app object or its lifecycle. |
| Shutdown | `await app.aclose()` refuses new execution, waits for operations in flight, and drains this app's subsystems under one deadline. | Caller-supplied stores or providers are closed, or other processes have stopped. |
Construction and resource acquisition have no general protocol, so a configured
component can perform more than one responsibility in its constructor. In the
generated local project, for example, SQLite store constructors open their files
and ensure their schemas, while PostgreSQL stores connect lazily and only validate
the schema that `cayu storage migrate` applied; `cayu console`, `cayu inspect`, and
`cayu check` call the factory and therefore exercise that configured behavior. Keep module imports
inert, keep constructor effects bounded and documented, and leave active-service
startup and cleanup under the explicit host or process entrypoint that owns them.
Shutdown is one call. The host that built the app closes it, and hands over any
resources it wants closed with it:
```python
from contextlib import aclosing
from pathlib import Path
from cayu import (
AgentSpec,
CayuApp,
Message,
ModelProvider,
RunRequest,
configured_database_url,
open_application_stores,
)
async def main(provider: ModelProvider) -> None:
stores = open_application_stores(
configured_database_url(), sqlite_path=Path("state/app.sqlite3").resolve()
)
# The app closes the stores, but only after every subsystem settled.
app = CayuApp(
session_store=stores.session_store,
task_store=stores.task_store,
owned_resources=(stores,),
)
app.register_provider(provider, default=True)
app.register_agent(AgentSpec(name="assistant", model="your-model"))
async with app:
request = RunRequest(agent_name="assistant", messages=[Message.text("user", "Hi")])
async with aclosing(app.run(request)) as events:
async for event in events:
print(event.type)
outcome = app.shutdown_outcome
if outcome is not None and not outcome.settled:
print(f"shutdown {outcome.status}; stores kept open, retry app.aclose()")
```
`async with app:` calls `aclose()` on exit. `aclosing(...)` closes a stream you
stop reading early, so shutdown does not wait for it. Without `owned_resources`,
close your own stores only once `app.shutdown_outcome.settled` is true. Do not
nest the app inside `async with stores:`, which closes them unconditionally. The
order, deadline, and outcome are specified in the runtime contracts.
## Process roles
| Role | Application ownership | Shared state | Automatic active services |
| --- | --- | --- | --- |
| One-off script | Calls the factory for its process. | Configured durable stores. | Only behavior explicitly invoked by the script. |
| Interactive console | Calls the factory once for the console process. | Configured durable stores. | None. |
| Server integration | Calls the factory for the server process. | Configured durable stores. | Only lifecycle behavior explicitly owned by the server integration. |
| Worker integration | Calls the factory for the worker process. | Configured durable stores. | Only worker behavior explicitly started by that process. |
| Test | Calls the factory for the test or fixture scope. | Test-selected stores. | None unless the test starts it. |
`cayu console`, `cayu inspect`, and `cayu check` use the declared factory for
operator workflows. `cayu serve` and `cayu worker` are shipped process-role
adapters that construct the same application through its declared factory.
One-off scripts should call that factory directly and own the lifecycle of any
active services they start; Cayu does not ship a `cayu script` command.
## Durable data
Generated projects build their session, task, and knowledge stores with
`open_application_stores(configured_database_url(), sqlite_path=...)` in
`configuration/storage.py`. The same code runs locally and in a deployment:
| Variable | Effect |
| --- | --- |
| `CAYU_DATABASE_URL` | `postgres://` or `postgresql://` selects PostgreSQL; an absolute `sqlite:///` URL selects that file. Unset selects SQLite at the project's absolute `sqlite_path` (`data/cayu.db`). |
| `CAYU_DATABASE_POOL_MAX` | Maximum connections in the one pool the PostgreSQL stores share (default 5). The Evals store that `cayu serve` and `cayu check` open uses its own pool of the same size. |
| `CAYU_DATABASE_DIRECT_URL` | Optional direct server address for the one task-admission `LISTEN` connection, kept outside the pool. Set it when `CAYU_DATABASE_URL` points at a transaction-pooling proxy such as PgBouncer, where `LISTEN` does not work. |
| `CAYU_REQUIRE_POSTGRES` | `1` makes every Cayu SQLite store raise at construction, so a deployment missing `CAYU_DATABASE_URL` fails at startup instead of writing local files. |
One PostgreSQL-backed application process opens at most `CAYU_DATABASE_POOL_MAX`
pooled connections plus the listener, and the Evals store's pool when a Cayu
command serves the project. Cayu CLI commands resolve `CAYU_DATABASE_URL` before
`[tool.cayu.session_store]`, so they inspect the same database the app uses.
Keep application-owned durable records in the configured database. Application
tables may share it with their own table prefix; Cayu reserves `cayu_` for its
tables (ADR 0001). `data/` locally and `/data` in a deployment hold files such as
artifacts, uploads, and fixtures. Do not open SQLite files there for durable
application state: a deployment's `/data` may be a network filesystem, where
SQLite is slow and its WAL mode is unsupported. Deployments of applications with
automatic memory must set `CAYU_MEMORY_EVIDENCE_KEY`, because the local
`data/memory-evidence.key` is git-ignored and never reaches a deployment.
## Console contract
`cayu console` constructs one console-local app and binds it as `app`. That name
is not a runtime registry or singleton. Opening the console does not start a
server lifespan, recovery, workers, watchers, schedulers, sessions, models, or
tools. Operations requested interactively may affect the same durable backends
used by other processes.
File-backed Cayu SQLite stores use WAL and a busy timeout, but SQLite remains
single-writer. Another process can still contend with a console write and raise
`database is locked`. Prefer a deployment-appropriate shared store such as
PostgreSQL when multiple active processes need sustained write concurrency.
## Dependency boundary
Generated production dependencies use `cayu[postgres,server]`, so any project can
switch to PostgreSQL through `CAYU_DATABASE_URL` and run `cayu serve` in its
deployment. Non-service presets also set `[tool.cayu.serve].auth` to
`cayu.server.environment_auth:OPERATOR_BASIC_AUTH`, so a deployed `cayu serve`
authenticates operators with `CAYU_OPERATOR_USERNAME` and `CAYU_OPERATOR_PASSWORD`
and refuses to start without them. Interactive console support
is an explicit development extra such as `cayu[console]`; a production process
does not need to install REPL tooling merely because the project declares a
factory.
## Usage and cost
Read token usage and cost from Cayu instead of computing them in the
application. In Python, call `await app.get_session_usage(session_id)` and
`await app.get_session_cost(session_id, pricing)`. Over HTTP, use
`GET /api/sessions/{session_id}/usage`, `GET /api/sessions?include=usage` for a
page of sessions, and `POST /api/sessions/{session_id}/cost`; `GET /api/contract`
lists these paths under `accounting`. Usage reads are incremental in every
built-in session store, and the HTTP usage endpoint supports `ETag` and
`If-None-Match`, so a UI can poll it cheaply.
Do not fold raw `model.completed` events to compute tokens or cost. That misses
cache counters, auxiliary provider attempts, hosted-tool usage, and price-book
rules, and it rereads the whole event history on every refresh.
## Anti-patterns
Avoid these shapes:
- a module-global `CayuApp` used as shared application state;
- calling `build_app()` during module import;
- starting migrations, recovery, workers, watchers, schedulers, models, or tools
as an import side effect;
- summing `model.completed` event payloads to show tokens or cost; and
- treating the console-local `app` binding as a runtime-wide registry.
## Verify the contract
For a generated project, verify the public boundary rather than exact prose:
```bash
cayu inspect --json
cayu check --fail-on warning --json
pytest
```
Project tests should prove that importing `app.py` does not construct the app,
two factory calls return distinct `CayuApp` objects, and `[tool.cayu].factory`
resolves to that callable. These checks prove structure and deterministic runtime
behavior; they do not claim live provider, environment, or service verification.
Use `cayu guide applications` for the generated placement convention,
normalized plan, generator compatibility, and declared-layout diagnostics.
Use `cayu guide app-ui` ([source](https://cayu.dev/docs/build/application-ui/)) before building a browser UI
over sessions: it covers the served `client.js`, polling rules, read-only GET
handlers, and the idle-tab budget for the server integration role.
# Quickstart
This is the canonical first application path. You need Python 3.11+ and `uv`. The first verification stage runs without credentials; a live model run uses your chosen provider.
## Create and verify
```sh
uvx cayu new myagent
cd myagent
uv sync --extra dev
uv run --no-sync cayu guide anatomy
uv run --no-sync cayu inspect --json
uv run --no-sync cayu check --fail-on warning --json
uv run --no-sync pytest
uv run --no-sync cayu eval run
```
Inspection should identify the registered agent and configured stores. The strict check should exit successfully with no warning-or-higher diagnostics. The generated tests and eval inject a deterministic provider through the application's public factory seam. Passing them proves the starter's runtime behavior; live-model quality is evaluated separately.
## Read the project
| File or directory | Responsibility |
| --- | --- |
| `pyproject.toml` | Declares `[tool.cayu] factory = "app:build_app"` and dependencies |
| `app.py` | Constructs and registers one fresh process-local application |
| `configuration/` | Settings, providers and storage |
| `agents/` and `prompts/` | Agent identity and model-facing instructions |
| `tools/` | Domain tools and their registration |
| `tests/` and `evals/` | Behavioral checks |
| `run.py` | The application-owned first-run entrypoint |
| `AGENTS.md` | Local placement and verification rules for coding agents |
Keep `app.py` focused on construction and registration. Put business calculations in their owning modules. Read [application anatomy](https://cayu.dev/docs/get-started/project-anatomy/) for process roles and cleanup.
## Give the first agent a job
Edit the existing agent and prompt rather than retaining the starter and adding a second agent. For example: accept a support request, identify missing order information, and return an evidence-based summary. Update the existing test and eval to express the required behavior.
A model-only job needs no tools. Add a tool when the agent needs an application capability, such as reading an order or preparing a proposal. Follow [tool authoring](https://cayu.dev/docs/build/tools/) when that requirement appears.
## Configure a provider and run
Choose a supported provider and model in `configuration/settings.py` or through the generated `CAYU_PROVIDER` setting. Set the credentials specified in the [provider guide](https://cayu.dev/docs/build/providers/). Then:
```sh
uv run --no-sync python run.py --message "Summarize this support request: my order arrived damaged."
```
Inspect the emitted events and final outcome. A provider authentication failure is a configuration problem; a session that fails or pauses is a runtime outcome that requires its recorded evidence.
## Open the local dashboard
```sh
uv run --no-sync cayu serve --dev --host 127.0.0.1 --port 8000
```
Open `http://127.0.0.1:8000/cayu/`. The explicit local development profile is for trusted loopback access. Read [server setup](https://cayu.dev/docs/operate/server/) before deploying.
## Continue
Re-run `inspect`, the strict `check`, tests and eval after changing behavior. Try the [restartable support recipe](https://cayu.dev/docs/recipes/support-agent/) for a complete credential-free journey through clarification, approval, execution and independent verification. Look up exact arguments in the [Python API](https://cayu.dev/docs/reference/python/).
# Cayu documentation
Cayu is an open-source Python framework for building and running domain-specific long-horizon agents, with a durable runtime built in.
Assemble your harness from model providers, tools, context, knowledge, memory and verification. The integrated runtime executes the agent with durable sessions, explicit authority and recorded progress.
These docs cover the latest Cayu release, on Python 3.11 and newer. Start with the generated project, then add the capabilities your job needs. Cayu Cloud is optional.
## The path from an idea to an operated app
1. **Define the job.** State the input, expected output and evidence that proves completion.
2. **Generate and verify.** Use `cayu new`; preserve the factory, tests and evals as you change behavior.
3. **Build the harness.** Add prompts, tools, context, policies and domain code only where the job requires them.
4. **Evaluate.** Check both the result and the recorded path to it.
5. **Operate.** Select stores, process roles, authentication and recovery policy for your deployment.
## Find the right kind of documentation
| You want to… | Start here |
| --- | --- |
| Install and make a first run | [Get started](https://cayu.dev/docs/get-started/installation/) |
| Understand the framework's pieces | [Concepts](https://cayu.dev/docs/concepts/) |
| Add a particular capability | [Build agents](https://cayu.dev/docs/build/) |
| Test an agent and compare changes | [Evaluate](https://cayu.dev/docs/evaluate/) |
| Deploy and inspect ongoing work | [Operate](https://cayu.dev/docs/operate/) |
| Follow a complete example | [Recipes](https://cayu.dev/docs/recipes/) or the [dining walkthrough](https://cayu.dev/walkthrough/) |
| Find exact arguments and behavior | [Reference](https://cayu.dev/docs/reference/) |
## Documentation for your installed version
The API and package guides on this site are generated from the latest public release, named in the [release manifest](https://cayu.dev/docs/release.json). Run `cayu --version` to compare it with your project. The installed `cayu guide` commands provide version-matched local guidance; use them when your project is on another release. Read the [release and upgrade guide](https://cayu.dev/docs/operate/upgrades/) before changing a persisted application.
The [documentation map](https://cayu.dev/docs/content-map/) lists the supported learning paths and their verification evidence. The [machine-readable index](https://cayu.dev/llms.txt) helps coding agents discover the same canonical pages.
# Learn Cayu
Follow these worked guides in order. Each has runnable code or a released executable fixture, commands to run it, and assertions that establish its result. Use the setup specified by each guide.
The tool, structured-output and support fixtures run without model-provider credentials. The first-project guide distinguishes those deterministic checks from a later live-model run.
## 1. Build and inspect a first project
Generate an application with `cayu new`. Run inspection, the strict check, tests and a scripted eval before configuring a provider.
[Open the worked guide](https://cayu.dev/docs/get-started/quickstart/).
**Try it:** Keep the generated factory inert at import time. Explain which checks run without provider credentials and which later run measures live-model behavior.
## 2. Implement and test one tool
Run the complete downloadable lookup tool. Follow the schema, effect declaration, context and returned result through the implementation and assertions.
[Open the worked guide](https://cayu.dev/docs/build/tools/).
**Try it:** Change the fixture's lookup result and update its assertion. Identify the separate policy check needed before a protected tool can execute.
## 3. Repair a structured result
Run the complete structured-output example. Its scripted provider produces an invalid attempt and a corrected result, so you can observe validation and repair.
[Open the worked guide](https://cayu.dev/docs/build/structured-output/).
**Try it:** Inspect both attempts and the successful structured result. Explain why a tool-strategy result can succeed without final answer text.
## 4. Resume an approval-gated service job
Run the released support fixture one command per process. Clarify the request, record an exact approval, deliver the receipt and verify the domain state.
[Open the worked guide](https://cayu.dev/docs/recipes/support-agent/).
**Try it:** Try approve and deny in separate fixture directories. Repeat receipt delivery and confirm that approval permits one replacement while denial permits none. Explain which evidence belongs to Cayu and which belongs to the domain service.
## Continue into the runtime
After the worked path, study [sessions](https://cayu.dev/docs/concepts/sessions/), [authority](https://cayu.dev/docs/concepts/authority/), [tasks and workers](https://cayu.dev/docs/build/tasks-workflows/) and [recovery](https://cayu.dev/docs/operate/recovery/). Use the [API reference](https://cayu.dev/docs/reference/) for exact contracts.
The [dining walkthrough](https://cayu.dev/walkthrough/) shows a larger public application through real code excerpts. It has its own older Cayu git pin, separate from these release-matched guides.
# Authentication and access
Authentication identifies an HTTP caller. Tool and domain authorization decide which action that caller or session may perform. Configure both boundaries.
## Protect deployed servers
Use an authentication target with `cayu serve`, or `ServerConfig.protected(auth)` when embedding the server. The built-in environment target reads `CAYU_OPERATOR_USERNAME` and `CAYU_OPERATOR_PASSWORD` once at target loading and fails on missing or blank values. Use deployed TLS and secret configuration.
For OIDC bearer verification, install `cayu[oidc]` and configure issuer, audience and required scopes on `OidcBearerAuth`. It verifies tokens callers already possess; it does not provide a browser sign-in flow.
A custom dependency accepts the request and returns `AuthContext` or a compatible mapping, or rejects with an HTTP authentication error. Inspect the [server API](https://cayu.dev/docs/reference/python/server/) for exact types and defaults.
## Define the application's data boundary
An `AuthContext.tenant` value is actor provenance. It does not automatically filter every store query or establish multi-tenant isolation. Implement and verify the intended data boundary through deployment/store separation and application authorization.
Test unauthenticated requests, insufficient authority and a caller attempting another session's operation. CORS controls browser access patterns; it does not substitute for authentication or authorization. Use [approvals](https://cayu.dev/docs/build/approvals/) for exact human-authorized tool actions and [authority](https://cayu.dev/docs/concepts/authority/) for effect policy.
# Bound runtime work
Set limits before launching a long run: model steps, tool duration and concurrency, worker concurrency, environment resources, evaluation trials and any cost budget your configured pricing can support.
## Bound execution separately from spend
`CayuConfig` owns process-wide defaults. `RunRequest` owns permitted per-run overrides. A maximum step count bounds runtime iterations; it is not a complete monetary estimate. A tool can perform expensive external work independently of model tokens.
Use the [configuration reference](https://cayu.dev/docs/reference/configuration/) to find the exact field owner and default. Do not create additional ambient environment switches in tools for settings already owned by typed runtime configuration.
## Account with available evidence
Pricing, usage observation and budget reservation/settlement have distinct roles. Configure an applicable price book before claiming a hard cost limit. Unavailable usage or pricing is not zero cost. Evaluation launch reports distinguish hard-bounded exposure from observed-only cost.
Test exhausted limits, unavailable pricing, child work and interruption. Verify that reservations and terminal evidence settle under the selected policy, including an ambiguous external outcome.
Reference: [budget API](https://cayu.dev/docs/reference/python/budgets/), [run configuration](https://cayu.dev/docs/reference/python/configuration/) and [evaluation launch](https://cayu.dev/docs/evaluate/first-eval/).
# Deploy an application
Package your generated application with its pinned dependencies and configured factory. Choose your platform's process manager, secret injection and durable storage. Server and worker processes each construct their own application graph.
## Release sequence
1. Run credential-free tests and representative evaluations against the candidate version.
2. Back up retained state and inspect migration status with the deployment's exact configuration.
3. Apply supported migrations as a separate one-off step.
4. Start authenticated server and worker entrypoints with shared durable stores where required.
5. Inspect readiness, run one bounded operation and verify its retained result.
6. Exercise cooperative shutdown and the chosen recovery policy before increasing traffic.
Use [storage](https://cayu.dev/docs/operate/storage/) for migration preflight and pooling. Configure PostgreSQL explicitly when local SQLite is inappropriate. Mount durable paths rather than relying on an ephemeral container filesystem.
## Own the platform boundary
Configure TLS, authentication, authorized data access, process restart policy and environment cleanup for your deployment. `cayu worker` has bounded shutdown and failure handling; it does not restart failed workers or choose abandoned-session policy.
The `cayu cloud` commands are an optional hosted deployment path. Their release-matched CLI flags are in the [cloud reference](https://cayu.dev/docs/reference/cli/cloud/). Availability, accounts and commercial limits depend on the hosted service; these docs' local framework workflows are independently runnable.
For a release change, use [upgrades](https://cayu.dev/docs/operate/upgrades/) and retain the exact tested version and migration receipt.
# Cayu project diagnostics
This guide ships with the Cayu package, so the installed version is always available offline with `cayu guide diagnostics`. [Release source](https://github.com/cayu-dev/cayu/blob/v0.9.1/src/cayu/guides/diagnostics.md).
`cayu check` renders these stable findings from the public application manifest.
Run the correction, then rerun `cayu inspect --json` and `cayu check --json`.
The last section, [request-cost](#request-cost), covers what a running server
spends on each HTTP route and how to check it.
## app-no-agents
`APP_NO_AGENTS` means the factory returned an app with no registered agent.
Register an `AgentSpec` with `CayuApp.register_agent()`.
## agent-generated-tracer-bullet-unfinished
`AGENT_GENERATED_TRACER_BULLET_UNFINISHED` means a generator left the agent's
explicit authoring-state marker in place. The generated prompt, placeholder
tool behavior, runtime test, and scripted trajectory are a runnable wiring
proof, not evidence that the requested domain behavior is complete.
Replace the domain system prompt, tool schema and implementation, runtime test
inputs and assertions, and trajectory eval behavior and assertions. For a
scaffold updated by `cayu generate tool`, change `_AUTHORING_STATE` to `None`
inside the generated agent-config region. For `cayu generate slice`, remove
`authoring_state=AgentAuthoringState.UNFINISHED_GENERATED_TRACER_BULLET` and
the unused import from the generated agent module. Verify with
`cayu inspect --json && cayu check --fail-on warning --json`.
The marker is an author assertion, not source analysis: Cayu does not scan
Python or prose for words such as `sample`, `echo`, or `tracer bullet`, and an
absent marker does not prove domain correctness.
## agent-provider-not-found
`AGENT_PROVIDER_NOT_FOUND` means an agent's explicit provider is absent, or no
model-pattern/default provider can resolve the agent. Register the named
provider, correct `provider_name`, or define an unambiguous default route.
## agent-provider-ambiguous
`AGENT_PROVIDER_AMBIGUOUS` means more than one registered provider pattern
matches the agent model. Make patterns disjoint or set the agent's
`provider_name` explicitly.
## agent-workflow-tool-not-registered
`AGENT_WORKFLOW_TOOL_NOT_REGISTERED` means an agent's explicit
`workflow_tool_names` contract names a tool that is not registered for that
same agent. Use the exact registered name, update the machine-owned tool-name
source after a rename, or register the intended tool. Cayu checks this explicit
contract and does not parse arbitrary natural-language prompt text.
## agent-workflow-workspace-not-registered
`AGENT_WORKFLOW_WORKSPACE_NOT_REGISTERED` means a registered file tool named in
`workflow_tool_names` has no structurally available workspace. Register a
static environment with a workspace, or an environment factory that supplies
one per session.
## agent-workflow-runner-not-registered
`AGENT_WORKFLOW_RUNNER_NOT_REGISTERED` means `exec_command`, `run_check`, or
another runner-backed workflow tool is registered and named in
`workflow_tool_names`, but no runner is structurally available.
Register a static environment with a runner, or an environment factory that
supplies one per session.
## agent-workflow-command-policy-not-registered
`AGENT_WORKFLOW_COMMAND_POLICY_NOT_REGISTERED` means an agent explicitly
declares `exec_command` or `run_check` as part of its workflow but the
registered command tool has no `CommandPolicy`. `RunCheckTool` rejects that
configuration during construction; attach a deny-by-default policy such as
`ProcessCommandPolicy`. Inspection reports only the policy type, never its
allowed executables, directories, environment values, or other policy data.
## external-tool-unguarded
`EXTERNAL_TOOL_UNGUARDED` means a tool declaring `ToolEffect.EXTERNAL` is under
a policy that can allow that specific tool without an enforcing boundary. The
diagnostic reports the effective per-tool coverage rather than trusting the
policy class name. Register an enforcing policy. Use
`AlwaysRequireApprovalToolPolicy(tools=[...])` when a human must authorize
execution, and include the external tool's actual name in its scope. Naming the
tool in an explicit allowlist (`StaticToolPolicy(allow=[...])`) is a deliberate
authorization and satisfies this check. The built-in `ask_user` tool always
pauses for the user before anything happens, so it is never reported.
## external-tool-coverage-unknown
`EXTERNAL_TOOL_COVERAGE_UNKNOWN` means an external-effect tool uses a custom or
otherwise unrecognized policy whose behavior Cayu cannot verify statically.
This remains an error rather than an acknowledgment-based bypass: use a
statically describable enforcing policy. To add application validation, compose
`GuardedToolPolicy(guards=(app_guard,), then=AlwaysRequireApprovalToolPolicy(...))`.
Each guard returns `None` or a denial; invalid results and exceptions deny, so a
guard cannot bypass the downstream enforcement. Cayu derives coverage from the
exact downstream built-ins without calling guards or probing application stores.
Overriding policy subclasses and unknown downstream policies remain unknown.
Declare a behavior identity on custom guards for portable recovery; coverage is
not a self-attestation and does not prove the guard's application logic.
## tool-approval-validity-rule
`TOOL_APPROVAL_VALIDITY_RULE` is informational. It names each required-field or
required-allowlist rule configured under `REQUIRE_APPROVAL`. Missing, empty or
wrongly typed arguments are always denied, including when an earlier rule would
request approval. Approval applies to authority violations of well-formed calls.
Correct the arguments and submit a new call; an approver cannot supply them.
Inspect these rules with `cayu check --json` and `cayu inspect --json`.
## tool-input-schema-unconstrained
`TOOL_INPUT_SCHEMA_UNCONSTRAINED` means a registered tool exposes `{}` as its
input schema. That is valid JSON Schema, but it accepts every JSON value and
does not teach the model which arguments to send. Declare the expected object
properties, required fields, and `additionalProperties` behavior in
`ToolSpec.input_schema`. If a tool derives its schema dynamically, override the
public `Tool.schema` property; Cayu treats that property as authoritative when
the tool is registered.
## tool-input-schema-runtime-unsupported
`TOOL_INPUT_SCHEMA_RUNTIME_UNSUPPORTED` means the registered schema is not a
locally resolvable Draft 2020-12 schema, or it declares a different `$schema`
dialect. Runtime keeps the prior tool-owned argument validation instead of
denying every call. Update the schema to Draft 2020-12 and inline external
references to enable runtime validation. No remote schema URLs are fetched. Run
`cayu check --json` to verify the repair.
## public-service-development-mode
`PUBLIC_SERVICE_DEVELOPMENT_MODE` means the maintained public-service factory
was assembled with its explicit local-development profile. Development access
may accept caller-selected test identities and an open operator mount, so it is
restricted to `cayu serve --dev` on a loopback listener. Build the same service
factory with `mode="production"` before deployment.
## evals-project-identity-not-configured
`EVALS_PROJECT_IDENTITY_NOT_CONFIGURED` means automatic Evals assembly cannot
derive a stable project identity. Add a valid PyPA distribution name under
`[project]` in `pyproject.toml`; Cayu normalizes runs of `.`, `_`, and `-` to a
single lowercase `-`. This warning does not disable an explicitly supplied
`EvalsConfig`.
Verify with `cayu check --json`.
## evals-project-store-not-configured
`EVALS_PROJECT_STORE_NOT_CONFIGURED` means production project serving cannot
select durable Evals storage. Configure SQLite or PostgreSQL under
`[tool.cayu.session_store]`, or set `CAYU_DATABASE_URL`. Production does not
create a database merely to clear the warning. Explicit trusted-local
`cayu serve --dev` may use the project-local `data/cayu.db` default.
Connection strings and paths are excluded from the finding. Verify with
`cayu check --json` in the intended deployment environment.
## evals-service-factory-context-migration-required
`EVALS_SERVICE_FACTORY_CONTEXT_MIGRATION_REQUIRED` means a maintained service
factory still uses the older signature and therefore cannot carry Cayu's
framework-owned Evals project context to `create_agent_service(...)`. The
service remains compatible and starts normally; only automatic project
assembly is unavailable through that factory.
Run `cayu generate service-context --dry-run`, review the proposed edit, then
run `cayu generate service-context`. The command modifies only the recognized
previous generated form. If it reports `manual_action_required`, add an
optional keyword-only `project_context: ProjectControlPlaneContext | None =
None` parameter and pass it unchanged as `project_context=project_context` to
`create_agent_service(...)`. Verify with `cayu check --json`.
## public-service-product-access-unsafe
`PUBLIC_SERVICE_PRODUCT_ACCESS_UNSAFE` means the maintained product API uses a
development or fail-closed placeholder access adapter rather than configured
production authentication. Configure `AuthenticatedProductAccess` so its
server-side dependency returns a trusted `ProductPrincipal`. Tenant identity
must not come from the product request body, query, Cayu labels or metadata,
model output, or tool input. For OIDC/JWT bearer tokens,
`OidcBearerAuth(...).product_dependency(tenant_claim=...)` takes both from the
verified token and rejects a token without the tenant claim.
## public-service-operator-access-unsafe
`PUBLIC_SERVICE_OPERATOR_ACCESS_UNSAFE` means the separately mounted `/cayu/`
operator control plane uses `OpenAccess` or a fail-closed placeholder because
production authentication is missing or invalid. Configure
`AuthenticatedAccess` for production. Operator authentication protects the raw
control plane; it does not make that surface customer-facing or tenant-scoped.
## public-service-identity-store-not-durable
`PUBLIC_SERVICE_IDENTITY_STORE_NOT_DURABLE` means the public-to-private identity
mapping declares development-only process state. Use durable application-owned
storage that atomically reserves idempotency identities and performs
tenant-qualified resource lookup before any Cayu operation. Cayu session IDs,
task IDs, labels, and metadata are not product authorization state.
## public-service-session-store-not-durable
`PUBLIC_SERVICE_SESSION_STORE_NOT_DURABLE` means the maintained service uses a
development-only, read-only, or unverified Cayu session store. Public-service
sessions must survive process restarts and accept runtime writes, so configure
a built-in durable `SessionStore` or a custom store that explicitly declares
`service_durability = RuntimeStoreDurability.DURABLE` after its durability
contract is verified.
## public-service-task-store-required
`PUBLIC_SERVICE_TASK_STORE_REQUIRED` means the maintained public service's
`CayuApp` has no task store, so it cannot bind and run the private task identity
reserved by the application-owned product mapping. Configure a durable
`TaskStore` before deployment.
## public-service-task-store-not-durable
`PUBLIC_SERVICE_TASK_STORE_NOT_DURABLE` means a task store is configured but it
is development-only or has not declared verified durability. Configure a
built-in durable `TaskStore` or a custom store that explicitly declares
`service_durability = RuntimeStoreDurability.DURABLE` after its durability
contract is verified.
These findings apply only to the inspectable service returned by Cayu's
maintained factory contract. The check intentionally reports host-owned routes
outside that contract as unverified; it does not scan arbitrary ASGI source or
claim to prove its authorization behavior. Run the generated assembled-app
suite with `pytest -q tests/test_public_service_security.py` in addition to the
deployment check.
Tool implementations must also declare `run` with `async def`. Cayu validates
that contract during agent registration so a synchronous implementation fails
before a session starts.
Inspection and checks are structural. Clearing all diagnostics does not prove a
provider credential, remote service, sandbox, network path, or deployment is
live. The manifest reports `has_system_prompt` but never prompt text; its
fingerprint records prompt presence, not prompt contents. A prompt edit between
two non-empty values therefore needs a runtime test or eval for verification.
## request-cost
A running Cayu server records the cost of every request: method, route
template, status, wall time, CPU time, and response size, keyed by a hashed
client. It is on by default under `cayu serve` and for local development
(`ServerConfig.local_development()`, or `mount_cayu` with `OpenAccess`). With
`mount_cayu`, pass `observe_host_requests=True` to also time the host app's
own routes, such as the `/api/...` routes your UI polls. Records stay in memory
only and never include raw paths, IDs, query values, cookies, or credentials.
A slow request is cheap to miss locally: on a laptop with a handful of records,
a 300 ms handler polled every 2 seconds looks fine, but in production it can
use a third of a 0.5 vCPU web process for one idle tab and grow with every job
stored. The server logs these warnings, once per condition:
- `cayu.server.slow_request`: a GET route's median time went over 250 ms.
- `cayu.server.growing_cost`: a GET route keeps getting slower as it is called,
which usually means it reads all stored history on each call.
- `cayu.server.hot_poll`: one client called the same GET more than 12 times a
minute for over 2 minutes and mostly got the same response back. See
`cayu guide app-ui` for polling alternatives.
### Leave it idle, measure, fix
Check the cost of an open page before calling UI work done:
1. Start the app (`cayu serve --dev`, or your own server with `mount_cayu`).
2. Open the UI in a browser and leave it idle for 60 seconds.
3. Measure the idle cost against a budget, for example 1% of a 0.5 vCPU web
process:
```bash
cayu diagnostics requests --since 60s --budget-idle-cpu 0.01 --vcpu 0.5
```
Add `--server-url http://127.0.0.1:8000/cayu` when Cayu is mounted at
`/cayu` by `mount_cayu`. The command prints requests per minute, p50/p95
wall time, CPU seconds per minute, and the share of `--vcpu` for each route,
highest CPU first. It exits 1 when the budget is exceeded and 2 when it
cannot read the summary. Use `--json` for machine-readable output.
4. Fix the top route and repeat until the command exits 0. Typical fixes: poll
less often or stop polling a hidden tab, return 304 Not Modified when nothing
changed, push changes over a stream, and make GET handlers read only recent
or changed records instead of rebuilding everything per request.
The same summary is available to operators at `GET /api/diagnostics/requests`
(protected like the rest of the control plane) and can be added to a support
bundle with `cayu doctor --bundle cayu-support.zip --requests-from SERVER_URL`.
CPU time is process CPU shared out across the requests running at the same
time, so work running in the background during a request, such as an agent
run, is charged to it. Measure idle cost while no agent work is running.
Timing adds about 15 microseconds of CPU per request (measured on an Apple M1
Pro with Python 3.14), under 0.1% of one CPU at 50 requests per second.
# Operate Cayu
Start with a local server and durable stores. Before adding multiple processes, define authentication, ownership, migration, recovery and shutdown behavior.
| Task | Guide |
| --- | --- |
| Run the control plane and dashboard | [Server](https://cayu.dev/docs/operate/server/) |
| Configure durable databases | [Storage](https://cayu.dev/docs/operate/storage/) |
| Advance queued and interrupted work | [Workers](https://cayu.dev/docs/operate/workers/) |
| Recover after a crash | [Recovery](https://cayu.dev/docs/operate/recovery/) |
| Diagnose execution | [Observability](https://cayu.dev/docs/operate/observability/) and [diagnostics](https://cayu.dev/docs/operate/diagnostics/) |
| Protect control-plane access | [Authentication](https://cayu.dev/docs/operate/authentication/) |
| Bound work and spend | [Budgets](https://cayu.dev/docs/operate/budgets/) |
| Deploy or upgrade an application | [Deployment](https://cayu.dev/docs/operate/deployment/) and [upgrades](https://cayu.dev/docs/operate/upgrades/) |
The framework owns execution and recorded lifecycle. Your application owns domain transactions, deployment configuration and external services. Running the open-source control plane does not require a hosted service.
# Events and observability
Consume the full event stream when running an agent. A provider response, tool result or visible answer is part of a longer execution record; the terminal session outcome determines whether the runtime completed, failed or interrupted the work.
## Follow one session
Use the dashboard or `cayu session --help` to inspect recorded messages and events. Retain session, interaction, step and operation identities when relating events to domain receipts. Avoid using incidental log ordering as the only proof of a transaction.
Keep three views separate: the durable execution history, the context projected to a model, and the provider's request/response format. A compacted context does not imply the stored trajectory was deleted.
## Diagnose failures
Start with the terminal failure and owning subsystem. Then inspect provider diagnostics, tool policy, environment lifecycle or storage evidence. A cancelled local task does not prove an external operation stopped. A failed observer does not automatically mean the candidate's domain operation failed.
Use [diagnostics](https://cayu.dev/docs/operate/diagnostics/) for structured support workflows and [diagnostics CLI](https://cayu.dev/docs/reference/cli/diagnostics/) for release-matched flags. Review retained data before sharing a diagnostic bundle; your application's prompts, attachments and tool results may contain sensitive information.
Reference: [events](https://cayu.dev/docs/reference/python/events/), [observability](https://cayu.dev/docs/reference/python/observability/) and [runtime guarantees](https://cayu.dev/docs/reference/runtime-guarantees/).
# Recover interrupted work
Recovery starts from recorded state, not from replaying the original prompt. Inspect the session's status, execution owner, pending actions and external-operation evidence before advancing it.
## Establish the recovery boundary
Use `cayu recovery --help` and the [recovery reference](https://cayu.dev/docs/reference/cli/recovery/) to inspect the available planning and execution commands. Choose the exact durable store and bounded selection. Review blockers before executing a recovery plan.
| Recorded condition | Required decision |
| --- | --- |
| Another owner has a live execution lease | Wait for or coordinate with that owner |
| Exact approval or user input is pending | Resolve the owning action through its normal boundary |
| External mutation may have completed | Reconcile its receipt or idempotency state |
| Native work remains unresolved | Preserve the owning continuation; do not invent a result |
| Cleanup did not settle | Retain unknown evidence and use the supported retry path |
An ancestor's delegated-action reference identifies a child but cannot resolve the child's action. Follow the reference to the owning session and apply normal actor and policy checks.
## Prove recovery across a process boundary
Use the [support recipe](https://cayu.dev/docs/recipes/support-agent/) to stop between commands and continue from SQLite in a new process. Test both an approved effect and a denied decision. Retrying delivery should read the existing durable result rather than repeat the domain mutation.
Do not report exactly-once external effects based on session persistence alone. The domain transaction, stable operation identity and receipt are the proof. Read [durability](https://cayu.dev/docs/concepts/durability/), [durable operations](https://cayu.dev/docs/build/durable-operations/) and [session API](https://cayu.dev/docs/reference/python/sessions/).
# Run the server
From a generated project with dependencies installed:
```sh
uv run --no-sync cayu serve --dev --host 127.0.0.1 --port 8000
```
Open `http://127.0.0.1:8000/cayu/`. This is the explicit trusted-local access profile. Without `--dev`, the CLI requires an authentication target before constructing the application or binding a port.
## Application ownership
The configured factory is synchronous and takes no arguments. It returns one fresh, process-scoped `CayuApp`. Importing a project should not start a server, worker or model run. Store construction may perform bounded local setup; long-running services belong to explicit entrypoints.
```toml
[tool.cayu]
factory = "app:build_app"
[tool.cayu.serve]
auth = "cayu.server.environment_auth:OPERATOR_BASIC_AUTH"
```
Provide `CAYU_OPERATOR_USERNAME` and `CAYU_OPERATOR_PASSWORD` through your deployment's secret configuration. Missing or blank values fail closed. Use TLS for deployed HTTP access.
## Embed the server
Use `create_server(app, config=...)` with `ServerConfig.protected(auth)` for a configured application, or `ServerConfig.local_development()` for trusted local work. Authentication, CORS, docs exposure, dashboard configuration and lifecycle policy are explicit typed choices. Naming a deployment “development” does not relax its security settings.
The [HTTP reference](https://cayu.dev/docs/reference/http/) describes the default `/api` control plane. Optional transports and application-specific routes require their corresponding configuration. Read [serve flags](https://cayu.dev/docs/reference/cli/serve/) and [server types](https://cayu.dev/docs/reference/python/server/).
# Durable storage
Generated projects use local SQLite at `data/cayu.db`. Use shared durable stores when independent server or worker processes must coordinate.
## Select one store
Storage-aware CLI precedence is explicit `--sqlite` or `--postgres`, then `CAYU_DATABASE_URL`, then the typed project table, then the existing project's exact `data/cayu.db` file. Resolution does not construct the application or guess another database filename.
```toml
[tool.cayu.session_store]
backend = "sqlite"
path = "data/cayu.db"
```
Relative paths are resolved from `pyproject.toml`. For PostgreSQL, install its extra and use an environment-variable name:
```toml
[tool.cayu.session_store]
backend = "postgres"
env = "CAYU_DATABASE_URL"
```
Keep connection credentials in secret configuration. Set `CAYU_REQUIRE_POSTGRES=1` where a missing database URL must fail instead of selecting SQLite.
## Apply migrations before startup
```sh
cayu storage status --json
cayu storage migrate
```
`status` is read-only: exit `0` means up to date, `3` means a forward migration passes preflight, `4` means incompatible, and `1` or `2` signals an operational or usage error. PostgreSQL application startup validates schema and does not silently run DDL.
For a populated PostgreSQL database, `migrate` requires an explicit supported backup authority or waiver. Inspect [storage command flags](https://cayu.dev/docs/reference/cli/storage/) before choosing one. Preserve the backup and migration receipt with the deployment.
When using transaction pooling, configure `CAYU_DATABASE_DIRECT_URL` for the dedicated listener and migration advisory locks. `CAYU_DATABASE_POOL_MAX` defaults to `5` per pool; account for the listener and separate eval-store pool as well as worker process count.
Reference: [storage API](https://cayu.dev/docs/reference/python/storage/) and [configuration](https://cayu.dev/docs/reference/configuration/).
# Upgrade and verify
These docs describe the latest Cayu release, named in the [release manifest](https://cayu.dev/docs/release.json). Pin that version when reproducing examples. If your installed release differs, use its shipped `cayu guide` topics and release-matched source before applying a signature or configuration example.
```sh
cayu version
cayu inspect --json
cayu check --fail-on warning --json
```
## Review the change
Read the target release notes and public API changes. Update the dependency constraint and lockfile together. Inspect generated-project convention changes before using a generator to add capabilities; scaffold metadata describes construction history rather than granting runtime authority.
Run deterministic tests, representative fresh evaluations, and interrupted-session recovery checks where the changed surface warrants them. A new candidate's result may be incomparable with a baseline when its evidence, authority or judge contract changed.
## Move durable state deliberately
Inspect migration status using the new release and deployment configuration. Back up before applying a supported migration. Do not assume an older binary can read a newly migrated database; use the release's compatibility report and your rollback plan.
Read [storage](https://cayu.dev/docs/operate/storage/) and the [public release notes](https://github.com/cayu-dev/cayu/blob/v0.9.1/docs/release-notes.md).
# Run workers
A named worker is an async function with two required positional arguments, `(app, stop)`. The CLI constructs the app once and supplies an `asyncio.Event` for cooperative shutdown.
```toml
[tool.cayu.workers]
dispatch = "workers:run_dispatch"
```
This complete entrypoint assumes the factory configured a `TaskStoreDispatcher`:
```python
from cayu import CayuApp, TaskStoreDispatcher
async def run_dispatch(app: CayuApp, stop) -> None:
if not isinstance(app.dispatcher, TaskStoreDispatcher):
raise RuntimeError("Configure a TaskStoreDispatcher in the factory")
await app.dispatcher.run_worker(
app, worker_id=f"{socket.gethostname()}-{os.getpid()}", stop=stop
)
```
```sh
cayu worker dispatch --shutdown-grace-seconds 45
```
## Choose the loop
The dispatcher continues existing sessions. `run_task_worker` claims fresh tasks and calls your handler; the handler owns the domain-specific request and terminal result. Use [tasks and workflows](https://cayu.dev/docs/build/tasks-workflows/) to choose between them.
The CLI does not infer a task query, handler or recovery policy. It performs no implicit incomplete-session recovery. Configure recovery explicitly and inspect pending approvals/input before attempting continuation.
## Add processes
On supported POSIX hosts, `--processes N` runs independent interpreters and app graphs. Use the same durable stores and unique worker IDs. In-memory stores remain separate. Limits inside an entrypoint apply per process; an entrypoint that starts the same unclaimed request will do so in every process.
SIGINT/SIGTERM request cooperative shutdown. Grace exhaustion is a bounded process boundary, and a failed child stops siblings. Workers are not automatically restarted by this CLI. Reference: [worker flags](https://cayu.dev/docs/reference/cli/worker/).
# Document review
Begin with a small synthetic document and a specific review contract, such as identifying required fields and reconciling line-item totals. Install only the file integrations your chosen input format requires.
## Split the work
1. Validate attachment type and limits.
2. Extract content with provenance.
3. Check exact facts in code where possible.
4. Ask the model for semantic review against an explicit rubric.
5. Retain a report artifact and verify its bytes and retrieval.
Place file access in tools, deterministic checks in domain code, review instructions in prompts, and completion assertions in tests/evals. A document's embedded instructions do not override those policies.
## Make failure observable
Test an unreadable file, missing field, inconsistent total and unsupported format. Assert the report identifies the evidence gap rather than inventing a value. If a report needs human approval before external delivery, keep drafting, approval and delivery as separate operations with exact identities.
Read [files and artifacts](https://cayu.dev/docs/build/files/), [structured extraction](https://cayu.dev/docs/recipes/structured-extraction/) and [test layers](https://cayu.dev/docs/evaluate/testing/).
# Recipes
Each recipe states the prerequisites, intended proof and where to extend the generated project.
- [Support agent](https://cayu.dev/docs/recipes/support-agent/): credential-free, cross-process approval, mutation receipt and repeat delivery.
- [Structured extraction](https://cayu.dev/docs/recipes/structured-extraction/): schema repair using a scripted provider and actual result assertions.
- [Repository agent](https://cayu.dev/docs/recipes/repository-agent/): bounded coding workspace with tests and reviewable changes.
- [Research agent](https://cayu.dev/docs/recipes/research-agent/): provenance, source coverage and grounded claims.
- [Document review](https://cayu.dev/docs/recipes/document-review/): bounded input, deterministic facts and retained report.
Run the support and extraction fixtures first. Live-provider behavior and remote environments need their own configured credentials and qualifications.
# Repository agent
Prerequisites: Python, `uv`, Git, `rg`, and a supported host for the selected workspace/runner. The coding preset checks its local workspace requirements before generation. Remote execution adapters require their own configuration.
```sh
uvx cayu new repo-agent --preset coding
cd repo-agent
uv sync --extra dev
uv run --no-sync cayu inspect --json
uv run --no-sync cayu check --fail-on warning --json
uv run --no-sync pytest -q
uv run --no-sync cayu eval run
```
Read the generated README and `AGENTS.md` before running against a repository. Choose an explicit workspace root and scoped provider configuration. Begin with a synthetic repository and one bounded task, such as adding an empty-input check and its meaningful regression test.
## Verify the work
Require a real diff, passing relevant tests, retained artifacts and a clear terminal session outcome. Include an authorization case for a command or path outside the allowed boundary. A final answer saying “tests passed” does not replace the test process's exit code and output.
Keep file/terminal tools, policies, environment construction and completion rules in their generated owning modules. Use a review workflow to inspect the actual change before publishing it.
Read [environments](https://cayu.dev/docs/build/environments/), [verified work](https://cayu.dev/docs/build/verified-work/) and [coding-agent instructions](https://cayu.dev/docs/get-started/coding-agents/).
# Research agent
Start with `cayu new research-agent` and its credential-free tests. A live research provider or external search tool needs separately configured credentials and policy. Use public or synthetic sources for your first evaluation.
## Define the result
Require an answer, source identities, publication/retrieval dates where relevant, claim-to-source links and stated gaps. Set a source and work budget. Give tools a narrow retrieval job; retrieved pages cannot authorize additional capabilities.
Separate retrieval from synthesis. Keep source text and provenance in bounded artifacts or knowledge interfaces rather than expanding every page into every model call. Decide how stale evidence is refreshed.
## Evaluate groundedness
Use deterministic assertions for required source coverage, file existence and structured fields. Use a rubric-based judge for whether each material claim is supported by the cited evidence. Test missing, contradictory and instruction-bearing source text.
A research council can add specialist roles, but every role needs explicit evidence and completion rules. More agents do not establish correctness by themselves. Compare the same cases and resource limits before adopting the extra coordination.
Read [tasks and collaboration](https://cayu.dev/docs/build/tasks-workflows/), [context and memory](https://cayu.dev/docs/build/context-memory/) and [quality judges](https://cayu.dev/docs/evaluate/judges/).
# Structured extraction
Install the matching release in a Python environment:
```sh
python -m pip install cayu
```
Download [structured-output.py](https://cayu.dev/docs/examples/structured-output.py), then run:
```sh
python structured-output.py
```
The [complete source and explanation](https://cayu.dev/docs/build/structured-output/) sends an invalid first response and a valid repaired response through `ScriptedModelProvider`. It asserts success, an empty tool-strategy final text, the exact validated output `{"invoice_total": 42.5}` and repair attempt `2`.
This proves runtime schema validation and repair for the fixture. To evaluate a real invoice extractor, replace the scripted provider in your project, use representative synthetic documents, and add assertions for totals, currency and evidence location. A valid schema alone does not prove a correct invoice total.
# A restartable support agent with external approval
This guide ships with the Cayu package, so the installed version is always available offline with `cayu guide order-support`. [Release source](https://github.com/cayu-dev/cayu/blob/v0.9.1/src/cayu/guides/order-support.md).
This installed application investigates a damaged order, asks which item was
broken, resumes the conversation in another process, and holds a replacement
call for an externally issued approval. It uses the same public contracts as
`cayu guide durable-service-tools` and `cayu guide durable-operations`; read those
companions for identity declarations and ambiguous-outcome reconciliation.
The canonical executable source ships as `cayu.examples.order_support`. There is
no checkout dependency or second copy to keep in sync. Inspect it with:
```sh
python -c 'import inspect, cayu.examples.order_support as example; print(inspect.getsource(example))'
python -m cayu.examples.order_support --help
```
## Shortest deterministic journey
`investigate -> propose -> wait for approval -> execute -> verify`
In this example, creating a bounded replacement proposal does **not** require
representative approval. After clarifying the damaged item, create the proposal
and request execution of that exact proposal; Cayu holds the execution tool call
until verified approval arrives. Approval gates execution, not proposal creation.
Customer prose is not an approval receipt, and resuming a conversation does not
approve a protected tool call. Other applications may choose different proposal
authorization rules.
The application authenticates the external receipt and checks business authority.
Cayu owns the pending tool approval and execution evidence; the downstream service
owns its idempotency and effect contract. These responsibilities are separate even
when the demo stores live in the same state directory.
With Cayu installed, run these commands from an empty working directory. Every
command starts and exits a separate OS process. No network or provider credentials
are needed. Use the same interpreter throughout.
```sh
python -m cayu.examples.order_support init demo-state demo-operator
python -m cayu.examples.order_support start demo-state
python -m cayu.examples.order_support clarify demo-state --item mug
python -m cayu.examples.order_support review demo-state review.json
python -m cayu.examples.order_support issue demo-state demo-operator review.json receipt.json approve
python -m cayu.examples.order_support deliver demo-state receipt.json
python -m cayu.examples.order_support verify demo-state
python -m cayu.examples.order_support deliver demo-state receipt.json
```
`start` reads the order, tracking, and policy, then asks whether the mug or plate
was damaged. `clarify` resumes the same session, persists one immutable proposal,
and exits with a native pending approval. No replacement exists yet. `review`
exports the proposal and the persisted native approval/round/call identities.
`issue` runs the separate representative fixture and signs its decision; it does
not execute a replacement. `deliver` verifies that receipt and constructs a
`ToolApprovalRequest`. Cayu executes the protected tool and the agent makes a
separate verification read. `verify` performs another read in a new conversation
turn. Delivering the identical receipt again reuses the completed resolution and
creates no additional replacement. Commands print Runtime events, not only a
canned success message.
The initial run uses `max_steps=18`. Later `ResumeRequest`s omit that setting and
inherit the verified persisted model-loop configuration; they do not need to
repeat it. Explicit overrides still undergo execution-profile admission. Older
sessions without matching stored configuration retain the existing default behavior.
The independently persisted databases are `demo-state/sessions.sqlite` (Cayu)
and `demo-state/service.sqlite` (orders, proposals, issued receipts, effects).
The representative's private signing key is under `demo-operator`, created with
owner-only permissions and never passed to a model or tool. Possession of that
file represents representative authority **only in this local teaching fixture**.
Production needs authenticated operators, protected signing infrastructure,
real business authorization, key rotation/revocation, and appropriate expiry.
Do not deploy this local fixture as an authentication service.
To repeat from fresh state, remove only these generated fixture files/directories:
```sh
rm -rf demo-state demo-operator review.json receipt.json
```
## Rejection and invalid delivery
Run `init`, `start`, `clarify`, and `review` again, then issue `deny` instead of
`approve`. Deliver and verify it: the replacement list stays empty. Re-delivery of
that denial also has no effect.
```sh
python -m cayu.examples.order_support issue demo-state demo-operator review.json receipt.json deny
python -m cayu.examples.order_support deliver demo-state receipt.json
python -m cayu.examples.order_support verify demo-state
```
With a pending approval, a receipt delivered with `--session another-session`
is rejected. Editing the receipt's proposal, decision, native IDs, or receipt ID
invalidates its signature. An unregistered receipt fails verification, even with
a valid signature. A legitimately issued receipt for different native IDs cannot
resolve the pending action. None of these paths calls the protected service tool.
Changing `--version 2` on delivery demonstrates profile rejection; the example
never auto-adopts changes or disables checks.
## Receipt adapter and state ownership
Read `receipt_to_native_request` in the canonical source for the complete adapter.
It verifies the pinned public-key signature, issued-receipt digest, conversation,
proposal contents/hash, bounded action, and approver authority. It then compares
the native identities with the pending action before constructing the typed
request with actor provenance. A repeated delivery still goes through Cayu's
completed-resolution contract; the adapter never calls the replacement service.
`ResolutionActorSource.REQUEST` accurately describes this trusted SDK adapter's
identity assertion; it does not claim server HTTP authentication.
Pending-action arguments are quarantined. The application therefore stores one
bounded immutable proposal per conversation. Its tool policy checks the exact
proposal ID against the protected call **before** requiring approval. The
representative reviews that service record, not inferred redacted arguments.
External receipt IDs, service action IDs, and Cayu approval/round/call IDs are
separate namespaces and are never substituted for each other.
| Owner | Records and responsibilities |
| --- | --- |
| Cayu | Conversation, pending approval, tool-round evidence, admission, supported resume/recovery, completed-resolution replay |
| Application/service | Operator authentication and business authorization, immutable proposal, receipt signature/binding verification, stable downstream action key and receipt, reconciliation |
This avoids an application conversation log, an application pending-tool state
machine, and a direct post-approval replacement handler. It intentionally retains
business proposals, representative receipts, and downstream idempotency records.
`review.json` is a transport view, not another authority for pending Runtime state.
An application-owned approval workflow remains reasonable when business approvals
span multiple systems or precede a Runtime session; use native tool approval when
holding this exact pending tool call is the useful boundary.
## Optional live provider
Set `OPENAI_API_KEY` and `SUPPORT_MODEL` in the environment, using a model available
to your account. Do not put provider secrets in files or command arguments. Start
with fresh state and add `--live` to **every** start/clarify/review/deliver/verify
command (init and issue are provider-independent). Never switch an existing
scripted session to live mode: that changes its execution profile.
For example, after setting those two environment variables:
```sh
python -m cayu.examples.order_support init live-state live-operator
python -m cayu.examples.order_support start live-state --live
python -m cayu.examples.order_support clarify live-state --item mug --live
python -m cayu.examples.order_support review live-state live-review.json --live
python -m cayu.examples.order_support issue live-state live-operator live-review.json live-receipt.json approve
python -m cayu.examples.order_support deliver live-state live-receipt.json --live
python -m cayu.examples.order_support verify live-state --live
```
The live model chooses its investigative calls and clarification using the same
tools, policy, stores, proposal and approval adapter. It runs no authoring CLI.
The deterministic script proves Runtime/service boundaries; it does not establish
live-model judgment or behavior. Live mode is optional and is validated separately.
## When every turn must end in a tool
Many support agents end each turn through one of a few tools, for example
`ask_customer` for a clarification or `propose_resolution` for a recorded
proposal. Live models occasionally stop before calling one, sometimes with an
empty step (`model.completed` with `step_classification.type == "invalid"`).
Cayu completes that turn normally unless a loop policy says otherwise, and your
application then has neither a question nor a proposal to return.
Pass `RequireFinalTool` on every `RunRequest` and `ResumeRequest` for that agent:
```python
from cayu import RequireFinalTool, ToolCompletionPolicy
names = ("ask_customer", "propose_resolution")
final_tool = RequireFinalTool(names)
completion = ToolCompletionPolicy(tool_names=names)
RunRequest(..., loop_policies=(final_tool,), tool_completion=completion)
ResumeRequest(..., loop_policies=(final_tool,), tool_completion=completion)
```
If the model stops before one of those tools has returned, the policy appends a
reminder and the model continues, up to two times. After that, the session is
interrupted with a reason instead of completing. A tool result counts only when
`is_error` is false, so return `ToolResult(..., is_error=True)` for failures such
as rejected evidence or an unavailable service. The example's `propose_replacement`
does this when the conversation already has a different proposal. The policy's
identity comes from its configuration, so use the same tool names in every process.
`tool_completion` skips the provider request after a successful final tool when
the host displays the question or proposal from that tool's result. Both tools
must be registered with `ToolEffect.NONE` or `ToolEffect.IDEMPOTENT`; an
`EXTERNAL` execution tool is ineligible. The SDK and HTTP run/resume requests
accept the same `{"tool_names": [...]}` setting. Unknown, blank, duplicate and
ineligible names fail before provider dispatch.
A single successful call completes with `session.completed` reason
`host_rendered_tool`. `run_to_completion(...).tool_completion` contains its
published call, effect and result, including the tool-round identity. Render
that result in the application. Cayu keeps the assistant call and tool result
in the conversation and does not generate an extra answer. A failed, denied,
blocked or malformed call does not trigger this completion. A round containing
sibling calls executes through the ordinary loop, preserving every call.
`RequireFinalTool` supplies reminders when the model stops too early;
`ToolCompletionPolicy` completes immediately when the designated tool succeeds.
Without `tool_completion`, the existing post-tool model step still runs. Pass
both settings consistently on fresh turns. They belong to the execution profile,
so changing or omitting an established policy requires the existing profile
adoption flow. Approval resolution and crash recovery retain the admitted policy.
`tool_completion` cannot be combined with `structured_output`, which owns a
separate validated output contract.
## Troubleshooting and limits
- Duplicate session: `run` creates, `resume(ResumeRequest(...))` continues an
ordinary conversation. The CLI's `clarify` and `verify` use resume. Inspect a
running or pending session before choosing approval/input resolution or recovery.
- Process died during a tool: ordinary resume automatically recovers an executor
proved dead on the same host, or one whose execution lease has expired.
`SessionExecutionInProgress` means the executor may still be running; retry
after it finishes or its lease expires (60 seconds by default). For an older
session without a recorded owner, first establish that its executor stopped,
then call `app.recover_incomplete_session(IncompleteSessionRecoveryRequest(...))`
and inspect the result before resuming. The `recover` command demonstrates
explicit recovery. Unknown external effects still interrupt for receipt-based
reconciliation; automatic recovery never blindly repeats the proposal write.
- Execution-profile mismatch: keep the same provider/model and declared versions
across processes. Process-local opaque identities need stable declarations from
the first run; adding one does not repair a persisted opaque baseline. Digests
may support only class-level diagnosis. See `cayu guide durable-service-tools`.
- No pending proposal: the live model may still be asking a question, or the
approval may already be resolved. Inspect events; never fabricate native IDs.
- Signature/binding error: obtain the receipt for this exact reviewed proposal and
conversation from the trusted representative. Do not rewrite signed fields.
The fixture supports process exits at completed conversation and pending-approval
boundaries. It does not prove arbitrary crash recovery, multi-host coordination,
power-loss durability, or generic exactly-once external effects. The local service
uses an atomic SQLite insert with a stable unique action key. A real remote service
must durably deduplicate that key and reconcile an ambiguous external outcome
before retrying. Never blindly replay an unknown effect: follow the existing
protocol in `cayu guide durable-operations` and `cayu guide tool-effects`.
# Cayu offline capability references
This guide ships with the Cayu package, so the installed version is always available offline with `cayu guide references`. [Release source](https://github.com/cayu-dev/cayu/blob/v0.9.1/src/cayu/guides/references.md).
These compact references are shipped in the Cayu package. They describe the
public seam to start from and the proof boundary to preserve. Use the smallest
section that matches the requested behavior. For an end-to-end operational
change, start with `cayu guide durable-operations`; it composes the sessions,
approvals, tool effects, pending actions, verification, and recovery seams below.
## domain-tool
A native Python tool subclasses `Tool`, declares one immutable `ToolSpec`, and
implements `async def run(ctx: ToolContext, args: dict) -> ToolResult`.
For a tool wrapping an application-owned service, use
`cayu guide durable-service-tools` for stable component identities and explicit
knowledge injection or environment binding across process reconstruction.
`ToolSpec` is the registered declaration:
- `name`: required durable tool identity.
- `description`: model-facing purpose; defaults to an empty string.
- `input_schema`: JSON Schema for `args`; defaults to unconstrained `{}`. Declare
properties, required fields, and `additionalProperties` deliberately.
- `parallel_safe`: whether Cayu may execute the tool alongside siblings; defaults
to `True`.
- `effect`: replay/mutation classification; defaults to `ToolEffect.EXTERNAL`, but
application tools should choose it explicitly with `cayu guide tool-effects`.
The public `Tool.schema` property is authoritative at registration. Override it
only when the runtime schema is derived rather than the declared
`ToolSpec.input_schema`. Most subclasses declare `spec` on the class as shown
below; a dynamically configured subclass may instead pass a `ToolSpec` to the
inherited constructor. Tool instances expose that declaration through `spec`,
derive `name` and `description` from it, and expose the effective input schema
through `schema`.
`ToolContext` carries invocation identity and admitted runtime resources. Cayu
constructs `ToolContext` for normal runtime execution and supplies the session
identity, applicable agent/environment/budget identities, JSON `metadata`, and
the resource handles admitted for that invocation. `session_id` is the only
required `ToolContext` constructor field. Optional identity fields are
`agent_name`, `environment_name`, `causal_budget_id`, `workspace_id`,
`artifact_store_id`, and `idempotency_key`. Depending on the registered
environment, Cayu may also provide `workspace`, `artifact_store`, `runner`,
`vault`, `proxy`, and `knowledge_store`. These resource handles may be absent,
so a tool that requires one must fail clearly when it is `None`. `mcp_servers`
is always a tuple, and absence is represented by `()`; check whether it is
empty rather than comparing it with `None`. Do not set the runtime-owned
secret-capture hooks; they are Cayu wiring rather than tool-author
configuration.
`ToolResult` has four fields:
- `content`: model-facing durable text; defaults to `""`.
- `structured`: optional JSON object for workflows and operator surfaces.
- `artifacts`: JSON object descriptors for durable outputs; defaults to `[]`.
- `is_error`: whether the tool reports a failed result; defaults to `False`.
`structured` and `artifacts` are recursively read-only after construction. Use
`result.model_dump(mode="json")` when a serializer or transformation needs
ordinary JSON dictionaries and lists, and construct a replacement `ToolResult`
when changing a result.
Construct a bare unit-test context with
`ToolContext(session_id="test-session")`. This direct implementation unit test
needs no application graph:
```python
from cayu import Tool, ToolContext, ToolEffect, ToolResult, ToolSpec
class LookupTool(Tool):
spec = ToolSpec(
name="lookup",
description="Look up one reviewed record.",
input_schema={
"type": "object",
"properties": {"query": {"type": "string"}},
"required": ["query"],
"additionalProperties": False,
},
effect=ToolEffect.NONE,
)
async def run(self, ctx: ToolContext, args: dict) -> ToolResult:
return ToolResult(
content=f"Found {args['query']}",
structured={"session_id": ctx.session_id},
)
tool = LookupTool()
context = ToolContext(session_id="test-session")
result = asyncio.run(tool.run(context, {"query": "Cayu"}))
assert result.content == "Found Cayu"
assert result.structured == {"session_id": "test-session"}
```
Calling `run` directly proves the implementation for caller-supplied arguments.
It does not prove schema validation, policy authorization, runtime resource
admission, event emission, or model behavior. For a fresh starter run
`cayu generate tool NAME --agent AGENT --effect EFFECT` to create a schema,
runtime test, eval, and tracer-bullet check loop through Cayu.
## approvals
Run `cayu guide order-support` for the installed restartable support agent and
external-receipt-to-native-approval adapter.
The runnable proposal-to-verification recipe is `cayu guide durable-operations`.
Tool effects describe replay risk; tool policies authorize execution. Use
`AlwaysRequireApprovalToolPolicy` for a named external-effect tool that needs a
human decision. Handle `SessionStatus.INTERRUPTED`, persist the pending action,
and resume through the public approval request APIs. Approval prompts alone are
not enforcement.
For application validation plus maintained authorization, use a narrowing
composition:
```python
from cayu import GuardedToolPolicy, RequiredArguments, AlwaysRequireApprovalToolPolicy
policy = GuardedToolPolicy(
guards=(RequiredArguments({"ask_user": ("question",)}), application_guard),
then=AlwaysRequireApprovalToolPolicy(tools=("remember_knowledge",)),
)
```
A custom `application_guard` implements async `check(request)` and returns `None`
or a `ToolPolicyResult(decision=ToolPolicyDecision.DENY, ...)`. Any other result or
ordinary exception denies. Guards cannot authorize a call or change the request
passed to downstream enforcement. Cayu checks coverage without running guards;
unknown downstream policies and overriding policy subclasses remain unknown.
Declare a guard's `execution_profile_identity` for portable recovery and bump it
when behavior changes. Undeclared custom guards remain process-local.
## environments
An `Environment` groups a workspace, runner, artifact store, vault, credential
proxy, knowledge store, and MCP servers. Register a static environment or an
`EnvironmentFactory`; choose a default explicitly. Local workspaces and runners
are development conveniences, not isolation boundaries.
## artifacts
Use an `ArtifactStore` for stable uploads and generated outputs. Bridge a
workspace file into an artifact with the public copy helpers, retain artifact
identity in durable state, and test with a temporary local artifact store.
## secrets-egress
Resolve secrets through a `Vault`/`SecretRef`, and put outbound authority in
credential proxies and `HttpEgressPolicy`. Do not place secret values in source,
prompts, manifests, diagnostics, tool results, or generated plans. Virtual
credentials keep provider credentials out of the tool process.
## mcp
Use `McpServerSpec`, an MCP client, and `McpToolset`/`McpToolAdapter` to expose
remote tools. Register `tools=toolset.tools` for a static snapshot, or explicitly
register `mcp_toolsets=(toolset,)` when the application owns the complete source
and needs `await app.refresh_mcp_toolset(toolset)`. Refreshable sources require a
stable `connection_id`; wrappers sharing one live session also share one source
owner and generation fence, and additions never widen existing session ceilings.
Targeted, discovered, and provider-native references keep using Cayu's canonical
catalogue-bound authority: a changed live source generation rejects an older
reference before policy or target work, while an exactly unchanged refresh keeps
the existing generation. Approval recovery, resume, and forks continue through
the ordinary execution-profile and durable capability-ceiling checks rather than
an MCP-specific recovery path.
Apply `McpManifestPolicy`, and test discovery limits, quarantine, stale snapshots,
timeouts, naming collisions, and policy rejection without trusting server-provided
descriptions as authority. When an explicitly refresh-owned built-in session declares
exact `tools.listChanged: true`, Cayu fences the dirty source and coalesces
`notifications/tools/list_changed` into the same governed refresh path. Stdio uses
its existing reader; Streamable HTTP also owns an optional GET/SSE listener, with
HTTP 405 falling back to explicit refresh and never polling. See
`examples/mcp_list_changed_refresh.py` for a deterministic credential-free server.
MCP 2026-07-28 subscription and cache-hint capabilities remain deferred.
## sessions
Run `cayu guide order-support` for the installed restartable support agent and
external-receipt-to-native-approval adapter.
`SessionStore` owns durable session identity, transcript, events, status,
checkpoints, and pending actions. Use `RunRequest.session_id` when identity must
be stable and the resume/interrupt/fork APIs for lifecycle changes. Inspect a
configured store read-only with `cayu session`.
## context
Context policies select model-facing history; context counting and pressure
estimation decide when to trim or compact. Compaction produces a checkpointed
summary but does not erase the durable transcript. Default-on privacy-safe
`RequestFootprint` events describe the final prepared request without retaining
its content or making a provider call; optional keyed HMAC identities make
request and cache-prefix equality comparable within one key version. Official
provider token counting remains separately opt-in. Overflow recovery must be
bounded and provider-neutral.
## knowledge
Knowledge stores hold reviewed/retrievable entries, separate from transcript
history and working files. Use explicit namespace, visibility, status, and
actor fields. Prove local remember/search behavior with an in-memory or SQLite
store before adding embeddings or remote infrastructure.
## background-work
`TaskStore` owns durable work; a dispatcher claims it and a worker executes it.
Event watchers react to persisted events with delivery identity and retries.
Keep enqueue, claim, execution, result recording, and recovery distinct so a
process crash does not silently duplicate work.
## workflows-hooks
Workflow helpers compose deterministic steps around agent runs. Runtime hooks
observe or gate documented phases. Keep orchestration state explicit and use
`workflow_tool_names` when instructions depend on exact registered tool names;
`cayu check` validates that structural contract.
## subagents
`SubagentTool` delegates model work to a child session with explicit context
and execution policy. Parent and child retain separate durable identities.
Test child completion/interruption assertions and define cancellation or
background behavior rather than treating delegation as an in-process function.
## evals
An `EvalPlan` combines a `CayuApp` and `EvalSuite`. Use
`ScriptedModelProvider`, in-memory stores, runtime-native trajectory assertions,
and bounded probes for hermetic regression proof. Scripted calls prove handling
of predetermined behavior, not live prompt comprehension.
## cost-control
Usage events feed session summaries, pricing, budgets, and run limits. Configure
the model catalog and price book explicitly, distinguish unpriced usage, and
test the stop boundary. Estimated cost is evidence with provenance, not a bill.
## observability
`cayu inspect` and `cayu check` are structural and credential-free. The console
and dashboard inspect durable state; logging and OpenTelemetry sinks observe
events. Read `tool.exposure.recorded` for content-minimized model-step capability
projection and `request.footprint.recorded` for final-request shape and typed
proof availability. Both may include the application-selected public non-secret
tool-exposure profile label; do not treat local estimates or fingerprints as
billing, exact provider-wire evidence, or proof about hidden provider prompts.
Do not confuse successful export with live provider, network, sandbox, or
deployment verification.
## server
The server extra provides an HTTP control plane over the same application and
durable stores. A generated project's `dev` extra installs it; for trusted local
inspection run `uv run cayu serve --dev` and open
`http://127.0.0.1:8000/cayu/`. Without `--dev`, `cayu serve` requires the
`[tool.cayu.serve].auth` target; generated agent and coding projects set it to
`cayu.server.environment_auth:OPERATOR_BASIC_AUTH`, which reads
`CAYU_OPERATOR_USERNAME` and `CAYU_OPERATOR_PASSWORD` at startup and refuses to
start without them (on Cayu Cloud, `cayu cloud service credentials --application
APP` shows them). The target may be any request-to-`AuthContext` dependency, such
as `BasicAuth.from_environment(...)` with other variables or, with
`cayu[oidc]`, `OidcBearerAuth.from_environment()` for bearer tokens from your
identity provider; it guards operator access to the control plane,
not end-user product login, which belongs in the service preset's
`AuthenticatedProductAccess`. Use `mount_cayu(..., path="/cayu")` when an
existing FastAPI product owns the host server; that mount requires
`AuthenticatedAccess(...)` on any public listener. Do not substitute client-IP
or forwarded-header checks for authentication. Put authentication/authorization
at the application boundary, run schema migration explicitly, and separate API
processes from task workers. Test the app factory and auth boundary before
deployment.
`OidcBearerAuth` from `cayu[oidc]` verifies JWT bearer tokens from an OpenID
Connect provider (Cognito, Auth0, Okta, Entra ID, Google, Workday) for either
operator access or, through `product_dependency()`, product access. It does not
provide browser sign-in.
## advanced-runtime
Advanced authority, isolation, caching, speculation, and recovery strategies
compose only when their evidence boundaries are explicit. Preserve stable
operation identity, fail closed on unknown policy coverage, and verify live
capabilities separately from a structural manifest.
## Human-attention notifications
Use `cayu guide human-attention` for the canonical EventSink, pending-action,
durable destination, and restart-reconciliation integration.
# cayu agent
Generated from the released command parser. These commands show help only; operations run when you invoke the command without `--help`.
```console
usage: cayu agent [-h] {bundle} ...
Manage portable Cayu agent representations.
positional arguments:
{bundle}
bundle Inspect and convert AgentBundle representations.
options:
-h, --help show this help message and exit
```
## cayu agent bundle
```console
usage: cayu agent bundle [-h] {export,inspect,import,unpack,pack} ...
Inspect or convert the single-file .cayu transport and its canonical unpacked
AgentBundle directory.
positional arguments:
{export,inspect,import,unpack,pack}
export Export an authorized durable snapshot as one .cayu
file.
inspect Validate and inspect one .cayu file.
import Validate, import, and pin a .cayu file into durable
stores.
unpack Unpack one .cayu file without changing bundle
identity.
pack Pack one canonical AgentBundle directory as .cayu.
options:
-h, --help show this help message and exit
```
## cayu agent bundle export
```console
usage: cayu agent bundle export [-h] --snapshot-store SNAPSHOT_STORE
--object-store OBJECT_STORE
--snapshot-root SNAPSHOT_ROOT
--binding-id BINDING_ID
--authority-scope-fingerprint AUTHORITY_SCOPE_FINGERPRINT
[--profile {reusable_agent,continuing_agent,evaluation_candidate}]
[--operation-id OPERATION_ID] --output OUTPUT
Export and protect an authorized snapshot from durable SQLite/CAS storage,
then publish one deterministic full .cayu file.
options:
-h, --help show this help message and exit
--snapshot-store SNAPSHOT_STORE
Durable SQLite AgentSnapshot store.
--object-store OBJECT_STORE
Durable filesystem AgentSnapshot object store.
--snapshot-root SNAPSHOT_ROOT
Authorized snapshot root SHA-256.
--binding-id BINDING_ID
Authorized binding SHA-256.
--authority-scope-fingerprint AUTHORITY_SCOPE_FINGERPRINT
Authorized source scope SHA-256.
--profile {reusable_agent,continuing_agent,evaluation_candidate}
Portable snapshot profile (default: reusable_agent).
--operation-id OPERATION_ID
Stable retry identity; derived when omitted.
--output OUTPUT Destination .cayu file.
```
## cayu agent bundle inspect
```console
usage: cayu agent bundle inspect [-h] [--json] source
Validate every transferred entry in one .cayu file and report bounded safe
metadata before import or materialization.
positional arguments:
source Source .cayu file.
options:
-h, --help show this help message and exit
--json Emit canonical JSON metadata.
```
## cayu agent bundle import
```console
usage: cayu agent bundle import [-h] --snapshot-store SNAPSHOT_STORE
--object-store OBJECT_STORE --subject SUBJECT
--authority-scope-fingerprint AUTHORITY_SCOPE_FINGERPRINT
--owner OWNER
[--retention-class {transient,run_evidence,candidate,champion,release,legal_hold}]
[--operation-id OPERATION_ID]
source
Validate one .cayu file, import its exact closure into SQLite/CAS storage, and
atomically publish and pin the rebound snapshot root.
positional arguments:
source Source .cayu file.
options:
-h, --help show this help message and exit
--snapshot-store SNAPSHOT_STORE
Durable SQLite AgentSnapshot store.
--object-store OBJECT_STORE
Durable filesystem AgentSnapshot object store.
--subject SUBJECT Destination AgentSnapshotSubject JSON document.
--authority-scope-fingerprint AUTHORITY_SCOPE_FINGERPRINT
Authorized destination scope SHA-256.
--owner OWNER Durable pin owner.
--retention-class {transient,run_evidence,candidate,champion,release,legal_hold}
Durable pin retention class (default: release).
--operation-id OPERATION_ID
Stable retry identity; derived when omitted.
```
## cayu agent bundle unpack
```console
usage: cayu agent bundle unpack [-h] --destination DESTINATION source
Validate and convert one .cayu file to its canonical directory for CAS,
debugging, or a later governed import.
positional arguments:
source Source .cayu file.
options:
-h, --help show this help message and exit
--destination DESTINATION
```
## cayu agent bundle pack
```console
usage: cayu agent bundle pack [-h] --output OUTPUT source
Validate and convert one canonical AgentBundle directory into deterministic
.cayu bytes for copying or download.
positional arguments:
source Canonical unpacked AgentBundle directory.
options:
-h, --help show this help message and exit
--output OUTPUT Destination .cayu file.
```
## Related guides
[Getting started](https://cayu.dev/docs/get-started/quickstart/) · [Operations](https://cayu.dev/docs/operate/) · [Configuration](https://cayu.dev/docs/reference/configuration/)
# cayu auth
Generated from the released command parser. These commands show help only; operations run when you invoke the command without `--help`.
```console
usage: cayu auth [-h] {openai} ...
Manage local provider sign-ins.
positional arguments:
{openai}
openai Manage the experimental OpenAI subscription sign-in.
options:
-h, --help show this help message and exit
```
## cayu auth openai
```console
usage: cayu auth openai [-h] {login,status,logout} ...
Manage the experimental OpenAI subscription sign-in. Use `cayu auth openai
status` to inspect local state.
positional arguments:
{login,status,logout}
login Sign in with a ChatGPT Plus/Pro subscription.
status Show subscription sign-in status.
logout Delete Cayu's local subscription credentials.
options:
-h, --help show this help message and exit
```
## cayu auth openai login
```console
usage: cayu auth openai login [-h] [--headless] [--no-browser]
Sign in with a ChatGPT Plus/Pro subscription. Run `cayu auth openai status`
afterward to verify local state.
options:
-h, --help show this help message and exit
--headless, --device-code
Use the device-code flow for SSH, containers, and
remote hosts.
--no-browser Print the authorization URL without opening a browser.
```
## cayu auth openai status
```console
usage: cayu auth openai status [-h]
Show subscription sign-in status. Use `cayu auth openai login` when no active
credentials are available.
options:
-h, --help show this help message and exit
```
## cayu auth openai logout
```console
usage: cayu auth openai logout [-h]
Delete Cayu's local subscription credentials. Run `cayu auth openai status` to
confirm removal.
options:
-h, --help show this help message and exit
```
## Related guides
[Getting started](https://cayu.dev/docs/get-started/quickstart/) · [Operations](https://cayu.dev/docs/operate/) · [Configuration](https://cayu.dev/docs/reference/configuration/)
# cayu check
Generated from the released command parser. These commands show help only; operations run when you invoke the command without `--help`.
```console
usage: cayu check [-h] [--format {json,table} | --json | --table]
[--output FILE] [--tag TAG] [--deploy]
[--fail-on {info,warning,error}]
[target]
Validate a booted Cayu project with actionable diagnostics. Run `cayu guide
diagnostics` to interpret stable finding codes.
positional arguments:
target Override project discovery with a module:factory
target.
options:
-h, --help show this help message and exit
--format {json,table}
Output format (default: json).
--json Emit JSON (shorthand for `--format json`).
--table Emit human-readable output (shorthand for `--format
table`).
--output, -o FILE Write output to FILE instead of stdout.
--tag TAG Run checks carrying this tag (repeatable).
--deploy Run only checks that gate deployment.
--fail-on {info,warning,error}
Lowest severity that exits 1 (default: error).
```
## Related guides
[Getting started](https://cayu.dev/docs/get-started/quickstart/) · [Operations](https://cayu.dev/docs/operate/) · [Configuration](https://cayu.dev/docs/reference/configuration/)
# cayu cloud
Generated from the released command parser. These commands show help only; operations run when you invoke the command without `--help`.
```console
usage: cayu cloud [-h] [--api-key-file API_KEY_FILE] [--context CONTEXT]
[--evidence-dir EVIDENCE_DIR]
[--timeout-seconds TIMEOUT_SECONDS]
{login,logout,whoami,applications,context,doctor,init,deploy,deployment,rollback,runtimes,service,env,evidence} ...
Manage Cayu Cloud.
positional arguments:
{login,logout,whoami,applications,context,doctor,init,deploy,deployment,rollback,runtimes,service,env,evidence}
login Sign in through WorkOS.
logout Delete the local login.
whoami Show the current user and Organization.
applications List deployed Agent applications.
context Select or inspect a private connection context.
doctor Verify authentication and API access.
init Generate cayu-cloud.toml.
deploy Publish an immutable Agent release.
deployment Inspect or operate an immutable release.
rollback Select an earlier immutable release.
runtimes Inspect retained runtime artifacts.
service Operate long-horizon Agent infrastructure.
env Manage Agent environment variables and secrets.
evidence Inspect local Cloud command evidence.
options:
-h, --help show this help message and exit
--api-key-file API_KEY_FILE
Defaults to context.
--context CONTEXT Private Cayu Cloud context JSON.
--evidence-dir EVIDENCE_DIR
--timeout-seconds TIMEOUT_SECONDS
```
## cayu cloud login
```console
usage: cayu cloud login [-h] [--no-browser]
Sign in to Cayu Cloud through WorkOS in a web browser.
options:
-h, --help show this help message and exit
--no-browser Print the verification URL and code without opening a browser.
```
## cayu cloud logout
```console
usage: cayu cloud logout [-h]
Delete the local interactive Cayu Cloud login.
options:
-h, --help show this help message and exit
```
## cayu cloud whoami
```console
usage: cayu cloud whoami [-h]
Show the current Cayu Cloud user and Organization.
options:
-h, --help show this help message and exit
```
## cayu cloud applications
```console
usage: cayu cloud applications [-h] {list,archive,archive-status} ...
List Agent applications deployed in the selected Cayu Cloud.
positional arguments:
{list,archive,archive-status}
options:
-h, --help show this help message and exit
```
## cayu cloud applications list
```console
usage: cayu cloud applications list [-h] [--lifecycle {active,archived,all}]
List Agent applications and their selected releases.
options:
-h, --help show this help message and exit
--lifecycle {active,archived,all}
Active Agents by default; archived also lists Agents
being archived.
```
## cayu cloud applications archive
```console
usage: cayu cloud applications archive [-h]
--expected-revision EXPECTED_REVISION
[--idempotency-key IDEMPOTENCY_KEY]
[--no-wait]
[--poll-seconds POLL_SECONDS]
[--wait-seconds WAIT_SECONDS]
application
Archive an Agent: stop its service, schedules and ingress for good and keep
its Releases, configuration, data and history. Requires an organization
administrator login and can't be undone.
positional arguments:
application The exact Agent slug.
options:
-h, --help show this help message and exit
--expected-revision EXPECTED_REVISION
The Agent revision the decision was made on
(`applications archive-status`).
--idempotency-key IDEMPOTENCY_KEY
Defaults to one derived from the revision.
--no-wait Return once requested.
--poll-seconds POLL_SECONDS
--wait-seconds WAIT_SECONDS
```
## cayu cloud applications archive-status
```console
usage: cayu cloud applications archive-status [-h] application
Show an Agent's lifecycle, revision and archive progress.
positional arguments:
application
options:
-h, --help show this help message and exit
```
## cayu cloud context
```console
usage: cayu cloud context [-h] {use,show,clear} ...
Select or inspect the private Cayu Cloud connection context.
positional arguments:
{use,show,clear}
options:
-h, --help show this help message and exit
```
## cayu cloud context use
```console
usage: cayu cloud context use [-h] context_path
Select a private Cayu Cloud context file for later commands.
positional arguments:
context_path
options:
-h, --help show this help message and exit
```
## cayu cloud context show
```console
usage: cayu cloud context show [-h]
Show the selected context without exposing its API key.
options:
-h, --help show this help message and exit
```
## cayu cloud context clear
```console
usage: cayu cloud context clear [-h]
Forget the selected Cayu Cloud context on this machine.
options:
-h, --help show this help message and exit
```
## cayu cloud doctor
```console
usage: cayu cloud doctor [-h]
Verify authentication and API access to the selected Cayu Cloud.
options:
-h, --help show this help message and exit
```
## cayu cloud init
```console
usage: cayu cloud init [-h] [--force] [path]
Generate cayu-cloud.toml from a Python Agent project. For a `cayu serve` web
process, also add the cayu server extra and the environment operator auth
target to pyproject.toml when they are missing; an existing auth target is
never replaced.
positional arguments:
path
options:
-h, --help show this help message and exit
--force Replace an existing cayu-cloud.toml.
```
## cayu cloud deploy
```console
usage: cayu cloud deploy [-h] [--manifest MANIFEST] [--revision REVISION]
[--application APPLICATION] [--no-wait]
[--no-promote] [--retry-failed | --no-retry-failed]
[--skip-deploy-check] [--poll-seconds POLL_SECONDS]
[--wait-seconds WAIT_SECONDS]
[source]
Publish and activate an immutable Agent release from a local directory or
repository.
positional arguments:
source
options:
-h, --help show this help message and exit
--manifest MANIFEST
--revision REVISION
--application APPLICATION
Create or update this application slug; defaults to
the application declared in cayu-cloud.toml.
--no-wait
--no-promote
--retry-failed, --no-retry-failed
Retry a replayed Cloud-side terminal failure with a
fresh submission key (default: enabled).
--skip-deploy-check Upload even when the local `cayu check --deploy` finds
problems that stop a public service starting on Cayu
Cloud.
--poll-seconds POLL_SECONDS
--wait-seconds WAIT_SECONDS
```
## cayu cloud deployment
```console
usage: cayu cloud deployment [-h]
{logs,status,timeline,wait,promote,retry} ...
Inspect, wait for, or promote one immutable Agent release.
positional arguments:
{logs,status,timeline,wait,promote,retry}
options:
-h, --help show this help message and exit
```
## cayu cloud deployment logs
```console
usage: cayu cloud deployment logs [-h] --application APPLICATION
[--diagnostic-offset DIAGNOSTIC_OFFSET]
[--diagnostic-limit DIAGNOSTIC_LIMIT]
deployment_id
Read structured publication logs for one release.
positional arguments:
deployment_id
options:
-h, --help show this help message and exit
--application APPLICATION
--diagnostic-offset DIAGNOSTIC_OFFSET
--diagnostic-limit DIAGNOSTIC_LIMIT
```
## cayu cloud deployment status
```console
usage: cayu cloud deployment status [-h] --application APPLICATION
deployment_id
Show the current publication status of one release.
positional arguments:
deployment_id
options:
-h, --help show this help message and exit
--application APPLICATION
```
## cayu cloud deployment timeline
```console
usage: cayu cloud deployment timeline [-h] --application APPLICATION
deployment_id
Show the publication milestones for one release.
positional arguments:
deployment_id
options:
-h, --help show this help message and exit
--application APPLICATION
```
## cayu cloud deployment wait
```console
usage: cayu cloud deployment wait [-h] --application APPLICATION
[--poll-seconds POLL_SECONDS]
[--wait-seconds WAIT_SECONDS]
deployment_id
Wait until one release is promotable or terminal.
positional arguments:
deployment_id
options:
-h, --help show this help message and exit
--application APPLICATION
--poll-seconds POLL_SECONDS
--wait-seconds WAIT_SECONDS
```
## cayu cloud deployment promote
```console
usage: cayu cloud deployment promote [-h] --application APPLICATION
deployment_id
Select a smoke-tested release for its Agent application.
positional arguments:
deployment_id
options:
-h, --help show this help message and exit
--application APPLICATION
```
## cayu cloud deployment retry
```console
usage: cayu cloud deployment retry [-h] --application APPLICATION
[--idempotency-key IDEMPOTENCY_KEY]
deployment_id
Create a new immutable release attempt from retained source.
positional arguments:
deployment_id
options:
-h, --help show this help message and exit
--application APPLICATION
--idempotency-key IDEMPOTENCY_KEY
```
## cayu cloud rollback
```console
usage: cayu cloud rollback [-h] --application APPLICATION deployment_id
Select an earlier immutable release and recreate the Agent service.
positional arguments:
deployment_id
options:
-h, --help show this help message and exit
--application APPLICATION
```
## cayu cloud runtimes
```console
usage: cayu cloud runtimes [-h] {list,status} ...
Inspect retained immutable runtime artifacts for an Agent.
positional arguments:
{list,status}
options:
-h, --help show this help message and exit
```
## cayu cloud runtimes list
```console
usage: cayu cloud runtimes list [-h] --application APPLICATION
List retained runtime artifacts for an Agent application.
options:
-h, --help show this help message and exit
--application APPLICATION
```
## cayu cloud runtimes status
```console
usage: cayu cloud runtimes status [-h] --application APPLICATION artifact_id
Show one retained runtime artifact and its lifecycle state.
positional arguments:
artifact_id
options:
-h, --help show this help message and exit
--application APPLICATION
```
## cayu cloud service
```console
usage: cayu cloud service [-h]
{credentials,destroy,logs,restart,sleep,status,wake} ...
Operate the long-running web, worker, and schedule infrastructure.
positional arguments:
{credentials,destroy,logs,restart,sleep,status,wake}
options:
-h, --help show this help message and exit
```
## cayu cloud service credentials
```console
usage: cayu cloud service credentials [-h] --application APPLICATION
Show the Agent's own /cayu/ operator username and password. The output is a
credential: keep it out of logs and shared terminals.
options:
-h, --help show this help message and exit
--application APPLICATION
```
## cayu cloud service destroy
```console
usage: cayu cloud service destroy [-h] --application APPLICATION
[--poll-seconds POLL_SECONDS]
[--wait-seconds WAIT_SECONDS]
Remove the Agent services and schedules but retain its releases.
options:
-h, --help show this help message and exit
--application APPLICATION
--poll-seconds POLL_SECONDS
--wait-seconds WAIT_SECONDS
```
## cayu cloud service logs
```console
usage: cayu cloud service logs [-h] --application APPLICATION
Read recent infrastructure logs from the Agent service.
options:
-h, --help show this help message and exit
--application APPLICATION
```
## cayu cloud service restart
```console
usage: cayu cloud service restart [-h] --application APPLICATION
Restart the Agent service on its selected immutable release.
options:
-h, --help show this help message and exit
--application APPLICATION
```
## cayu cloud service sleep
```console
usage: cayu cloud service sleep [-h] --application APPLICATION
Scale only the Agent web service to zero.
options:
-h, --help show this help message and exit
--application APPLICATION
```
## cayu cloud service status
```console
usage: cayu cloud service status [-h] --application APPLICATION
Show the Agent web, worker, schedule, and endpoint state.
options:
-h, --help show this help message and exit
--application APPLICATION
```
## cayu cloud service wake
```console
usage: cayu cloud service wake [-h] --application APPLICATION
Start only the Agent web service.
options:
-h, --help show this help message and exit
--application APPLICATION
```
## cayu cloud env
```console
usage: cayu cloud env [-h] {list,set,unset} ...
Manage Agent-owned environment variables and write-only secrets.
positional arguments:
{list,set,unset}
options:
-h, --help show this help message and exit
```
## cayu cloud env list
```console
usage: cayu cloud env list [-h] --application APPLICATION
List environment variables without revealing secret values.
options:
-h, --help show this help message and exit
--application APPLICATION
```
## cayu cloud env set
```console
usage: cayu cloud env set [-h] --application APPLICATION [--secret]
[--value-file VALUE_FILE]
assignment
Set NAME=value, or use NAME --secret --value-file PATH.
positional arguments:
assignment
options:
-h, --help show this help message and exit
--application APPLICATION
--secret
--value-file VALUE_FILE
Read a secret from PATH; use - for standard input.
```
## cayu cloud env unset
```console
usage: cayu cloud env unset [-h] --application APPLICATION name
Remove one Agent environment variable.
positional arguments:
name
options:
-h, --help show this help message and exit
--application APPLICATION
```
## cayu cloud evidence
```console
usage: cayu cloud evidence [-h] {list,show,verify} ...
Inspect local content-free records produced by Cloud commands.
positional arguments:
{list,show,verify}
options:
-h, --help show this help message and exit
```
## cayu cloud evidence list
```console
usage: cayu cloud evidence list [-h]
List local Cayu Cloud command evidence records.
options:
-h, --help show this help message and exit
```
## cayu cloud evidence show
```console
usage: cayu cloud evidence show [-h] evidence_id
Show one local content-free evidence record.
positional arguments:
evidence_id
options:
-h, --help show this help message and exit
```
## cayu cloud evidence verify
```console
usage: cayu cloud evidence verify [-h]
Verify the integrity of every local evidence record.
options:
-h, --help show this help message and exit
```
## Related guides
[Getting started](https://cayu.dev/docs/get-started/quickstart/) · [Operations](https://cayu.dev/docs/operate/) · [Configuration](https://cayu.dev/docs/reference/configuration/)
# cayu console
Generated from the released command parser. These commands show help only; operations run when you invoke the command without `--help`.
```console
usage: cayu console [-h] [target]
Open an IPython console with a live, writable Cayu application. Use `cayu
inspect` first for read-only structural inspection.
positional arguments:
target Override project discovery with a module:factory target.
options:
-h, --help show this help message and exit
```
## Related guides
[Getting started](https://cayu.dev/docs/get-started/quickstart/) · [Operations](https://cayu.dev/docs/operate/) · [Configuration](https://cayu.dev/docs/reference/configuration/)
# cayu dashboard
Generated from the released command parser. These commands show help only; operations run when you invoke the command without `--help`.
```console
usage: cayu dashboard [-h] {eject} ...
Manage the bundled Cayu control-plane source. Use `cayu dashboard eject
DESTINATION` to create an editable project.
positional arguments:
{eject}
eject Extract the version-matched dashboard source.
options:
-h, --help show this help message and exit
```
## cayu dashboard eject
```console
usage: cayu dashboard eject [-h] DESTINATION
Extract the version-matched dashboard source into an empty destination, then
install its Node dependencies and customize it locally.
positional arguments:
DESTINATION
options:
-h, --help show this help message and exit
```
## Related guides
[Getting started](https://cayu.dev/docs/get-started/quickstart/) · [Operations](https://cayu.dev/docs/operate/) · [Configuration](https://cayu.dev/docs/reference/configuration/)
# cayu diagnostics
Generated from the released command parser. These commands show help only; operations run when you invoke the command without `--help`.
```console
usage: cayu diagnostics [-h] {requests} ...
Read live diagnostics from a running Cayu server.
positional arguments:
{requests}
requests Summarize recent request rate, latency, and CPU by route.
options:
-h, --help show this help message and exit
```
## cayu diagnostics requests
```console
usage: cayu diagnostics requests [-h] [--server-url SERVER_URL]
[--since DURATION] [--vcpu VCPU]
[--budget-idle-cpu FRACTION] [--top TOP]
[--authorization-env NAME]
[--format {json,table} | --json | --table]
[--output FILE]
Summarize the server's in-memory request-timing buffer: requests per minute,
wall time, and CPU seconds per minute by route template, the top routes by
CPU, and the estimated share of --vcpu CPUs. With --budget-idle-cpu the
command exits 1 when that share exceeds the budget; it exits 2 when the
summary cannot be read. Authorization is read from CAYU_API_AUTHORIZATION by
default and is never printed.
options:
-h, --help show this help message and exit
--server-url SERVER_URL
Cayu server root URL, including any mount prefix but
excluding /api (default: http://127.0.0.1:8000; use
http://127.0.0.1:8000/cayu for an app that calls
mount_cayu at /cayu).
--since DURATION Window to summarize, such as 90s, 5m, or 1h (default:
5m).
--vcpu VCPU CPUs available to the web process, used for the share
(default: 1).
--budget-idle-cpu FRACTION
Exit 1 when request CPU exceeds this fraction of
--vcpu, for example 0.01 for 1%.
--top TOP Number of routes to show in table output (default:
10).
--authorization-env NAME
Environment variable containing the complete
Authorization header value.
--format {json,table}
Output format (default: table).
--json Emit JSON (shorthand for `--format json`).
--table Emit human-readable output (shorthand for `--format
table`).
--output, -o FILE Write output to FILE instead of stdout.
```
## Related guides
[Getting started](https://cayu.dev/docs/get-started/quickstart/) · [Operations](https://cayu.dev/docs/operate/) · [Configuration](https://cayu.dev/docs/reference/configuration/)
# cayu doctor
Generated from the released command parser. These commands show help only; operations run when you invoke the command without `--help`.
```console
usage: cayu doctor [-h] --bundle OUTPUT [--session SESSION_ID]
[--requests-from SERVER_URL] [--requests-since DURATION]
[--authorization-env NAME] [--json]
[target]
Boot the selected Cayu project without running agent work and atomically write
a bounded, redacted diagnostic support bundle.
positional arguments:
target Override project discovery with a module:factory
target.
options:
-h, --help show this help message and exit
--bundle OUTPUT Output ZIP path. The file is written atomically with
restrictive permissions.
--session SESSION_ID Include a bounded envelope-only durable event tail for
this session (repeatable; no history is read by
default).
--requests-from SERVER_URL
Also read the request-cost summary from this running
Cayu server (root URL including any mount prefix,
excluding /api). The bundle keeps the summary but not
the URL or credential.
--requests-since DURATION
Request-cost window for --requests-from, such as 5m or
1h (default: 5m).
--authorization-env NAME
Environment variable containing the complete
Authorization header for --requests-from.
--json Print the stable command outcome as JSON.
```
## Related guides
[Getting started](https://cayu.dev/docs/get-started/quickstart/) · [Operations](https://cayu.dev/docs/operate/) · [Configuration](https://cayu.dev/docs/reference/configuration/)
# cayu eval
Generated from the released command parser. These commands show help only; operations run when you invoke the command without `--help`.
```console
usage: cayu eval [-h]
{run,resume,cancel,rescore,retry,package,status,failures,export,report,memory-report,compare,validate,inspect,merge} ...
Run and report Cayu runtime-native evals. Start with `cayu eval run` for the
project-configured hermetic proof.
positional arguments:
{run,resume,cancel,rescore,retry,package,status,failures,export,report,memory-report,compare,validate,inspect,merge}
run Run a configured or explicit eval plan.
resume Resume a native benchmark campaign using its saved
admission.
cancel Request native cancellation for a benchmark campaign.
rescore Evaluate a new static scorer using saved output,
without candidate or judge calls.
retry Execute a bounded, explicitly selected successor
trial.
package Create, validate, and inspect portable benchmark
packages.
status Inspect process-run progress.
failures Inspect recorded case failures and session
diagnostics.
export Export validated process receipts and results to a new
private ZIP.
report Render a JSON or HTML report from eval results.
memory-report Build a paired repeated-trial memory experiment
report.
compare Compare baseline and current eval results.
validate Validate a portable eval corpus.
inspect Inspect a validated portable eval corpus.
merge Atomically merge portable eval corpora.
options:
-h, --help show this help message and exit
```
## cayu eval run
```console
usage: cayu eval run [-h] [--format {json} | --json] [--output FILE]
[--html-output FILE] [--corpus FILE] [--suite SUITE_ID]
[--max-concurrency COUNT] [--stagger-seconds SECONDS]
[--processes COUNT] [--process-directory DIRECTORY]
[--case-timeout-seconds SECONDS] [--package PATH]
[--case CASE_ID] [--campaign-directory NEW_DIRECTORY]
[--admit-only] [--trials TRIALS]
[--minimum-passed-trials MINIMUM_PASSED_TRIALS]
[--max-steps MAX_STEPS]
[--max-total-tokens MAX_TOTAL_TOKENS]
[--max-tool-calls MAX_TOOL_CALLS]
[--max-estimated-cost MAX_ESTIMATED_COST]
[--currency CURRENCY] [--provider PROVIDER]
[--model MODEL] [--environment ENVIRONMENT]
[--max-execution-attempts MAX_EXECUTION_ATTEMPTS]
[--max-retry-attempts MAX_RETRY_ATTEMPTS]
[--retry-category RETRY_CATEGORIES]
[--retry-backoff-seconds RETRY_BACKOFF_SECONDS]
[target]
Run a configured or explicit eval plan and emit a stable JSON result. Use
`--output FILE` to save it.
positional arguments:
target Python target that returns a direct-suite or corpus-
target EvalPlan. Defaults to [tool.cayu].eval_target.
options:
-h, --help show this help message and exit
--format {json} Output format (default: json).
--json Emit JSON (shorthand for `--format json`).
--output, -o FILE Write output to FILE instead of stdout.
--html-output FILE Also write an HTML report to FILE.
--corpus FILE Run a portable corpus through the target's trusted
CorpusTarget.
--suite SUITE_ID Corpus suite to run (optional only when the corpus has
one suite).
--max-concurrency COUNT
Maximum concurrently executing cases (default: 1).
--stagger-seconds SECONDS
Minimum interval between case admissions (default: 0);
2 minutes = 120 seconds.
--processes COUNT Fresh Python worker processes for native direct suites
(default: 1).
--process-directory DIRECTORY
New private directory for process admission, logs, and
worker results.
--case-timeout-seconds SECONDS
Limit each direct-suite case to SECONDS; corpus
timeouts are declared in JSON.
--package PATH Run a portable benchmark package through native
durable Evals.
--case CASE_ID Explicit packaged-benchmark case selection; repeat to
select multiple cases.
--campaign-directory NEW_DIRECTORY
New private directory for native campaign admission,
catalog, and inspection.
--admit-only Persist a packaged campaign without dispatching it;
use eval resume to start it.
--trials TRIALS Independent trials per package case.
--minimum-passed-trials MINIMUM_PASSED_TRIALS
Passed trials needed by the package's native trial
policy.
--max-steps MAX_STEPS
Narrow the packaged target's per-trial model-step
limit.
--max-total-tokens MAX_TOTAL_TOKENS
Per-trial observed-token stop threshold; in-flight
usage can overshoot.
--max-tool-calls MAX_TOOL_CALLS
Narrow the packaged target's per-trial tool-call
allowance.
--max-estimated-cost MAX_ESTIMATED_COST
Per-trial priced budget; requires the target's trusted
PriceBook.
--currency CURRENCY Currency for --max-estimated-cost (default: USD).
--provider PROVIDER Registered provider for the packaged run; requires
--model.
--model MODEL Model on the explicitly selected --provider.
--environment ENVIRONMENT
Existing target environment for the packaged run.
--max-execution-attempts MAX_EXECUTION_ATTEMPTS
Explicitly authorize up to this many full execution
attempts after interruption (1-10).
--max-retry-attempts MAX_RETRY_ATTEMPTS
Reserve up to 3 selective successor trials per
original slot; default 0.
--retry-category RETRY_CATEGORIES
Eligible typed failure category; repeat. Wrong answers
are excluded by default.
--retry-backoff-seconds RETRY_BACKOFF_SECONDS
Minimum elapsed time after the preceding attempt,
0-3600 seconds.
```
## cayu eval resume
```console
usage: cayu eval resume [-h] directory [target]
Resume saved native work under its admitted recovery policy. Inspect it with
`cayu eval status DIRECTORY`.
positional arguments:
directory
target
options:
-h, --help show this help message and exit
```
## cayu eval cancel
```console
usage: cayu eval cancel [-h] directory
Request cancellation of admitted campaign runs. Inspect settlement with `cayu
eval status DIRECTORY`.
positional arguments:
directory
options:
-h, --help show this help message and exit
```
## cayu eval rescore
```console
usage: cayu eval rescore [-h] --package SCORER_PACKAGE --output OUTPUT
directory
Apply a new static scorer package to retained output. Review the new receipt
at --output; original results remain available through eval status.
positional arguments:
directory
options:
-h, --help show this help message and exit
--package SCORER_PACKAGE
--output OUTPUT New scorer receipt outside the original campaign.
```
## cayu eval retry
```console
usage: cayu eval retry [-h] --case CASE_ID --trial TRIAL [--attempt ATTEMPT]
[--allow-reexecution]
directory [target]
Retry one eligible original trial within its reserved allowance. Inspect
original and successor evidence with `cayu eval status DIRECTORY`.
positional arguments:
directory
target
options:
-h, --help show this help message and exit
--case CASE_ID
--trial TRIAL
--attempt ATTEMPT
--allow-reexecution Explicit decision to replay when the target has no
application reset contract.
```
## cayu eval package
```console
usage: cayu eval package [-h] {init,validate,inspect} ...
Manage portable native benchmark definitions. Start with `cayu eval package
init DIRECTORY`, then validate the package.
positional arguments:
{init,validate,inspect}
init Create the installed synthetic benchmark package in a
new directory.
validate Validate a package without importing any execution
target.
inspect Inspect a package without importing any execution
target.
options:
-h, --help show this help message and exit
```
## cayu eval package init
```console
usage: cayu eval package init [-h] [--failure-modes] [--interruption-case]
[--scorer-version SCORER_VERSION]
directory
Create a synthetic benchmark package without external calls. Next use `cayu
eval package validate DIRECTORY` or launch with `cayu eval run --package
DIRECTORY`.
positional arguments:
directory
options:
-h, --help show this help message and exit
--failure-modes Include synthetic provider and scoring failures.
--interruption-case Include a synthetic pending call for explicit process-
loss qualification.
--scorer-version SCORER_VERSION
Explicit synthetic scorer version for saved-output
qualification.
```
## cayu eval package validate
```console
usage: cayu eval package validate [-h] [--case CASE_IDS] path
Validate exact package material and cohort identity. Launch a validated
package with `cayu eval run TARGET --package PATH`.
positional arguments:
path
options:
-h, --help show this help message and exit
--case CASE_IDS
```
## cayu eval package inspect
```console
usage: cayu eval package inspect [-h] [--case CASE_IDS] path
Inspect exact package material and cohort identity. Launch a validated package
with `cayu eval run TARGET --package PATH`.
positional arguments:
path
options:
-h, --help show this help message and exit
--case CASE_IDS
```
## cayu eval status
```console
usage: cayu eval status [-h] [--sessions | --no-sessions]
[--session-evidence FILE]
[--max-sessions MAX_SESSIONS]
[--max-diagnostics MAX_DIAGNOSTICS] [--case CASE_ID]
[--format {json,table} | --json | --table]
[--output FILE]
PROCESS_DIRECTORY
Read native process receipts without importing the target or starting work.
Session observations use explicit read-only store links. Admission and recent
events do not prove worker liveness.
positional arguments:
PROCESS_DIRECTORY
options:
-h, --help show this help message and exit
--sessions, --no-sessions
Include bounded live session observations (default:
enabled for failures).
--session-evidence FILE
Explicit launch- and case-bound session locators for
an older run.
--max-sessions MAX_SESSIONS
--max-diagnostics MAX_DIAGNOSTICS
--case CASE_ID
--format {json,table}
Output format (default: table).
--json Emit JSON (shorthand for `--format json`).
--table Emit human-readable output (shorthand for `--format
table`).
--output, -o FILE Write output to FILE instead of stdout.
```
## cayu eval failures
```console
usage: cayu eval failures [-h] [--sessions | --no-sessions]
[--session-evidence FILE]
[--max-sessions MAX_SESSIONS]
[--max-diagnostics MAX_DIAGNOSTICS] [--case CASE_ID]
[--format {json,table} | --json | --table]
[--output FILE]
PROCESS_DIRECTORY
Read native process receipts without importing the target or starting work.
Session observations use explicit read-only store links. Admission and recent
events do not prove worker liveness.
positional arguments:
PROCESS_DIRECTORY
options:
-h, --help show this help message and exit
--sessions, --no-sessions
Include bounded live session observations (default:
enabled for failures).
--session-evidence FILE
Explicit launch- and case-bound session locators for
an older run.
--max-sessions MAX_SESSIONS
--max-diagnostics MAX_DIAGNOSTICS
--case CASE_ID
--format {json,table}
Output format (default: table).
--json Emit JSON (shorthand for `--format json`).
--table Emit human-readable output (shorthand for `--format
table`).
--output, -o FILE Write output to FILE instead of stdout.
```
## cayu eval export
```console
usage: cayu eval export [-h] --output NEW_ZIP PROCESS_DIRECTORY
Export exact inspected receipt and result bytes with hashes. This does not
snapshot session stores or include application files and logs.
positional arguments:
PROCESS_DIRECTORY
options:
-h, --help show this help message and exit
--output, -o NEW_ZIP
```
## cayu eval report
```console
usage: cayu eval report [-h] [--format {html,json} | --json | --html]
[--output FILE]
RESULTS_JSON
Render saved eval results as HTML by default or JSON explicitly. Use `--output
FILE` to save the report.
positional arguments:
RESULTS_JSON Eval JSON results file.
options:
-h, --help show this help message and exit
--format {html,json} Output format (default: html).
--json Emit JSON (shorthand for `--format json`).
--html Emit HTML (shorthand for `--format html`).
--output, -o FILE Write output to FILE instead of stdout.
```
## cayu eval memory-report
```console
usage: cayu eval memory-report [-h] [--format {html,json} | --json | --html]
[--output FILE]
REQUEST_JSON
Validate an exact memory experiment request and build its deterministic fixed-
candidate report without launching additional work.
positional arguments:
REQUEST_JSON Memory experiment report request JSON file.
options:
-h, --help show this help message and exit
--format {html,json} Output format (default: json).
--json Emit JSON (shorthand for `--format json`).
--html Emit HTML (shorthand for `--format html`).
--output, -o FILE Write output to FILE instead of stdout.
```
## cayu eval compare
```console
usage: cayu eval compare [-h] [--format {html,json} | --json | --html]
[--output FILE] [--score-tolerance DELTA]
BASELINE_JSON CURRENT_JSON
Compare baseline and current eval results. JSON is the default; a nonzero exit
reports regressions.
positional arguments:
BASELINE_JSON
CURRENT_JSON
options:
-h, --help show this help message and exit
--format {html,json} Output format (default: json).
--json Emit JSON (shorthand for `--format json`).
--html Emit HTML (shorthand for `--format html`).
--output, -o FILE Write output to FILE instead of stdout.
--score-tolerance DELTA
Allowed score drop before a regression is flagged
(default: 0.0).
```
## cayu eval validate
```console
usage: cayu eval validate [-h] [--format {json,table} | --json | --table]
[--output FILE]
CORPUS_JSON
Validate one bounded portable eval corpus without loading an application
target.
positional arguments:
CORPUS_JSON
options:
-h, --help show this help message and exit
--format {json,table}
Output format (default: table).
--json Emit JSON (shorthand for `--format json`).
--table Emit human-readable output (shorthand for `--format
table`).
--output, -o FILE Write output to FILE instead of stdout.
```
## cayu eval inspect
```console
usage: cayu eval inspect [-h] [--format {json,table} | --json | --table]
[--output FILE]
CORPUS_JSON
Show the target, suites, cases, assertions, and expanded result count for one
validated corpus.
positional arguments:
CORPUS_JSON
options:
-h, --help show this help message and exit
--format {json,table}
Output format (default: table).
--json Emit JSON (shorthand for `--format json`).
--table Emit human-readable output (shorthand for `--format
table`).
--output, -o FILE Write output to FILE instead of stdout.
```
## cayu eval merge
```console
usage: cayu eval merge [-h] [--replace-conflicts] [--format {json,table} |
--json | --table] [--output FILE]
DESTINATION_JSON CORPUS_JSON [CORPUS_JSON ...]
Validate and merge compatible corpus files, then atomically replace the
destination.
positional arguments:
DESTINATION_JSON
CORPUS_JSON
options:
-h, --help show this help message and exit
--replace-conflicts Replace same-ID content conflicts in command-line
order.
--format {json,table}
Output format (default: table).
--json Emit JSON (shorthand for `--format json`).
--table Emit human-readable output (shorthand for `--format
table`).
--output, -o FILE Write output to FILE instead of stdout.
```
## Related guides
[Getting started](https://cayu.dev/docs/get-started/quickstart/) · [Operations](https://cayu.dev/docs/operate/) · [Configuration](https://cayu.dev/docs/reference/configuration/)
# cayu generate
Generated from the released command parser. These commands show help only; operations run when you invoke the command without `--help`.
```console
usage: cayu generate [-h] {slice,tool,service-context} ...
Plan or add reviewable generated Cayu application slices. Use `--dry-run` to
inspect the plan without writing files.
positional arguments:
{slice,tool,service-context}
slice Add one agent, typed tool, runtime test, and
trajectory eval.
tool Attach the first generated tool tracer bullet to the
starter agent.
service-context Migrate a generated maintained service to Cayu project
context assembly.
options:
-h, --help show this help message and exit
```
## cayu generate slice
```console
usage: cayu generate slice [-h] --tool TOOL
--effect {none,idempotent,external} [--dry-run]
[--format {json,table} | --json | --table]
[--output FILE]
name
Add one agent, typed tool, runtime test, and trajectory eval. Finish the
generated tracer bullet, then run its verification commands.
positional arguments:
name snake_case agent/slice name.
options:
-h, --help show this help message and exit
--tool TOOL snake_case tool name.
--effect {none,idempotent,external}
Declared ToolEffect. See `cayu guide tool-effects` for
the decision table.
--dry-run Plan without writes.
--format {json,table}
Output format (default: json).
--json Emit JSON (shorthand for `--format json`).
--table Emit human-readable output (shorthand for `--format
table`).
--output, -o FILE Write output to FILE instead of stdout.
```
## cayu generate tool
```console
usage: cayu generate tool [-h] --agent AGENT
--effect {none,idempotent,external} [--dry-run]
[--format {json,table} | --json | --table]
[--output FILE]
name
Attach the first generated tool tracer bullet to the starter agent. Finish its
domain behavior, then run `cayu check`.
positional arguments:
name snake_case tool name.
options:
-h, --help show this help message and exit
--agent AGENT Existing scaffolded starter agent name.
--effect {none,idempotent,external}
Declared ToolEffect. See `cayu guide tool-effects` for
the decision table.
--dry-run Plan without writes.
--format {json,table}
Output format (default: json).
--json Emit JSON (shorthand for `--format json`).
--table Emit human-readable output (shorthand for `--format
table`).
--output, -o FILE Write output to FILE instead of stdout.
```
## cayu generate service-context
```console
usage: cayu generate service-context [-h] [--dry-run] [--format {json,table} |
--json | --table] [--output FILE]
Safely add the framework-owned project_context pass-through required for zero-
code Control Plane features. Customized factories receive manual guidance.
options:
-h, --help show this help message and exit
--dry-run Plan without writes.
--format {json,table}
Output format (default: json).
--json Emit JSON (shorthand for `--format json`).
--table Emit human-readable output (shorthand for `--format
table`).
--output, -o FILE Write output to FILE instead of stdout.
```
## Related guides
[Getting started](https://cayu.dev/docs/get-started/quickstart/) · [Operations](https://cayu.dev/docs/operate/) · [Configuration](https://cayu.dev/docs/reference/configuration/)
# cayu guide
Generated from the released command parser. These commands show help only; operations run when you invoke the command without `--help`.
```console
usage: cayu guide [-h] [--json] [TOPIC[#SECTION]]
Read version-matched guidance shipped in the installed Cayu package. Use TOPIC#SECTION for a specific emitted documentation anchor.
positional arguments:
TOPIC[#SECTION]
options:
-h, --help show this help message and exit
--json Emit stable topic, section, package, relationship, and
verification metadata.
Topics:
order-support Restartable support agent with external approval receipts.
anatomy Application lifecycle and process roles.
app-ui Browser UIs over sessions: client.js, polling rules, and the idle budget.
applications Generated application convention, placement, planning, and drift checks.
authoring Concept map and the supported authoring loop.
diagnostics Stable `cayu check` findings and fixes, and the request-cost check.
durable-operations Observe, propose, authorize, act once, verify, and recover.
durable-service-tools Reconstruct service-backed tools with stable identities and scoped knowledge.
evals-ai-quality Rubrics, references, judge authority, calibration, and interpretation.
evals-first First Control Plane suite, run, baseline, and comparison.
evals-production Production sessions, scenarios, tools, process behavior, and memory.
human-attention Durable notifications over canonical pending actions.
providers Primary integrations and compatible Chat Completions endpoints.
references Offline references for optional capabilities.
structured-output Credential-free structured-output runtime proof.
thinking Reasoning effort vocabulary and provider compatibility.
tool-effects Replay and mutation effect decisions.
verified-work Contract-bound tasks: proposals, independent verifiers, and bounded attempts.
```
## Related guides
[Getting started](https://cayu.dev/docs/get-started/quickstart/) · [Operations](https://cayu.dev/docs/operate/) · [Configuration](https://cayu.dev/docs/reference/configuration/)
# CLI reference
Complete command and flag lookup. Run `cayu COMMAND --help` for the version installed in your project.
```console
usage: cayu [-h] [--version]
{version,agent,auth,check,cloud,console,dashboard,diagnostics,doctor,eval,generate,guide,inspect,lambda-microvm,new,recovery,serve,session,storage,worker} ...
Developer/admin CLI for Cayu agent projects.
positional arguments:
{version,agent,auth,check,cloud,console,dashboard,diagnostics,doctor,eval,generate,guide,inspect,lambda-microvm,new,recovery,serve,session,storage,worker}
version Print the Cayu version.
agent Manage portable Cayu agents.
auth Manage local provider sign-ins.
check Validate a booted Cayu project with actionable
diagnostics.
cloud Manage Cayu Cloud.
console Open an IPython console with a booted Cayu
application.
dashboard Manage the bundled Cayu control-plane source.
diagnostics Read live diagnostics from a running Cayu server.
doctor Write a bounded, redacted diagnostic support bundle.
eval Run and report Cayu runtime-native evals.
generate Plan or add reviewable generated Cayu application
slices.
guide Print package-shipped Cayu application guidance.
inspect Describe a booted Cayu project without running it.
lambda-microvm Manage AWS Lambda MicroVM support artifacts.
new Scaffold a new Cayu agent project.
recovery Plan and execute bounded recovery through the
registered application.
serve Serve a configured Cayu project's control plane.
session Inspect durable sessions and resolve unavailable
provider work.
storage Inspect, migrate, and export Cayu storage.
worker Run a configured Cayu worker entrypoint.
options:
-h, --help show this help message and exit
--version show program's version number and exit
```
## Commands
- [cayu version](https://cayu.dev/docs/reference/cli/version/)
- [cayu agent](https://cayu.dev/docs/reference/cli/agent/)
- [cayu auth](https://cayu.dev/docs/reference/cli/auth/)
- [cayu check](https://cayu.dev/docs/reference/cli/check/)
- [cayu cloud](https://cayu.dev/docs/reference/cli/cloud/)
- [cayu console](https://cayu.dev/docs/reference/cli/console/)
- [cayu dashboard](https://cayu.dev/docs/reference/cli/dashboard/)
- [cayu diagnostics](https://cayu.dev/docs/reference/cli/diagnostics/)
- [cayu doctor](https://cayu.dev/docs/reference/cli/doctor/)
- [cayu eval](https://cayu.dev/docs/reference/cli/eval/)
- [cayu generate](https://cayu.dev/docs/reference/cli/generate/)
- [cayu guide](https://cayu.dev/docs/reference/cli/guide/)
- [cayu inspect](https://cayu.dev/docs/reference/cli/inspect/)
- [cayu lambda-microvm](https://cayu.dev/docs/reference/cli/lambda-microvm/)
- [cayu new](https://cayu.dev/docs/reference/cli/new/)
- [cayu recovery](https://cayu.dev/docs/reference/cli/recovery/)
- [cayu serve](https://cayu.dev/docs/reference/cli/serve/)
- [cayu session](https://cayu.dev/docs/reference/cli/session/)
- [cayu storage](https://cayu.dev/docs/reference/cli/storage/)
- [cayu worker](https://cayu.dev/docs/reference/cli/worker/)
## Automation and exit behavior
CLI exit semantics are command-specific. `cayu check --fail-on warning --json` fails when diagnostics reach the requested severity; use it before tests and evals. `--json` is available only on commands whose help declares it. An invalid argument is a parser error; a successful inspection does not prove live-provider behavior. Capture command exit status and the structured result together. Use the generated project test and eval commands for behavioral verification.
## Project and storage selection
Commands that boot an application use `[tool.cayu].factory` and construct a fresh process-local app. Storage-only commands use their explicit selectors, then `CAYU_DATABASE_URL`, project configuration and the existing conventional local store. Read [application anatomy](https://cayu.dev/docs/get-started/project-anatomy/) and [storage](https://cayu.dev/docs/operate/storage/) before automating across processes.
# cayu inspect
Generated from the released command parser. These commands show help only; operations run when you invoke the command without `--help`.
```console
usage: cayu inspect [-h] [--format {json,table} | --json | --table]
[--output FILE] [--agent AGENT | --tool TOOL |
--environment ENVIRONMENT]
[target]
Describe a booted Cayu project without running it. This proves the structural
manifest only; use `cayu check` for diagnostics.
positional arguments:
target Override project discovery with a module:factory
target.
options:
-h, --help show this help message and exit
--format {json,table}
Output format (default: json).
--json Emit JSON (shorthand for `--format json`).
--table Emit human-readable output (shorthand for `--format
table`).
--output, -o FILE Write output to FILE instead of stdout.
--agent AGENT Return one agent from the manifest.
--tool TOOL Return agents registering this tool.
--environment ENVIRONMENT
Return one environment from the manifest.
```
## Related guides
[Getting started](https://cayu.dev/docs/get-started/quickstart/) · [Operations](https://cayu.dev/docs/operate/) · [Configuration](https://cayu.dev/docs/reference/configuration/)
# cayu lambda-microvm
Generated from the released command parser. These commands show help only; operations run when you invoke the command without `--help`.
```console
usage: cayu lambda-microvm [-h] {sidecar} ...
Manage AWS Lambda MicroVM support artifacts. Use the sidecar export command to
materialize a build context.
positional arguments:
{sidecar}
sidecar Manage the first-party Lambda MicroVM command sidecar.
options:
-h, --help show this help message and exit
```
## cayu lambda-microvm sidecar
```console
usage: cayu lambda-microvm sidecar [-h] {export} ...
Manage the first-party Lambda MicroVM command sidecar. Use `cayu lambda-
microvm sidecar export DESTINATION` next.
positional arguments:
{export}
export Export the versioned sidecar image build context.
options:
-h, --help show this help message and exit
```
## cayu lambda-microvm sidecar export
```console
usage: cayu lambda-microvm sidecar export [-h] [--replace] [--browser]
DESTINATION
Export the versioned sidecar image build context. Build the emitted context
with the container tooling documented inside it.
positional arguments:
DESTINATION
options:
-h, --help show this help message and exit
--replace Delete and replace all contents of an existing destination
directory.
--browser Export the browser image variant: the sidecar under
lambda_microvm_sidecar/, this release's browser worker under
browser-worker/, and a root Dockerfile that installs Chromium
and Playwright.
```
## Related guides
[Getting started](https://cayu.dev/docs/get-started/quickstart/) · [Operations](https://cayu.dev/docs/operate/) · [Configuration](https://cayu.dev/docs/reference/configuration/)
# cayu new
Generated from the released command parser. These commands show help only; operations run when you invoke the command without `--help`.
```console
usage: cayu new [-h] [--agent-name AGENT_NAME] [--dir DIR]
[--provider {neutral,openai,anthropic,openrouter,cayu-gateway,openai-subscription}]
[--preset {agent,service,coding}] [--execution {none,docker}]
[--with CAPABILITY] [--without CAPABILITY] [--interactive]
[--dry-run] [--json] [--list-presets] [--list-capabilities]
[--explain CAPABILITY] [--coding-toolchain {python}]
[--coding-command-authority {structured}]
[name]
Scaffold a new Cayu application project. Follow the printed `uv sync` and
credential-free verification commands next.
positional arguments:
name Project name (also the directory name); required
unless using discovery.
options:
-h, --help show this help message and exit
--agent-name AGENT_NAME
Registered first-agent name (default: project name).
--dir DIR Parent directory to create the project in (default:
current directory).
--provider {neutral,openai,anthropic,openrouter,cayu-gateway,openai-subscription}
Provider adapter (default: neutral). Omit for a
provider-neutral scaffold; CAYU_PROVIDER can select or
override it later.
--preset {agent,service,coding}
Coherent application shape: agent (default), service,
or coding.
--execution {none,docker}
Execution adapter (default: none; docker is admitted
only for coding).
--with CAPABILITY Enable a maintained optional capability; repeat or use
comma-separated names.
--without CAPABILITY Disable an optional preset capability while retaining
its ownership home.
--interactive Prompt for omitted choices when attached to a
terminal.
--dry-run Resolve and validate the exact generation plan without
writing files.
--json Emit stable structured discovery, plan, error, or
creation output.
--list-presets List package-shipped maintained presets without
creating a project.
--list-capabilities List selectable, preset-owned, and extension-only
capability seams.
--explain CAPABILITY Explain one package-shipped capability without
creating a project.
--coding-toolchain {python}
Explicit admitted profile for Docker coding execution.
The generated Python profile is the first built-in;
applications may register custom
DockerCodingToolchainProfile values in
environments/coding.py.
--coding-command-authority {structured}
Model-facing command authority for Docker coding. The
maintained surface accepts only application-owned
structured selectors.
```
## Related guides
[Getting started](https://cayu.dev/docs/get-started/quickstart/) · [Operations](https://cayu.dev/docs/operate/) · [Configuration](https://cayu.dev/docs/reference/configuration/)
# cayu recovery
Generated from the released command parser. These commands show help only; operations run when you invoke the command without `--help`.
```console
usage: cayu recovery [-h] {plan,execute} ...
Inspect recovery work through the canonical Cayu application factory, then
execute an exact saved plan. Planning is read-only; execution rejects stale
durable state and records per-session receipts.
positional arguments:
{plan,execute}
plan Create a bounded, secret-free recovery plan without mutating
storage.
execute Execute decisions bound to an exact saved recovery plan.
options:
-h, --help show this help message and exit
```
## cayu recovery plan
```console
usage: cayu recovery plan [-h] (--session SESSION_ID | --status STATUS)
[--inactive-for-seconds INACTIVE_FOR_SECONDS]
[--limit LIMIT]
[--inspection-limit INSPECTION_LIMIT]
[--cursor CURSOR] [--format {json} | --json]
[--output FILE]
[target]
Boot the registered application once and inspect exact session, registration,
claim, model, tool, task, and interruption-cascade state. Save the JSON output
and pass it unchanged to `cayu recovery execute`.
positional arguments:
target Override project discovery with a module:factory
target.
options:
-h, --help show this help message and exit
--session SESSION_ID Inspect one exact session; repeat for more sessions.
--status STATUS Inspect one lifecycle status; repeat for more
statuses.
--inactive-for-seconds INACTIVE_FOR_SECONDS
Include only sessions inactive for at least this many
seconds.
--limit LIMIT Maximum plan items (default: 100; max: 1000).
--inspection-limit INSPECTION_LIMIT
Maximum session rows inspected (default: 1000; max:
10000).
--cursor CURSOR Continue a status-based plan page.
--format {json} Output format (default: json).
--json Emit JSON (shorthand for `--format json`).
--output, -o FILE Write output to FILE instead of stdout.
```
## cayu recovery execute
```console
usage: cayu recovery execute [-h] [--target TARGET]
--execution-id EXECUTION_ID
[--decisions DECISIONS.json]
[--max-concurrency MAX_CONCURRENCY]
[--format {json} | --json] [--output FILE]
PLAN.json
Load an immutable plan, boot the same registered application once, reject
changed durable state, and emit replayable per-session receipts. No action
with an unknown external effect is selected automatically.
positional arguments:
PLAN.json
options:
-h, --help show this help message and exit
--target TARGET Override project discovery with a module:factory
target.
--execution-id EXECUTION_ID
Stable operator-supplied id; reuse it to replay an
ambiguous invocation.
--decisions DECISIONS.json
Optional JSON array of RecoveryDecision objects.
--max-concurrency MAX_CONCURRENCY
Maximum independent sessions executed concurrently
(default: 1; max: 32).
--format {json} Output format (default: json).
--json Emit JSON (shorthand for `--format json`).
--output, -o FILE Write output to FILE instead of stdout.
```
## Related guides
[Getting started](https://cayu.dev/docs/get-started/quickstart/) · [Operations](https://cayu.dev/docs/operate/) · [Configuration](https://cayu.dev/docs/reference/configuration/)
# cayu serve
Generated from the released command parser. These commands show help only; operations run when you invoke the command without `--help`.
```console
usage: cayu serve [-h] [--host HOST] [--port PORT] [--dev |
--auth MODULE:ATTRIBUTE]
Start one process-scoped Cayu control plane. Select --dev explicitly for
unauthenticated local development or configure authentication for deployment.
options:
-h, --help show this help message and exit
--host HOST Host interface passed to uvicorn (default: 127.0.0.1).
--port PORT TCP port passed to uvicorn (default: 8000).
--dev Explicitly allow an unauthenticated local-development
server.
--auth MODULE:ATTRIBUTE
Override [tool.cayu.serve].auth with a protected auth
dependency target.
```
## Related guides
[Getting started](https://cayu.dev/docs/get-started/quickstart/) · [Operations](https://cayu.dev/docs/operate/) · [Configuration](https://cayu.dev/docs/reference/configuration/)
# cayu session
Generated from the released command parser. These commands show help only; operations run when you invoke the command without `--help`.
```console
usage: cayu session [-h]
{list,show,interactions,usage,tools,events,transcript,resolve-provider-operation} ...
Inspect durable Cayu sessions without mutating storage, or send an explicit
provider-operation disposition to a running Cayu server. Start with `cayu
session list`; JSON is the default output.
positional arguments:
{list,show,interactions,usage,tools,events,transcript,resolve-provider-operation}
list List sessions by newest activity.
show Show a compact session overview.
interactions Page response-scoped interaction summaries.
usage Show per-model-call token usage.
tools Show paired durable tool calls.
events Page durable session events.
transcript Page bounded transcript metadata and previews.
resolve-provider-operation
Explicitly retry or fail unavailable provider work.
options:
-h, --help show this help message and exit
```
## cayu session list
```console
usage: cayu session list [-h] [--sqlite PATH | --postgres DSN]
[--status {pending,running,interrupting,completed,failed,interrupted}]
[--agent AGENT] [--environment ENVIRONMENT]
[--label KEY=VALUE] [--limit LIMIT]
[--offset OFFSET | --cursor CURSOR]
[--format {json,table,jsonl} | --json | --table |
--jsonl] [--output FILE]
List sessions by newest activity. Use a returned session id with `cayu session
show SESSION_ID`.
options:
-h, --help show this help message and exit
--sqlite PATH
--postgres DSN
--status {pending,running,interrupting,completed,failed,interrupted}
--agent AGENT Filter by exact agent name.
--environment ENVIRONMENT
Filter by exact environment name.
--label KEY=VALUE Filter by an exact label; repeat for multiple labels.
--limit LIMIT
--offset OFFSET
--cursor CURSOR
--format {json,table,jsonl}
Output format (default: json).
--json Emit JSON (shorthand for `--format json`).
--table Emit human-readable output (shorthand for `--format
table`).
--jsonl Emit JSON Lines (shorthand for `--format jsonl`).
--output, -o FILE Write output to FILE instead of stdout.
```
## cayu session show
```console
usage: cayu session show [-h] [--sqlite PATH | --postgres DSN]
[--format {json,table,jsonl} | --json | --table |
--jsonl] [--output FILE]
session_id
Show a compact session overview. Use usage, tools, events, or transcript for
bounded detail.
positional arguments:
session_id
options:
-h, --help show this help message and exit
--sqlite PATH
--postgres DSN
--format {json,table,jsonl}
Output format (default: json).
--json Emit JSON (shorthand for `--format json`).
--table Emit human-readable output (shorthand for `--format
table`).
--jsonl Emit JSON Lines (shorthand for `--format jsonl`).
--output, -o FILE Write output to FILE instead of stdout.
```
## cayu session interactions
```console
usage: cayu session interactions [-h] [--sqlite PATH | --postgres DSN]
[--before-sequence BEFORE_SEQUENCE]
[--limit LIMIT]
[--format {json,table,jsonl} | --json |
--table | --jsonl] [--output FILE]
session_id
Page durable interaction summaries newest first. Use an interaction id with
usage, tools, events, or transcript for response-scoped evidence.
positional arguments:
session_id
options:
-h, --help show this help message and exit
--sqlite PATH
--postgres DSN
--before-sequence BEFORE_SEQUENCE
--limit LIMIT
--format {json,table,jsonl}
Output format (default: json).
--json Emit JSON (shorthand for `--format json`).
--table Emit human-readable output (shorthand for `--format
table`).
--jsonl Emit JSON Lines (shorthand for `--format jsonl`).
--output, -o FILE Write output to FILE instead of stdout.
```
## cayu session usage
```console
usage: cayu session usage [-h] [--sqlite PATH | --postgres DSN]
[--offset OFFSET] [--limit LIMIT]
[--after-sequence AFTER_SEQUENCE]
[--before-sequence BEFORE_SEQUENCE]
[--interaction-id INTERACTION_ID]
[--format {json,table,jsonl} | --json | --table |
--jsonl] [--output FILE]
session_id
Show bounded per-model-call token usage and pricing state. Use `--limit` and
`--offset` to page results.
positional arguments:
session_id
options:
-h, --help show this help message and exit
--sqlite PATH
--postgres DSN
--offset OFFSET
--limit LIMIT
--after-sequence AFTER_SEQUENCE
--before-sequence BEFORE_SEQUENCE
--interaction-id INTERACTION_ID
--format {json,table,jsonl}
Output format (default: json).
--json Emit JSON (shorthand for `--format json`).
--table Emit human-readable output (shorthand for `--format
table`).
--jsonl Emit JSON Lines (shorthand for `--format jsonl`).
--output, -o FILE Write output to FILE instead of stdout.
```
## cayu session tools
```console
usage: cayu session tools [-h] [--sqlite PATH | --postgres DSN]
[--offset OFFSET] [--limit LIMIT]
[--after-sequence AFTER_SEQUENCE]
[--before-sequence BEFORE_SEQUENCE]
[--interaction-id INTERACTION_ID]
[--format {json,table,jsonl} | --json | --table |
--jsonl] [--output FILE]
session_id
Show paired durable tool calls without result bodies. Use event inspection for
bounded payload metadata.
positional arguments:
session_id
options:
-h, --help show this help message and exit
--sqlite PATH
--postgres DSN
--offset OFFSET
--limit LIMIT
--after-sequence AFTER_SEQUENCE
--before-sequence BEFORE_SEQUENCE
--interaction-id INTERACTION_ID
--format {json,table,jsonl}
Output format (default: json).
--json Emit JSON (shorthand for `--format json`).
--table Emit human-readable output (shorthand for `--format
table`).
--jsonl Emit JSON Lines (shorthand for `--format jsonl`).
--output, -o FILE Write output to FILE instead of stdout.
```
## cayu session events
```console
usage: cayu session events [-h] [--sqlite PATH | --postgres DSN]
[--type EVENT_TYPES] [--tool TOOL] [--agent AGENT]
[--environment ENVIRONMENT] [--since SINCE]
[--until UNTIL] [--after-sequence AFTER_SEQUENCE]
[--before-sequence BEFORE_SEQUENCE]
[--interaction-id INTERACTION_ID] [--limit LIMIT]
[--include-payload [BYTES]]
[--format {json,table,jsonl} | --json | --table |
--jsonl] [--output FILE]
session_id
Page durable session events with optional filters. Use `--include-payload`
only for bounded payload previews.
positional arguments:
session_id
options:
-h, --help show this help message and exit
--sqlite PATH
--postgres DSN
--type EVENT_TYPES
--tool TOOL
--agent AGENT
--environment ENVIRONMENT
--since SINCE
--until UNTIL
--after-sequence AFTER_SEQUENCE
--before-sequence BEFORE_SEQUENCE
--interaction-id INTERACTION_ID
--limit LIMIT
--include-payload [BYTES]
--format {json,table,jsonl}
Output format (default: json).
--json Emit JSON (shorthand for `--format json`).
--table Emit human-readable output (shorthand for `--format
table`).
--jsonl Emit JSON Lines (shorthand for `--format jsonl`).
--output, -o FILE Write output to FILE instead of stdout.
```
## cayu session transcript
```console
usage: cayu session transcript [-h] [--sqlite PATH | --postgres DSN]
[--offset OFFSET] [--limit LIMIT]
[--interaction-id INTERACTION_ID] [--sizes]
[--include-content [BYTES]]
[--format {json,table,jsonl} | --json |
--table | --jsonl] [--output FILE]
session_id
Page bounded transcript metadata with redacted content by default. Use
`--include-content` only for bounded, redacted previews.
positional arguments:
session_id
options:
-h, --help show this help message and exit
--sqlite PATH
--postgres DSN
--offset OFFSET
--limit LIMIT
--interaction-id INTERACTION_ID
--sizes Include serialized size metadata for each content
part.
--include-content [BYTES]
Include redacted serialized content, bounded per
message.
--format {json,table,jsonl}
Output format (default: json).
--json Emit JSON (shorthand for `--format json`).
--table Emit human-readable output (shorthand for `--format
table`).
--jsonl Emit JSON Lines (shorthand for `--format jsonl`).
--output, -o FILE Write output to FILE instead of stdout.
```
## cayu session resolve-provider-operation
```console
usage: cayu session resolve-provider-operation [-h] --stage-id STAGE_ID
--run-epoch RUN_EPOCH
--action {fallback_retry,fail}
[--reason REASON]
[--metadata JSON]
--server-url SERVER_URL
[--authorization-env NAME]
[--mutation-id MUTATION_ID]
[--format {json,table} |
--json | --table]
[--output FILE]
session_id
Send a run-epoch-fenced fallback_retry or fail disposition to the Cayu server.
Read stage_id and run_epoch from `cayu session show` or the session state API.
Authorization is read from CAYU_API_AUTHORIZATION by default and is never
printed.
positional arguments:
session_id
options:
-h, --help show this help message and exit
--stage-id STAGE_ID
--run-epoch RUN_EPOCH
--action {fallback_retry,fail}
--reason REASON
--metadata JSON Bounded JSON object recorded with the durable
resolution.
--server-url SERVER_URL
Cayu server root URL, including any mount prefix but
excluding /api.
--authorization-env NAME
Environment variable containing the complete
Authorization header value.
--mutation-id MUTATION_ID
Stable Cayu-Mutation-ID for correlating an ambiguous
SSE reconnect.
--format {json,table}
Output format (default: json).
--json Emit JSON (shorthand for `--format json`).
--table Emit human-readable output (shorthand for `--format
table`).
--output, -o FILE Write output to FILE instead of stdout.
```
## Related guides
[Getting started](https://cayu.dev/docs/get-started/quickstart/) · [Operations](https://cayu.dev/docs/operate/) · [Configuration](https://cayu.dev/docs/reference/configuration/)
# cayu storage
Generated from the released command parser. These commands show help only; operations run when you invoke the command without `--help`.
```console
usage: cayu storage [-h] {status,migrate,export} ...
Inspect, migrate, and export Cayu storage. Schema changes are explicit; start
with `cayu storage status`.
positional arguments:
{status,migrate,export}
status Show the database schema revision and any pending
migrations.
migrate Apply pending forward migrations under the backend
lock.
export Export sessions (or tasks) as JSON or JSONL for
backup/replay.
options:
-h, --help show this help message and exit
```
## cayu storage status
```console
usage: cayu storage status [-h] [--sqlite PATH | --postgres DSN]
[--acknowledge-breaking REVISION]
[--reset-empty-recall-state]
[--format {json,table} | --json | --table]
[--output FILE]
Show the database schema revision and any pending migrations. Without --sqlite
or --postgres, the target resolves from CAYU_DATABASE_URL, then
[tool.cayu.session_store]. Use `--output FILE` to write data to a destination.
Exits 0 when up to date, 3 when a forward migration is available, and 4 when
this build cannot migrate the database with the given inputs.
options:
-h, --help show this help message and exit
--sqlite PATH Path to a SQLite database file (overrides the resolved
target).
--postgres DSN Postgres connection string (overrides the resolved
target). Prefer CAYU_DATABASE_URL to keep credentials
off the command line.
--acknowledge-breaking REVISION
Acknowledge one exact pending breaking revision;
repeat for every breaking boundary in the planned
path.
--reset-empty-recall-state
Authorize rebuilding the empty prerelease recall
checkpoint/delivery tables when crossing revision 73.
--format {json,table}
Output format (default: json).
--json Emit JSON (shorthand for `--format json`).
--table Emit human-readable output (shorthand for `--format
table`).
--output, -o FILE Write output to FILE instead of stdout.
```
## cayu storage migrate
```console
usage: cayu storage migrate [-h] [--sqlite PATH | --postgres DSN]
[--backup PATH | --backup-sha256 SHA256 |
--backup-managed REF | --waive-backup]
[--acknowledge-breaking REVISION]
[--reset-empty-recall-state]
[--format {json,table} | --json | --table]
[--output FILE]
Apply pending forward migrations under the backend lock. Without --sqlite or
--postgres, the target resolves from CAYU_DATABASE_URL, then
[tool.cayu.session_store]. Use `--output FILE` to write data to a destination.
options:
-h, --help show this help message and exit
--sqlite PATH Path to a SQLite database file (overrides the resolved
target).
--postgres DSN Postgres connection string (overrides the resolved
target). Prefer CAYU_DATABASE_URL to keep credentials
off the command line.
--backup PATH SQLite backup destination (default: a unique sibling
file). Not valid for Postgres.
--backup-sha256 SHA256
Attest an application-consistent Postgres backup by
its lowercase SHA-256 digest.
--backup-managed REF Record a provider-managed Postgres backup: rds-
snapshot: or rds-pitr:.
--waive-backup Explicitly waive retained backup/restore authority.
--acknowledge-breaking REVISION
Acknowledge one exact pending breaking revision;
repeat for every breaking boundary in the planned
path.
--reset-empty-recall-state
Authorize rebuilding the empty prerelease recall
checkpoint/delivery tables when crossing revision 73.
--format {json,table}
Output format (default: json).
--json Emit JSON (shorthand for `--format json`).
--table Emit human-readable output (shorthand for `--format
table`).
--output, -o FILE Write output to FILE instead of stdout.
```
## cayu storage export
```console
usage: cayu storage export [-h] [--sqlite PATH | --postgres DSN] [--tasks]
[--max-session-bytes MAX_SESSION_BYTES]
[--max-record-bytes MAX_RECORD_BYTES]
[--format {json,jsonl} | --json | --jsonl]
[--output FILE]
Export sessions (or tasks) as JSON or JSONL for backup/replay. Without
--sqlite or --postgres, the target resolves from CAYU_DATABASE_URL, then
[tool.cayu.session_store]. Use `--output FILE` to write data to a destination.
options:
-h, --help show this help message and exit
--sqlite PATH Path to a SQLite database file (overrides the resolved
target).
--postgres DSN Postgres connection string (overrides the resolved
target). Prefer CAYU_DATABASE_URL to keep credentials
off the command line.
--tasks Export tasks instead of sessions.
--max-session-bytes MAX_SESSION_BYTES
Session export byte ceiling (default: 67108864); raise
for large histories.
--max-record-bytes MAX_RECORD_BYTES
Session component byte ceiling (default: 8388608);
does not change stored-value validation.
--format {json,jsonl}
Output format (default: json).
--json Emit JSON (shorthand for `--format json`).
--jsonl Emit JSON Lines (shorthand for `--format jsonl`).
--output, -o FILE Write output to FILE instead of stdout.
```
## Related guides
[Getting started](https://cayu.dev/docs/get-started/quickstart/) · [Operations](https://cayu.dev/docs/operate/) · [Configuration](https://cayu.dev/docs/reference/configuration/)
# cayu version
Generated from the released command parser. These commands show help only; operations run when you invoke the command without `--help`.
```console
usage: cayu version [-h]
Print the installed Cayu version, then return to `cayu --help`.
options:
-h, --help show this help message and exit
```
## Related guides
[Getting started](https://cayu.dev/docs/get-started/quickstart/) · [Operations](https://cayu.dev/docs/operate/) · [Configuration](https://cayu.dev/docs/reference/configuration/)
# cayu worker
Generated from the released command parser. These commands show help only; operations run when you invoke the command without `--help`.
```console
usage: cayu worker [-h] [--processes PROCESSES]
[--shutdown-grace-seconds SHUTDOWN_GRACE_SECONDS]
name
Build the configured Cayu application once and run one named async worker
until it completes or receives a cooperative stop request.
positional arguments:
name Worker name from [tool.cayu.workers].
options:
-h, --help show this help message and exit
--processes PROCESSES
Independent worker processes; each constructs its own
app (default: 1).
--shutdown-grace-seconds SHUTDOWN_GRACE_SECONDS
Maximum cooperative shutdown time after SIGINT or
SIGTERM (default: 30).
```
## Related guides
[Getting started](https://cayu.dev/docs/get-started/quickstart/) · [Operations](https://cayu.dev/docs/operate/) · [Configuration](https://cayu.dev/docs/reference/configuration/)
# Configuration reference
Configuration is explicit at several boundaries. Set a value at its owning boundary rather than adding a second environment switch that bypasses the factory.
| Owner | Purpose | Exact declarations |
| --- | --- | --- |
| `configuration/settings.py` | Generated project's validated deployment inputs | [Project anatomy](https://cayu.dev/docs/get-started/project-anatomy/) |
| `CayuConfig` | Immutable process-wide runtime defaults | [Configuration API](https://cayu.dev/docs/reference/python/configuration/) |
| `RunRequest` | One run's input and allowed overrides | [Applications API](https://cayu.dev/docs/reference/python/applications/) |
| `AgentSpec` | Model, prompt and agent capabilities | [Agents API](https://cayu.dev/docs/reference/python/agents/) |
| `ServerConfig` | Authentication, exposure, CORS and lifecycle | [Server API](https://cayu.dev/docs/reference/python/server/) |
| `[tool.cayu]` | Project factory and CLI convention | [Project convention](https://cayu.dev/docs/reference/project-convention/) |
The linked API entries contain fields, types, defaults and validators from the documented release. Source expressions such as `Field(...)` preserve validation bounds and factories rather than replacing them with guessed values.
## Project tables
```toml
[tool.cayu]
factory = "app:build_app"
[tool.cayu.session_store]
backend = "sqlite"
path = "data/cayu.db"
[tool.cayu.serve]
auth = "cayu.server.environment_auth:OPERATOR_BASIC_AUTH"
[tool.cayu.workers]
dispatch = "workers:run_dispatch"
```
Use the generated project's existing tables when adding a capability. The factory is a synchronous zero-argument callable. Named workers are async `(app, stop)` entrypoints. `[tool.cayu.scaffold]` records the generator's preset, adapters, capabilities and convention version; it is not runtime authorization.
## Environment inputs
| Variable | Owner | Behavior |
| --- | --- | --- |
| `CAYU_DATABASE_URL` | Application stores and storage-aware CLI | PostgreSQL or absolute SQLite URL; blank configured values fail |
| `CAYU_DATABASE_POOL_MAX` | Store pools | Positive pool ceiling, default `5` per pool |
| `CAYU_DATABASE_DIRECT_URL` | PostgreSQL listener and resolved migration/status | Direct connection when pooled traffic uses a transaction proxy |
| `CAYU_REQUIRE_POSTGRES` | SQLite store construction | `1` rejects SQLite; `0` or unset does not |
| `CAYU_OPERATOR_USERNAME` | Environment Basic-auth target | Nonblank deployed operator username |
| `CAYU_OPERATOR_PASSWORD` | Environment Basic-auth target | Nonblank deployed secret password |
| `CAYU_WORKER_INDEX` | Multi-process worker supervisor | Zero-based child process index |
| `CAYU_WORKER_COUNT` | Multi-process worker supervisor | Total child count |
Provider credentials and model selections belong to the chosen project's validated settings and provider adapter. Read its generated README and [providers guide](https://cayu.dev/docs/build/providers/); there is no universal API key for all providers.
Storage target precedence is explicit CLI selector, environment, project table, then the existing conventional local file. Typed server configuration does not read a secret manager on your behalf. Resolve inputs before constructing it.
## Inspect effective settings
```sh
cayu inspect --json
cayu check --fail-on warning --json
```
Inspect reports non-secret configuration and ownership. Check verifies configured readiness without establishing live-provider quality. Preserve failure exit codes in automation. For complete option lookup, use [CLI reference](https://cayu.dev/docs/reference/cli/).
# Glossary
| Term | Meaning |
| --- | --- |
| Application | Process-scoped graph of providers, agents, tools, stores and policy |
| Factory | Synchronous zero-argument function constructing a fresh application |
| Session | Durable identity and recorded history of agent work |
| Interaction | A runtime interaction within that session |
| Step | A bounded runtime iteration, potentially containing model and tool activity |
| Attempt | One try within a repair or retry boundary |
| Event | Recorded runtime evidence with identity and type |
| Trajectory | Retained sequence of execution evidence used for inspection or evaluation |
| Context | Model-facing input selected for an invocation |
| Compaction | Replacement of part of the model-facing context with a bounded summary/projection |
| Memory | Scoped, application-selected retained information used by later work |
| Knowledge | Domain facts exposed through an application-owned interface |
| Tool effect | Declared category used by policy to classify a capability |
| Approval | Exact human decision at an application/runtime authorization boundary |
| Task | Durable job record with assignment, claim and result state |
| Worker | Process/loop that claims or advances work |
| Dispatcher | Coordination for continuing existing sessions |
| Lease | Time-bounded execution ownership |
| Fence | Identity/generation check that prevents stale owners from mutating state |
| Receipt | Durable evidence identifying a completed or reconciled operation |
| Workspace | Bounded file-access interface |
| Runner | Process-execution interface |
| Environment | Session-owned resources and lifecycle around execution |
| Artifact | Retained output with a stable retrievable identity |
| Eval case | Input plus expectations for one logical behavior |
| Eval suite | Versioned collection of cases and execution/judgment configuration |
| Judge | Evaluator of semantic quality against an explicit rubric and evidence |
| Baseline | Reviewed immutable evaluation result used for compatible comparison |
Use [concepts](https://cayu.dev/docs/concepts/) for the relationships and [API reference](https://cayu.dev/docs/reference/python/) for the exact public types.
# HTTP: agents
Default control-plane operations. [Complete OpenAPI schema](https://cayu.dev/docs/openapi.json) · [Authentication and tenancy](https://cayu.dev/docs/operate/authentication/).
## GET /api/agents
List Agents
Operation ID: `list_agents_api_agents_get`.
### Responses
```json
{
"200": {
"description": "Successful Response",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/AgentsResponse"
}
}
}
}
}
```
Model declarations: [AgentsResponse](https://cayu.dev/docs/reference/http/schemas/models-1/#agentsresponse).
## GET /api/agents/{agent_name}
Get Agent
Operation ID: `get_agent_api_agents__agent_name__get`.
| Parameter | Location | Required | Schema |
| --- | --- | --- | --- |
| `agent_name` | path | True | `{"type":"string","minLength":1,"title":"Agent Name"}` |
### Responses
```json
{
"200": {
"description": "Successful Response",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/AgentsResponse"
}
}
}
},
"422": {
"description": "Validation Error",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/HTTPValidationError"
}
}
}
}
}
```
Model declarations: [AgentsResponse](https://cayu.dev/docs/reference/http/schemas/models-1/#agentsresponse), [HTTPValidationError](https://cayu.dev/docs/reference/http/schemas/models-5/#httpvalidationerror).
# HTTP: artifacts
Default control-plane operations. [Complete OpenAPI schema](https://cayu.dev/docs/openapi.json) · [Authentication and tenancy](https://cayu.dev/docs/operate/authentication/).
## GET /api/artifacts
List Artifacts
Operation ID: `list_artifacts_api_artifacts_get`.
| Parameter | Location | Required | Schema |
| --- | --- | --- | --- |
| `limit` | query | False | `{"type":"integer","maximum":500,"minimum":1,"default":100,"title":"Limit"}` |
| `offset` | query | False | `{"type":"integer","maximum":10000,"minimum":0,"default":0,"title":"Offset"}` |
| `artifact_store_id` | query | False | `{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Artifact Store Id"}` |
| `scope` | query | False | `{"anyOf":[{"$ref":"#/components/schemas/ArtifactScope"},{"type":"null"}],"title":"Scope"}` |
| `session_id` | query | False | `{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Session Id"}` |
| `agent_name` | query | False | `{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Agent Name"}` |
| `environment_name` | query | False | `{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Environment Name"}` |
### Responses
```json
{
"200": {
"description": "Successful Response",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ArtifactsResponse"
}
}
}
},
"404": {
"description": "The requested artifact store or artifact does not exist.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ApiErrorResponse"
}
}
}
},
"409": {
"description": "Registered artifact-store identifiers are not unique.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ApiErrorResponse"
}
}
}
},
"500": {
"description": "An artifact store is misconfigured or returned invalid data.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ApiErrorResponse"
}
}
}
},
"503": {
"description": "An artifact store is unavailable.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ApiErrorResponse"
}
}
}
},
"422": {
"description": "Validation Error",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/HTTPValidationError"
}
}
}
}
}
```
Model declarations: [ApiErrorResponse](https://cayu.dev/docs/reference/http/schemas/models-1/#apierrorresponse), [ArtifactsResponse](https://cayu.dev/docs/reference/http/schemas/models-3/#artifactsresponse), [HTTPValidationError](https://cayu.dev/docs/reference/http/schemas/models-5/#httpvalidationerror).
## GET /api/artifacts/{artifact_id}
Get Artifact
Operation ID: `get_artifact_api_artifacts__artifact_id__get`.
| Parameter | Location | Required | Schema |
| --- | --- | --- | --- |
| `artifact_id` | path | True | `{"type":"string","minLength":1,"title":"Artifact Id"}` |
| `artifact_store_id` | query | False | `{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Artifact Store Id"}` |
| `max_bytes` | query | False | `{"type":"integer","maximum":262144,"minimum":1,"default":64000,"title":"Max Bytes"}` |
### Responses
```json
{
"200": {
"description": "Successful Response",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ArtifactReadResponse"
}
}
}
},
"404": {
"description": "The requested artifact store or artifact does not exist.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ApiErrorResponse"
}
}
}
},
"409": {
"description": "Registered artifact-store identifiers are not unique.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ApiErrorResponse"
}
}
}
},
"500": {
"description": "An artifact store is misconfigured or returned invalid data.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ApiErrorResponse"
}
}
}
},
"503": {
"description": "An artifact store is unavailable.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ApiErrorResponse"
}
}
}
},
"422": {
"description": "Validation Error",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/HTTPValidationError"
}
}
}
}
}
```
Model declarations: [ApiErrorResponse](https://cayu.dev/docs/reference/http/schemas/models-1/#apierrorresponse), [ArtifactReadResponse](https://cayu.dev/docs/reference/http/schemas/models-3/#artifactreadresponse), [HTTPValidationError](https://cayu.dev/docs/reference/http/schemas/models-5/#httpvalidationerror).
## GET /api/artifacts/{artifact_id}/content
Get Artifact Content
Operation ID: `get_artifact_content_api_artifacts__artifact_id__content_get`.
| Parameter | Location | Required | Schema |
| --- | --- | --- | --- |
| `artifact_id` | path | True | `{"type":"string","minLength":1,"title":"Artifact Id"}` |
| `artifact_store_id` | query | True | `{"type":"string","minLength":1,"title":"Artifact Store Id"}` |
| `disposition` | query | False | `{"enum":["attachment","inline"],"type":"string","default":"attachment","title":"Disposition"}` |
| `max_bytes` | query | False | `{"type":"integer","maximum":268435456,"minimum":1,"default":268435456,"title":"Max Bytes"}` |
### Responses
```json
{
"200": {
"description": "Complete artifact bytes. The response Content-Type reflects validated stored artifact metadata.",
"content": {
"application/octet-stream": {
"schema": {
"type": "string",
"format": "binary"
}
}
},
"headers": {
"Content-Disposition": {
"description": "Sanitized inline or attachment disposition and filename.",
"schema": {
"type": "string"
}
},
"X-Content-Type-Options": {
"description": "Always nosniff.",
"schema": {
"type": "string",
"enum": [
"nosniff"
]
}
},
"X-Cayu-Artifact-Id": {
"description": "Sanitized artifact identifier.",
"schema": {
"type": "string"
}
},
"X-Cayu-Artifact-Store-Id": {
"description": "Sanitized artifact-store identifier.",
"schema": {
"type": "string"
}
},
"Cache-Control": {
"description": "Prevents authenticated artifact bytes from being cached.",
"schema": {
"type": "string",
"enum": [
"private, no-store"
]
}
}
}
},
"413": {
"description": "Artifact exceeds the direct content response limit.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ApiErrorResponse"
}
}
}
},
"404": {
"description": "The requested artifact store or artifact does not exist.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ApiErrorResponse"
}
}
}
},
"409": {
"description": "Registered artifact-store identifiers are not unique.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ApiErrorResponse"
}
}
}
},
"500": {
"description": "An artifact store is misconfigured or returned invalid data.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ApiErrorResponse"
}
}
}
},
"503": {
"description": "An artifact store is unavailable.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ApiErrorResponse"
}
}
}
},
"422": {
"description": "Validation Error",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/HTTPValidationError"
}
}
}
}
}
```
Model declarations: [ApiErrorResponse](https://cayu.dev/docs/reference/http/schemas/models-1/#apierrorresponse), [HTTPValidationError](https://cayu.dev/docs/reference/http/schemas/models-5/#httpvalidationerror).
# HTTP: causal-budgets
Default control-plane operations. [Complete OpenAPI schema](https://cayu.dev/docs/openapi.json) · [Authentication and tenancy](https://cayu.dev/docs/operate/authentication/).
## GET /api/causal-budgets/{causal_budget_id}/usage
Get Causal Budget Usage
Operation ID: `get_causal_budget_usage_api_causal_budgets__causal_budget_id__usage_get`.
| Parameter | Location | Required | Schema |
| --- | --- | --- | --- |
| `causal_budget_id` | path | True | `{"type":"string","minLength":1,"title":"Causal Budget Id"}` |
### Responses
```json
{
"200": {
"description": "Successful Response",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/CausalBudgetUsageSummary"
}
}
}
},
"422": {
"description": "Validation Error",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/HTTPValidationError"
}
}
}
}
}
```
Model declarations: [CausalBudgetUsageSummary](https://cayu.dev/docs/reference/http/schemas/models-4/#causalbudgetusagesummary), [HTTPValidationError](https://cayu.dev/docs/reference/http/schemas/models-5/#httpvalidationerror).
## POST /api/causal-budgets/{causal_budget_id}/cost
Estimate Causal Budget Cost
Operation ID: `estimate_causal_budget_cost_api_causal_budgets__causal_budget_id__cost_post`.
| Parameter | Location | Required | Schema |
| --- | --- | --- | --- |
| `causal_budget_id` | path | True | `{"type":"string","minLength":1,"title":"Causal Budget Id"}` |
### Request body
```json
{
"required": true,
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/SessionCostBody"
}
}
}
}
```
Model declarations: [SessionCostBody](https://cayu.dev/docs/reference/http/schemas/models-8/#sessioncostbody).
### Responses
```json
{
"200": {
"description": "Successful Response",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/CausalBudgetCostSummary"
}
}
}
},
"422": {
"description": "Validation Error",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/HTTPValidationError"
}
}
}
}
}
```
Model declarations: [CausalBudgetCostSummary](https://cayu.dev/docs/reference/http/schemas/models-4/#causalbudgetcostsummary), [HTTPValidationError](https://cayu.dev/docs/reference/http/schemas/models-5/#httpvalidationerror).
## POST /api/causal-budgets/{causal_budget_id}/summary
Get Causal Budget Summary
Operation ID: `get_causal_budget_summary_api_causal_budgets__causal_budget_id__summary_post`.
| Parameter | Location | Required | Schema |
| --- | --- | --- | --- |
| `causal_budget_id` | path | True | `{"type":"string","minLength":1,"title":"Causal Budget Id"}` |
### Request body
```json
{
"required": true,
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/SessionCostBody"
}
}
}
}
```
Model declarations: [SessionCostBody](https://cayu.dev/docs/reference/http/schemas/models-8/#sessioncostbody).
### Responses
```json
{
"200": {
"description": "Successful Response",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/CausalBudgetSummaryResponse"
}
}
}
},
"413": {
"description": "The causal-budget summary exceeds its session or serialized response safety bound.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ApiErrorResponse"
}
}
}
},
"501": {
"description": "The configured session store cannot enforce bounded accounting reads.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ApiErrorResponse"
}
}
}
},
"422": {
"description": "Validation Error",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/HTTPValidationError"
}
}
}
}
}
```
Model declarations: [ApiErrorResponse](https://cayu.dev/docs/reference/http/schemas/models-1/#apierrorresponse), [CausalBudgetSummaryResponse](https://cayu.dev/docs/reference/http/schemas/models-4/#causalbudgetsummaryresponse), [HTTPValidationError](https://cayu.dev/docs/reference/http/schemas/models-5/#httpvalidationerror).
# HTTP: contract
Default control-plane operations. [Complete OpenAPI schema](https://cayu.dev/docs/openapi.json) · [Authentication and tenancy](https://cayu.dev/docs/operate/authentication/).
## GET /api/contract
Get Contract
Return the versioned Cayu server contract. Authentication controls access to protected routes, but AuthContext.tenant is actor provenance only and does not filter or isolate Cayu data. Capability metadata supports presentation and discovery only; route enforcement remains authoritative.
Operation ID: `get_contract_api_contract_get`.
### Responses
```json
{
"200": {
"description": "Successful Response",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ServerContractResponse"
}
}
}
}
}
```
Model declarations: [ServerContractResponse](https://cayu.dev/docs/reference/http/schemas/models-8/#servercontractresponse).
# HTTP: diagnostics
Default control-plane operations. [Complete OpenAPI schema](https://cayu.dev/docs/openapi.json) · [Authentication and tenancy](https://cayu.dev/docs/operate/authentication/).
## GET /api/diagnostics/requests
Get Request Cost Diagnostics
Summarize this process's recent request cost from the bounded in-memory request-timing buffer: per-route rate, wall time, apportioned CPU seconds, and the estimated share of `vcpu` CPUs. Records use route templates and keyed client hashes only. Returns `enabled: false` when request timing is off.
Operation ID: `get_request_cost_diagnostics_api_diagnostics_requests_get`.
| Parameter | Location | Required | Schema |
| --- | --- | --- | --- |
| `since_seconds` | query | False | `{"type":"number","maximum":86400,"exclusiveMinimum":0,"default":300,"title":"Since Seconds"}` |
| `vcpu` | query | False | `{"type":"number","maximum":1024,"exclusiveMinimum":0,"default":1.0,"title":"Vcpu"}` |
### Responses
```json
{
"200": {
"description": "Successful Response",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/RequestCostSummary"
}
}
}
},
"422": {
"description": "Validation Error",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/HTTPValidationError"
}
}
}
}
}
```
Model declarations: [HTTPValidationError](https://cayu.dev/docs/reference/http/schemas/models-5/#httpvalidationerror), [RequestCostSummary](https://cayu.dev/docs/reference/http/schemas/models-7/#requestcostsummary).
# HTTP: environments
Default control-plane operations. [Complete OpenAPI schema](https://cayu.dev/docs/openapi.json) · [Authentication and tenancy](https://cayu.dev/docs/operate/authentication/).
## GET /api/environments
List Environments
Operation ID: `list_environments_api_environments_get`.
### Responses
```json
{
"200": {
"description": "Successful Response",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/EnvironmentsResponse"
}
}
}
}
}
```
Model declarations: [EnvironmentsResponse](https://cayu.dev/docs/reference/http/schemas/models-4/#environmentsresponse).
## GET /api/environments/{environment_name}
Get Environment
Operation ID: `get_environment_api_environments__environment_name__get`.
| Parameter | Location | Required | Schema |
| --- | --- | --- | --- |
| `environment_name` | path | True | `{"type":"string","minLength":1,"title":"Environment Name"}` |
### Responses
```json
{
"200": {
"description": "Successful Response",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/EnvironmentsResponse"
}
}
}
},
"422": {
"description": "Validation Error",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/HTTPValidationError"
}
}
}
}
}
```
Model declarations: [EnvironmentsResponse](https://cayu.dev/docs/reference/http/schemas/models-4/#environmentsresponse), [HTTPValidationError](https://cayu.dev/docs/reference/http/schemas/models-5/#httpvalidationerror).
# HTTP: event-side-effects
Default control-plane operations. [Complete OpenAPI schema](https://cayu.dev/docs/openapi.json) · [Authentication and tenancy](https://cayu.dev/docs/operate/authentication/).
## GET /api/event-side-effects/health
Event Side Effect Health
Operation ID: `event_side_effect_health_api_event_side_effects_health_get`.
### Responses
```json
{
"200": {
"description": "Successful Response",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/EventSideEffectHealthResponse"
}
}
}
},
"503": {
"description": "Delivery store unavailable"
}
}
```
Model declarations: [EventSideEffectHealthResponse](https://cayu.dev/docs/reference/http/schemas/models-5/#eventsideeffecthealthresponse).
## GET /api/event-side-effects/deliveries
Event Side Effect Deliveries
Operation ID: `event_side_effect_deliveries_api_event_side_effects_deliveries_get`.
| Parameter | Location | Required | Schema |
| --- | --- | --- | --- |
| `status` | query | False | `{"anyOf":[{"enum":["pending","leased","failed","delivered","dead_lettered"],"type":"string"},{"type":"null"}],"title":"Status"}` |
| `claimable_only` | query | False | `{"type":"boolean","default":false,"title":"Claimable Only"}` |
| `outstanding_only` | query | False | `{"type":"boolean","default":true,"title":"Outstanding Only"}` |
| `limit` | query | False | `{"type":"integer","maximum":200,"minimum":1,"default":100,"title":"Limit"}` |
| `cursor` | query | False | `{"anyOf":[{"type":"string","maxLength":16384},{"type":"null"}],"title":"Cursor"}` |
### Responses
```json
{
"200": {
"description": "Successful Response",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/PersistedEventSideEffectPage"
}
}
}
},
"400": {
"description": "Invalid cursor"
},
"503": {
"description": "Delivery store unavailable"
},
"422": {
"description": "Validation Error",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/HTTPValidationError"
}
}
}
}
}
```
Model declarations: [HTTPValidationError](https://cayu.dev/docs/reference/http/schemas/models-5/#httpvalidationerror), [PersistedEventSideEffectPage](https://cayu.dev/docs/reference/http/schemas/models-7/#persistedeventsideeffectpage).
# HTTP: health
Default control-plane operations. [Complete OpenAPI schema](https://cayu.dev/docs/openapi.json) · [Authentication and tenancy](https://cayu.dev/docs/operate/authentication/).
## GET /api/health
Health
Operation ID: `health_api_health_get`.
### Responses
```json
{
"200": {
"description": "Successful Response",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/HealthResponse"
}
}
}
}
}
```
Model declarations: [HealthResponse](https://cayu.dev/docs/reference/http/schemas/models-5/#healthresponse).
# HTTP API reference
The Cayu HTTP control plane. Download the [OpenAPI schema](https://cayu.dev/docs/openapi.json) for exact request and response models, required fields, enum values, validation bounds and response content types.
## Configuration and authentication
This reference is generated by constructing the released server with `ServerConfig.local_development()` and in-memory stores; it does not launch a listener or contact providers. The schema describes its default route groups. Optional browser-control and other application-selected capabilities can add routes; application policies can disable groups. Use your configured server's schema for those variations.
For local development, run `cayu serve --dev --host 127.0.0.1` and open `/cayu/`. Deployed servers require `ServerConfig.protected(...)` or a configured authentication target. Authentication, business authorization and tenant isolation are separate decisions. `AuthContext.tenant` is operator provenance; it does not filter stored data.
## Requests and responses
Paths below include the default `/api` prefix. Embedding under a host mount can add another prefix. Inspect operation-specific success/error schemas. Streaming endpoints can return `text/event-stream`; consume events incrementally and follow the declared terminal behavior. A successful HTTP response does not establish that a requested run or external effect completed.
## Endpoint groups
| Group | Operations |
| --- | --- |
| [agents](https://cayu.dev/docs/reference/http/agents/) | 2 |
| [artifacts](https://cayu.dev/docs/reference/http/artifacts/) | 3 |
| [causal-budgets](https://cayu.dev/docs/reference/http/causal-budgets/) | 3 |
| [contract](https://cayu.dev/docs/reference/http/contract/) | 1 |
| [diagnostics](https://cayu.dev/docs/reference/http/diagnostics/) | 1 |
| [environments](https://cayu.dev/docs/reference/http/environments/) | 2 |
| [event-side-effects](https://cayu.dev/docs/reference/http/event-side-effects/) | 2 |
| [health](https://cayu.dev/docs/reference/http/health/) | 1 |
| [knowledge](https://cayu.dev/docs/reference/http/knowledge/) | 4 |
| [operations](https://cayu.dev/docs/reference/http/operations/) | 1 |
| [pending-actions](https://cayu.dev/docs/reference/http/pending-actions/) | 1 |
| [provider-operations](https://cayu.dev/docs/reference/http/provider-operations/) | 1 |
| [recovery](https://cayu.dev/docs/reference/http/recovery/) | 1 |
| [resume](https://cayu.dev/docs/reference/http/resume/) | 1 |
| [run](https://cayu.dev/docs/reference/http/run/) | 1 |
| [sessions](https://cayu.dev/docs/reference/http/sessions/) | 25 |
| [system](https://cayu.dev/docs/reference/http/system/) | 1 |
| [tasks](https://cayu.dev/docs/reference/http/tasks/) | 9 |
| [tool-approvals](https://cayu.dev/docs/reference/http/tool-approvals/) | 2 |
| [tool-rounds](https://cayu.dev/docs/reference/http/tool-rounds/) | 1 |
| [usage](https://cayu.dev/docs/reference/http/usage/) | 1 |
| [user-input](https://cayu.dev/docs/reference/http/user-input/) | 2 |
## Error handling
Validation failures are described by the operation's HTTP validation schema. Authentication failures use the configured access policy. Domain conflicts, unavailable state and runtime failures carry their own typed evidence; read the response body and the session's durable events before deciding whether a retry is safe. Use idempotency and reconciliation for mutations with an uncertain downstream outcome.
## Related
[Server setup](https://cayu.dev/docs/operate/server/) · [Application UIs](https://cayu.dev/docs/build/application-ui/) · [Runtime guarantees](https://cayu.dev/docs/reference/runtime-guarantees/)
# HTTP: knowledge
Default control-plane operations. [Complete OpenAPI schema](https://cayu.dev/docs/openapi.json) · [Authentication and tenancy](https://cayu.dev/docs/operate/authentication/).
## GET /api/knowledge/pending
List Pending Knowledge
Operation ID: `list_pending_knowledge_api_knowledge_pending_get`.
| Parameter | Location | Required | Schema |
| --- | --- | --- | --- |
| `namespace` | query | False | `{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Namespace"}` |
| `label` | query | False | `{"anyOf":[{"type":"array","items":{"type":"string"}},{"type":"null"}],"title":"Label"}` |
| `kind` | query | False | `{"anyOf":[{"type":"array","items":{"type":"string"}},{"type":"null"}],"title":"Kind"}` |
| `aspect` | query | False | `{"anyOf":[{"type":"array","items":{"type":"string"}},{"type":"null"}],"title":"Aspect"}` |
| `visibility` | query | False | `{"anyOf":[{"type":"array","items":{"$ref":"#/components/schemas/KnowledgeVisibility"}},{"type":"null"}],"title":"Visibility"}` |
| `source_type` | query | False | `{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Source Type"}` |
| `source_id` | query | False | `{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Source Id"}` |
| `limit` | query | False | `{"type":"integer","default":50,"title":"Limit"}` |
| `max_bytes` | query | False | `{"type":"integer","default":20000,"title":"Max Bytes"}` |
### Responses
```json
{
"200": {
"description": "Successful Response",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/PendingKnowledgeListResponse"
}
}
}
},
"422": {
"description": "Validation Error",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/HTTPValidationError"
}
}
}
}
}
```
Model declarations: [HTTPValidationError](https://cayu.dev/docs/reference/http/schemas/models-5/#httpvalidationerror), [PendingKnowledgeListResponse](https://cayu.dev/docs/reference/http/schemas/models-6/#pendingknowledgelistresponse).
## GET /api/knowledge/pending/{entry_id}
Get Pending Knowledge
Operation ID: `get_pending_knowledge_api_knowledge_pending__entry_id__get`.
| Parameter | Location | Required | Schema |
| --- | --- | --- | --- |
| `entry_id` | path | True | `{"type":"string","minLength":1,"title":"Entry Id"}` |
| `max_chunks` | query | False | `{"type":"integer","maximum":50,"minimum":1,"default":50,"title":"Max Chunks"}` |
| `max_bytes` | query | False | `{"type":"integer","maximum":128000,"minimum":1,"default":128000,"title":"Max Bytes"}` |
### Responses
```json
{
"200": {
"description": "Successful Response",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/PendingKnowledgeDetailResponse"
}
}
}
},
"422": {
"description": "Validation Error",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/HTTPValidationError"
}
}
}
}
}
```
Model declarations: [HTTPValidationError](https://cayu.dev/docs/reference/http/schemas/models-5/#httpvalidationerror), [PendingKnowledgeDetailResponse](https://cayu.dev/docs/reference/http/schemas/models-6/#pendingknowledgedetailresponse).
## POST /api/knowledge/{entry_id}/approve
Approve Knowledge
Operation ID: `approve_knowledge_api_knowledge__entry_id__approve_post`.
| Parameter | Location | Required | Schema |
| --- | --- | --- | --- |
| `entry_id` | path | True | `{"type":"string","minLength":1,"title":"Entry Id"}` |
| `Idempotency-Key` | header | False | `{"anyOf":[{"type":"string","minLength":1,"maxLength":256},{"type":"null"}],"title":"Idempotency-Key"}` |
### Responses
```json
{
"200": {
"description": "Successful Response",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ApiReviewedKnowledgeEntry"
}
}
}
},
"422": {
"description": "Validation Error",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/HTTPValidationError"
}
}
}
}
}
```
Model declarations: [ApiReviewedKnowledgeEntry](https://cayu.dev/docs/reference/http/schemas/models-2/#apireviewedknowledgeentry), [HTTPValidationError](https://cayu.dev/docs/reference/http/schemas/models-5/#httpvalidationerror).
## POST /api/knowledge/{entry_id}/reject
Reject Knowledge
Operation ID: `reject_knowledge_api_knowledge__entry_id__reject_post`.
| Parameter | Location | Required | Schema |
| --- | --- | --- | --- |
| `entry_id` | path | True | `{"type":"string","minLength":1,"title":"Entry Id"}` |
### Responses
```json
{
"200": {
"description": "Successful Response",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ApiReviewedKnowledgeEntry"
}
}
}
},
"422": {
"description": "Validation Error",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/HTTPValidationError"
}
}
}
}
}
```
Model declarations: [ApiReviewedKnowledgeEntry](https://cayu.dev/docs/reference/http/schemas/models-2/#apireviewedknowledgeentry), [HTTPValidationError](https://cayu.dev/docs/reference/http/schemas/models-5/#httpvalidationerror).
# HTTP: operations
Default control-plane operations. [Complete OpenAPI schema](https://cayu.dev/docs/openapi.json) · [Authentication and tenancy](https://cayu.dev/docs/operate/authentication/).
## POST /api/operations/snapshot
Get Operational Snapshot
Return exact current status counts from each configured store. Session and task sections are separate store-local snapshots and are not presented as one cross-store atomic read. Scope is the configured stores; authentication does not add tenant filtering.
Operation ID: `get_operational_snapshot_api_operations_snapshot_post`.
### Request body
```json
{
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/OperationalSnapshotRequest"
}
}
},
"required": true
}
```
Model declarations: [OperationalSnapshotRequest](https://cayu.dev/docs/reference/http/schemas/models-6/#operationalsnapshotrequest).
### Responses
```json
{
"200": {
"description": "Successful Response",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/OperationalSnapshotResponse"
}
}
}
},
"501": {
"description": "The configured store does not implement this aggregate read.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ApiErrorResponse"
}
}
}
},
"422": {
"description": "Validation Error",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/HTTPValidationError"
}
}
}
}
}
```
Model declarations: [ApiErrorResponse](https://cayu.dev/docs/reference/http/schemas/models-1/#apierrorresponse), [HTTPValidationError](https://cayu.dev/docs/reference/http/schemas/models-5/#httpvalidationerror), [OperationalSnapshotResponse](https://cayu.dev/docs/reference/http/schemas/models-6/#operationalsnapshotresponse).
# HTTP: pending-actions
Default control-plane operations. [Complete OpenAPI schema](https://cayu.dev/docs/openapi.json) · [Authentication and tenancy](https://cayu.dev/docs/operate/authentication/).
## GET /api/pending-actions
List Pending Actions
Operation ID: `list_pending_actions_api_pending_actions_get`.
| Parameter | Location | Required | Schema |
| --- | --- | --- | --- |
| `limit` | query | False | `{"type":"integer","maximum":200,"minimum":1,"default":50,"title":"Limit"}` |
| `session_id` | query | False | `{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Session Id"}` |
| `q` | query | False | `{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Q"}` |
| `kind` | query | False | `{"anyOf":[{"$ref":"#/components/schemas/PendingActionKind"},{"type":"null"}],"title":"Kind"}` |
| `agent_name` | query | False | `{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Agent Name"}` |
| `environment_name` | query | False | `{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Environment Name"}` |
| `cursor` | query | False | `{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Cursor"}` |
### Responses
```json
{
"200": {
"description": "Successful Response",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/PendingActionsResponse"
}
}
}
},
"409": {
"description": "A checkpoint requires a different Cayu runtime version.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/CheckpointCompatibilityErrorResponse"
}
}
}
},
"413": {
"description": "The pending-action page exceeds the bounded response size.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ApiErrorResponse"
}
}
}
},
"422": {
"description": "Validation Error",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/HTTPValidationError"
}
}
}
}
}
```
Model declarations: [ApiErrorResponse](https://cayu.dev/docs/reference/http/schemas/models-1/#apierrorresponse), [CheckpointCompatibilityErrorResponse](https://cayu.dev/docs/reference/http/schemas/models-4/#checkpointcompatibilityerrorresponse), [HTTPValidationError](https://cayu.dev/docs/reference/http/schemas/models-5/#httpvalidationerror), [PendingActionsResponse](https://cayu.dev/docs/reference/http/schemas/models-6/#pendingactionsresponse).
# HTTP: provider-operations
Default control-plane operations. [Complete OpenAPI schema](https://cayu.dev/docs/openapi.json) · [Authentication and tenancy](https://cayu.dev/docs/operate/authentication/).
## POST /api/provider-operations/resolve
Resolve Provider Operation
Operation ID: `resolve_provider_operation_api_provider_operations_resolve_post`.
| Parameter | Location | Required | Schema |
| --- | --- | --- | --- |
| `Cayu-Mutation-ID` | header | False | `{"anyOf":[{"type":"string","minLength":1,"maxLength":128,"pattern":"^[A-Za-z0-9][A-Za-z0-9._~-]*$"},{"type":"null"}],"description":"Client-generated mutation identity used to correlate an ambiguous SSE reconnect with its durable server acceptance event. Send the same value on the initial mutation and every Last-Event-ID replay request; it is a replay correlation key, not permission to repeat the POST.","title":"Cayu-Mutation-Id"}` |
### Request body
```json
{
"required": true,
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ProviderOperationResolutionBody"
}
}
}
}
```
Model declarations: [ProviderOperationResolutionBody](https://cayu.dev/docs/reference/http/schemas/models-7/#provideroperationresolutionbody).
### Responses
```json
{
"200": {
"description": "Server-Sent Events stream. Each runtime event is emitted as an SSE frame whose `data:` value is a JSON SseEventEnvelope. A classified terminal runtime or observer condition is emitted as `event: error` with a SseErrorEnvelope payload.",
"content": {
"text/event-stream": {
"schema": {
"type": "string",
"description": "SSE stream. Runtime `data:` frames contain SseEventEnvelope JSON; `event: error` frames contain SseErrorEnvelope JSON."
}
}
}
},
"404": {
"description": "The replay session or mutation target does not exist.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ApiErrorResponse"
}
}
}
},
"409": {
"description": "The replay event marker is unknown or the mutation conflicts with the current session state.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ApiErrorResponse"
}
}
}
},
"500": {
"description": "The mutation could not open an accepted durable stream.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ApiErrorResponse"
}
}
}
},
"413": {
"description": "The control-plane request exceeds its encoded byte limit.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ApiErrorResponse"
}
}
}
},
"422": {
"description": "Validation Error",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/HTTPValidationError"
}
}
}
}
}
```
Model declarations: [ApiErrorResponse](https://cayu.dev/docs/reference/http/schemas/models-1/#apierrorresponse), [HTTPValidationError](https://cayu.dev/docs/reference/http/schemas/models-5/#httpvalidationerror).
# HTTP: recovery
Default control-plane operations. [Complete OpenAPI schema](https://cayu.dev/docs/openapi.json) · [Authentication and tenancy](https://cayu.dev/docs/operate/authentication/).
## GET /api/recovery/startup
Startup Recovery Status
Latest process-local interruption sweep; authenticated server operator view.
Operation ID: `startup_recovery_status_api_recovery_startup_get`.
### Responses
```json
{
"200": {
"description": "Successful Response",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/StartupRecoveryResult"
}
}
}
}
}
```
Model declarations: [StartupRecoveryResult](https://cayu.dev/docs/reference/http/schemas/models-10/#startuprecoveryresult).
# HTTP: resume
Default control-plane operations. [Complete OpenAPI schema](https://cayu.dev/docs/openapi.json) · [Authentication and tenancy](https://cayu.dev/docs/operate/authentication/).
## POST /api/resume
Resume Agent
Operation ID: `resume_agent_api_resume_post`.
| Parameter | Location | Required | Schema |
| --- | --- | --- | --- |
| `Cayu-Mutation-ID` | header | False | `{"anyOf":[{"type":"string","minLength":1,"maxLength":128,"pattern":"^[A-Za-z0-9][A-Za-z0-9._~-]*$"},{"type":"null"}],"description":"Client-generated mutation identity used to correlate an ambiguous SSE reconnect with its durable server acceptance event. Send the same value on the initial mutation and every Last-Event-ID replay request; it is a replay correlation key, not permission to repeat the POST.","title":"Cayu-Mutation-Id"}` |
### Request body
```json
{
"required": true,
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ResumeBody"
}
}
}
}
```
Model declarations: [ResumeBody](https://cayu.dev/docs/reference/http/schemas/models-7/#resumebody).
### Responses
```json
{
"200": {
"description": "Server-Sent Events stream. Each runtime event is emitted as an SSE frame whose `data:` value is a JSON SseEventEnvelope. A classified terminal runtime or observer condition is emitted as `event: error` with a SseErrorEnvelope payload.",
"content": {
"text/event-stream": {
"schema": {
"type": "string",
"description": "SSE stream. Runtime `data:` frames contain SseEventEnvelope JSON; `event: error` frames contain SseErrorEnvelope JSON."
}
}
}
},
"404": {
"description": "The replay session or mutation target does not exist.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ApiErrorResponse"
}
}
}
},
"409": {
"description": "The replay event marker is unknown or the mutation conflicts with the current session state.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ApiErrorResponse"
}
}
}
},
"500": {
"description": "The mutation could not open an accepted durable stream.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ApiErrorResponse"
}
}
}
},
"413": {
"description": "The control-plane request exceeds its encoded byte limit.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ApiErrorResponse"
}
}
}
},
"422": {
"description": "Validation Error",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/HTTPValidationError"
}
}
}
}
}
```
Model declarations: [ApiErrorResponse](https://cayu.dev/docs/reference/http/schemas/models-1/#apierrorresponse), [HTTPValidationError](https://cayu.dev/docs/reference/http/schemas/models-5/#httpvalidationerror).
# HTTP: run
Default control-plane operations. [Complete OpenAPI schema](https://cayu.dev/docs/openapi.json) · [Authentication and tenancy](https://cayu.dev/docs/operate/authentication/).
## POST /api/run
Run Agent
Operation ID: `run_agent_api_run_post`.
| Parameter | Location | Required | Schema |
| --- | --- | --- | --- |
| `Cayu-Mutation-ID` | header | False | `{"anyOf":[{"type":"string","minLength":1,"maxLength":128,"pattern":"^[A-Za-z0-9][A-Za-z0-9._~-]*$"},{"type":"null"}],"description":"Client-generated mutation identity used to correlate an ambiguous SSE reconnect with its durable server acceptance event. Send the same value on the initial mutation and every Last-Event-ID replay request; it is a replay correlation key, not permission to repeat the POST.","title":"Cayu-Mutation-Id"}` |
### Request body
```json
{
"required": true,
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/RunBody"
}
}
}
}
```
Model declarations: [RunBody](https://cayu.dev/docs/reference/http/schemas/models-8/#runbody).
### Responses
```json
{
"200": {
"description": "Server-Sent Events stream. Each runtime event is emitted as an SSE frame whose `data:` value is a JSON SseEventEnvelope. A classified terminal runtime or observer condition is emitted as `event: error` with a SseErrorEnvelope payload.",
"content": {
"text/event-stream": {
"schema": {
"type": "string",
"description": "SSE stream. Runtime `data:` frames contain SseEventEnvelope JSON; `event: error` frames contain SseErrorEnvelope JSON."
}
}
}
},
"404": {
"description": "The replay session or mutation target does not exist.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ApiErrorResponse"
}
}
}
},
"409": {
"description": "The replay event marker is unknown or the mutation conflicts with the current session state.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ApiErrorResponse"
}
}
}
},
"500": {
"description": "The mutation could not open an accepted durable stream.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ApiErrorResponse"
}
}
}
},
"413": {
"description": "The control-plane request exceeds its encoded byte limit.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ApiErrorResponse"
}
}
}
},
"400": {
"description": "The authenticated caller identity cannot be represented as durable invocation provenance.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ApiErrorResponse"
}
}
}
},
"422": {
"description": "Validation Error",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/HTTPValidationError"
}
}
}
}
}
```
Model declarations: [ApiErrorResponse](https://cayu.dev/docs/reference/http/schemas/models-1/#apierrorresponse), [HTTPValidationError](https://cayu.dev/docs/reference/http/schemas/models-5/#httpvalidationerror).
# HTTP: sessions
Default control-plane operations. [Complete OpenAPI schema](https://cayu.dev/docs/openapi.json) · [Authentication and tenancy](https://cayu.dev/docs/operate/authentication/).
## POST /api/sessions/{session_id}/compact
Compact Session
Operation ID: `compact_session_api_sessions__session_id__compact_post`.
| Parameter | Location | Required | Schema |
| --- | --- | --- | --- |
| `session_id` | path | True | `{"type":"string","minLength":1,"pattern":"^[^\\x00]+$","title":"Session Id"}` |
| `Cayu-Mutation-ID` | header | False | `{"anyOf":[{"type":"string","minLength":1,"maxLength":128,"pattern":"^[A-Za-z0-9][A-Za-z0-9._~-]*$"},{"type":"null"}],"description":"Client-generated mutation identity used to correlate an ambiguous SSE reconnect with its durable server acceptance event. Send the same value on the initial mutation and every Last-Event-ID replay request; it is a replay correlation key, not permission to repeat the POST.","title":"Cayu-Mutation-Id"}` |
### Request body
```json
{
"required": true,
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/CompactSessionBody"
}
}
}
}
```
Model declarations: [CompactSessionBody](https://cayu.dev/docs/reference/http/schemas/models-4/#compactsessionbody).
### Responses
```json
{
"200": {
"description": "Server-Sent Events stream. Each runtime event is emitted as an SSE frame whose `data:` value is a JSON SseEventEnvelope. A classified terminal runtime or observer condition is emitted as `event: error` with a SseErrorEnvelope payload.",
"content": {
"text/event-stream": {
"schema": {
"type": "string",
"description": "SSE stream. Runtime `data:` frames contain SseEventEnvelope JSON; `event: error` frames contain SseErrorEnvelope JSON."
}
}
}
},
"404": {
"description": "The replay session or mutation target does not exist.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ApiErrorResponse"
}
}
}
},
"409": {
"description": "The replay event marker is unknown or the mutation conflicts with the current session state.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ApiErrorResponse"
}
}
}
},
"500": {
"description": "The mutation could not open an accepted durable stream.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ApiErrorResponse"
}
}
}
},
"422": {
"description": "Validation Error",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/HTTPValidationError"
}
}
}
}
}
```
Model declarations: [ApiErrorResponse](https://cayu.dev/docs/reference/http/schemas/models-1/#apierrorresponse), [HTTPValidationError](https://cayu.dev/docs/reference/http/schemas/models-5/#httpvalidationerror).
## GET /api/sessions/{session_id}/human-review
Inspect Human Review
Operation ID: `inspect_human_review_api_sessions__session_id__human_review_get`.
| Parameter | Location | Required | Schema |
| --- | --- | --- | --- |
| `session_id` | path | True | `{"type":"string","title":"Session Id"}` |
| `purpose` | query | True | `{"type":"string","minLength":1,"maxLength":128,"title":"Purpose"}` |
### Responses
```json
{
"200": {
"description": "Successful Response",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/HumanReviewView"
}
}
}
},
"422": {
"description": "Validation Error",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/HTTPValidationError"
}
}
}
}
}
```
Model declarations: [HTTPValidationError](https://cayu.dev/docs/reference/http/schemas/models-5/#httpvalidationerror), [HumanReviewView](https://cayu.dev/docs/reference/http/schemas/models-5/#humanreviewview).
## GET /api/sessions
List Sessions
Operation ID: `list_sessions_api_sessions_get`.
| Parameter | Location | Required | Schema |
| --- | --- | --- | --- |
| `limit` | query | False | `{"type":"integer","maximum":1000,"minimum":1,"default":50,"title":"Limit"}` |
| `offset` | query | False | `{"type":"integer","minimum":0,"default":0,"title":"Offset"}` |
| `cursor` | query | False | `{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Cursor"}` |
| `q` | query | False | `{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Q"}` |
| `status` | query | False | `{"anyOf":[{"$ref":"#/components/schemas/SessionStatus"},{"type":"null"}],"title":"Status"}` |
| `debug_state` | query | False | `{"anyOf":[{"$ref":"#/components/schemas/SessionDebugState"},{"type":"null"}],"title":"Debug State"}` |
| `agent_name` | query | False | `{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Agent Name"}` |
| `provider_name` | query | False | `{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Provider Name"}` |
| `model` | query | False | `{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Model"}` |
| `environment_name` | query | False | `{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Environment Name"}` |
| `parent_session_id` | query | False | `{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Parent Session Id"}` |
| `causal_budget_id` | query | False | `{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Causal Budget Id"}` |
| `order_by` | query | False | `{"$ref":"#/components/schemas/SessionOrder","default":"updated_at_desc"}` |
| `label` | query | False | `{"anyOf":[{"type":"array","items":{"type":"string"}},{"type":"null"}],"title":"Label"}` |
| `label_selector` | query | False | `{"anyOf":[{"type":"array","items":{"type":"string"}},{"type":"null"}],"title":"Label Selector"}` |
| `include` | query | False | `{"anyOf":[{"type":"array","items":{"const":"usage","type":"string"}},{"type":"null"}],"title":"Include"}` |
### Responses
```json
{
"200": {
"description": "Successful Response",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ListSessionsResponse"
}
}
}
},
"422": {
"description": "Validation Error",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/HTTPValidationError"
}
}
}
}
}
```
Model declarations: [HTTPValidationError](https://cayu.dev/docs/reference/http/schemas/models-5/#httpvalidationerror), [ListSessionsResponse](https://cayu.dev/docs/reference/http/schemas/models-6/#listsessionsresponse).
## POST /api/sessions/{session_id}/topology
Get Session Topology
Operation ID: `get_session_topology_api_sessions__session_id__topology_post`.
| Parameter | Location | Required | Schema |
| --- | --- | --- | --- |
| `session_id` | path | True | `{"type":"string","minLength":1,"title":"Session Id"}` |
### Request body
```json
{
"required": true,
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/SessionTopologyRequest"
}
}
}
}
```
Model declarations: [SessionTopologyRequest](https://cayu.dev/docs/reference/http/schemas/models-9/#sessiontopologyrequest).
### Responses
```json
{
"200": {
"description": "Successful Response",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/SessionTopologyResponse"
}
}
}
},
"404": {
"description": "The focus session or one requested expanded session/task parent does not exist.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ApiErrorResponse"
}
}
}
},
"409": {
"description": "Durable session/task lineage is inconsistent, or continuation authority cannot cross the configured redaction boundary.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ApiErrorResponse"
}
}
}
},
"413": {
"description": "The request bytes, session/task ancestry, or serialized response exceed a safety bound.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ApiErrorResponse"
}
}
}
},
"501": {
"description": "The configured session store does not implement topology reads.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ApiErrorResponse"
}
}
}
},
"422": {
"description": "Validation Error",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/HTTPValidationError"
}
}
}
}
}
```
Model declarations: [ApiErrorResponse](https://cayu.dev/docs/reference/http/schemas/models-1/#apierrorresponse), [HTTPValidationError](https://cayu.dev/docs/reference/http/schemas/models-5/#httpvalidationerror), [SessionTopologyResponse](https://cayu.dev/docs/reference/http/schemas/models-9/#sessiontopologyresponse).
## POST /api/sessions/summary
Get Sessions Summary
Operation ID: `get_sessions_summary_api_sessions_summary_post`.
| Parameter | Location | Required | Schema |
| --- | --- | --- | --- |
| `limit` | query | False | `{"type":"integer","maximum":1000,"minimum":1,"default":1000,"title":"Limit"}` |
| `offset` | query | False | `{"type":"integer","minimum":0,"default":0,"title":"Offset"}` |
| `cursor` | query | False | `{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Cursor"}` |
| `q` | query | False | `{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Q"}` |
| `status` | query | False | `{"anyOf":[{"$ref":"#/components/schemas/SessionStatus"},{"type":"null"}],"title":"Status"}` |
| `debug_state` | query | False | `{"anyOf":[{"$ref":"#/components/schemas/SessionDebugState"},{"type":"null"}],"title":"Debug State"}` |
| `agent_name` | query | False | `{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Agent Name"}` |
| `provider_name` | query | False | `{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Provider Name"}` |
| `model` | query | False | `{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Model"}` |
| `environment_name` | query | False | `{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Environment Name"}` |
| `parent_session_id` | query | False | `{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Parent Session Id"}` |
| `causal_budget_id` | query | False | `{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Causal Budget Id"}` |
| `order_by` | query | False | `{"$ref":"#/components/schemas/SessionOrder","default":"updated_at_desc"}` |
| `label` | query | False | `{"anyOf":[{"type":"array","items":{"type":"string"}},{"type":"null"}],"title":"Label"}` |
| `label_selector` | query | False | `{"anyOf":[{"type":"array","items":{"type":"string"}},{"type":"null"}],"title":"Label Selector"}` |
### Request body
```json
{
"content": {
"application/json": {
"schema": {
"anyOf": [
{
"$ref": "#/components/schemas/SessionsSummaryBody"
},
{
"type": "null"
}
],
"title": "Body"
}
}
}
}
```
Model declarations: [SessionsSummaryBody](https://cayu.dev/docs/reference/http/schemas/models-10/#sessionssummarybody).
### Responses
```json
{
"200": {
"description": "Successful Response",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/SessionsSummaryResponse"
}
}
}
},
"422": {
"description": "Validation Error",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/HTTPValidationError"
}
}
}
}
}
```
Model declarations: [HTTPValidationError](https://cayu.dev/docs/reference/http/schemas/models-5/#httpvalidationerror), [SessionsSummaryResponse](https://cayu.dev/docs/reference/http/schemas/models-10/#sessionssummaryresponse).
## GET /api/sessions/{session_id}/usage
Get Session Usage
Operation ID: `get_session_usage_api_sessions__session_id__usage_get`.
| Parameter | Location | Required | Schema |
| --- | --- | --- | --- |
| `session_id` | path | True | `{"type":"string","minLength":1,"title":"Session Id"}` |
| `if-none-match` | header | False | `{"anyOf":[{"type":"string"},{"type":"null"}],"title":"If-None-Match"}` |
### Responses
```json
{
"200": {
"description": "Successful Response",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/SessionUsageSummary"
}
}
}
},
"304": {
"description": "The summary still matches `If-None-Match`."
},
"422": {
"description": "Validation Error",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/HTTPValidationError"
}
}
}
}
}
```
Model declarations: [HTTPValidationError](https://cayu.dev/docs/reference/http/schemas/models-5/#httpvalidationerror), [SessionUsageSummary](https://cayu.dev/docs/reference/http/schemas/models-10/#sessionusagesummary).
## POST /api/sessions/{session_id}/cost
Estimate Session Cost
Operation ID: `estimate_session_cost_api_sessions__session_id__cost_post`.
| Parameter | Location | Required | Schema |
| --- | --- | --- | --- |
| `session_id` | path | True | `{"type":"string","minLength":1,"title":"Session Id"}` |
### Request body
```json
{
"required": true,
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/SessionCostBody"
}
}
}
}
```
Model declarations: [SessionCostBody](https://cayu.dev/docs/reference/http/schemas/models-8/#sessioncostbody).
### Responses
```json
{
"200": {
"description": "Successful Response",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/SessionCostSummary"
}
}
}
},
"422": {
"description": "Validation Error",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/HTTPValidationError"
}
}
}
}
}
```
Model declarations: [HTTPValidationError](https://cayu.dev/docs/reference/http/schemas/models-5/#httpvalidationerror), [SessionCostSummary](https://cayu.dev/docs/reference/http/schemas/models-8/#sessioncostsummary).
## GET /api/sessions/{session_id}/state
Get Session State
Return bounded lifecycle state. The response carries a weak `ETag`; a matching `If-None-Match` returns `304` without a body.
Operation ID: `get_session_state_api_sessions__session_id__state_get`.
| Parameter | Location | Required | Schema |
| --- | --- | --- | --- |
| `session_id` | path | True | `{"type":"string","minLength":1,"title":"Session Id"}` |
### Responses
```json
{
"200": {
"description": "Successful Response",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/SessionStateResponse"
}
}
}
},
"304": {
"description": "The session state is unchanged from the `If-None-Match` entity tag."
},
"422": {
"description": "Validation Error",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/HTTPValidationError"
}
}
}
}
}
```
Model declarations: [HTTPValidationError](https://cayu.dev/docs/reference/http/schemas/models-5/#httpvalidationerror), [SessionStateResponse](https://cayu.dev/docs/reference/http/schemas/models-9/#sessionstateresponse).
## GET /api/sessions/{session_id}/tool-view
Get Session Tool View
Inspect one tenant-bound current view without callable authority.
Operation ID: `get_session_tool_view_api_sessions__session_id__tool_view_get`.
| Parameter | Location | Required | Schema |
| --- | --- | --- | --- |
| `session_id` | path | True | `{"type":"string","minLength":1,"title":"Session Id"}` |
| `limit` | query | False | `{"type":"integer","maximum":256,"minimum":1,"default":256,"title":"Limit"}` |
### Responses
```json
{
"200": {
"description": "A bounded, content-minimized current discovery view.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ToolDiscoveryViewInspection"
}
}
},
"headers": {
"Cache-Control": {
"description": "Prevents authenticated discovery metadata from being cached.",
"schema": {
"type": "string",
"enum": [
"private, no-store"
]
}
}
}
},
"401": {
"description": "The request has no valid authenticated identity.",
"headers": {
"Cache-Control": {
"description": "Prevents authenticated discovery metadata from being cached.",
"schema": {
"type": "string",
"enum": [
"private, no-store"
]
}
}
},
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ApiErrorResponse"
}
}
}
},
"403": {
"description": "The authenticated identity has no verified tenant.",
"headers": {
"Cache-Control": {
"description": "Prevents authenticated discovery metadata from being cached.",
"schema": {
"type": "string",
"enum": [
"private, no-store"
]
}
}
},
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ApiErrorResponse"
}
}
}
},
"404": {
"description": "The session/view does not exist or belongs to a different verified tenant.",
"headers": {
"Cache-Control": {
"description": "Prevents authenticated discovery metadata from being cached.",
"schema": {
"type": "string",
"enum": [
"private, no-store"
]
}
}
},
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ApiErrorResponse"
}
}
}
},
"409": {
"description": "The durable discovery view is inconsistent with current authority.",
"headers": {
"Cache-Control": {
"description": "Prevents authenticated discovery metadata from being cached.",
"schema": {
"type": "string",
"enum": [
"private, no-store"
]
}
}
},
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ApiErrorResponse"
}
}
}
},
"422": {
"description": "The bounded tool-view inspection request is invalid.",
"headers": {
"Cache-Control": {
"description": "Prevents authenticated discovery metadata from being cached.",
"schema": {
"type": "string",
"enum": [
"private, no-store"
]
}
}
},
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ApiErrorResponse"
}
}
}
},
"500": {
"description": "Tool-view inspection failed before a safe view could be returned.",
"headers": {
"Cache-Control": {
"description": "Prevents authenticated discovery metadata from being cached.",
"schema": {
"type": "string",
"enum": [
"private, no-store"
]
}
}
},
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ApiErrorResponse"
}
}
}
}
}
```
Model declarations: [ApiErrorResponse](https://cayu.dev/docs/reference/http/schemas/models-1/#apierrorresponse), [ToolDiscoveryViewInspection](https://cayu.dev/docs/reference/http/schemas/models-11/#tooldiscoveryviewinspection).
## GET /api/sessions/{session_id}/summary
Get Session Summary
Operation ID: `get_session_summary_api_sessions__session_id__summary_get`.
| Parameter | Location | Required | Schema |
| --- | --- | --- | --- |
| `session_id` | path | True | `{"type":"string","minLength":1,"title":"Session Id"}` |
### Responses
```json
{
"200": {
"description": "Successful Response",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/SessionSummaryResponse"
}
}
}
},
"422": {
"description": "Validation Error",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/HTTPValidationError"
}
}
}
}
}
```
Model declarations: [HTTPValidationError](https://cayu.dev/docs/reference/http/schemas/models-5/#httpvalidationerror), [SessionSummaryResponse](https://cayu.dev/docs/reference/http/schemas/models-9/#sessionsummaryresponse).
## GET /api/sessions/{session_id}/interactions
List Session Interactions
Operation ID: `list_session_interactions_api_sessions__session_id__interactions_get`.
| Parameter | Location | Required | Schema |
| --- | --- | --- | --- |
| `session_id` | path | True | `{"type":"string","minLength":1,"title":"Session Id"}` |
| `before_sequence` | query | False | `{"anyOf":[{"type":"integer","minimum":1},{"type":"null"}],"title":"Before Sequence"}` |
| `limit` | query | False | `{"type":"integer","maximum":100,"minimum":1,"default":50,"title":"Limit"}` |
### Responses
```json
{
"200": {
"description": "Successful Response",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ListSessionInteractionsResponse"
}
}
}
},
"422": {
"description": "Validation Error",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/HTTPValidationError"
}
}
}
}
}
```
Model declarations: [HTTPValidationError](https://cayu.dev/docs/reference/http/schemas/models-5/#httpvalidationerror), [ListSessionInteractionsResponse](https://cayu.dev/docs/reference/http/schemas/models-6/#listsessioninteractionsresponse).
## GET /api/sessions/{session_id}/interactions/{interaction_id}
Get Session Interaction
Operation ID: `get_session_interaction_api_sessions__session_id__interactions__interaction_id__get`.
| Parameter | Location | Required | Schema |
| --- | --- | --- | --- |
| `session_id` | path | True | `{"type":"string","minLength":1,"title":"Session Id"}` |
| `interaction_id` | path | True | `{"type":"string","minLength":1,"title":"Interaction Id"}` |
### Responses
```json
{
"200": {
"description": "Successful Response",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ApiInteractionSummary"
}
}
}
},
"422": {
"description": "Validation Error",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/HTTPValidationError"
}
}
}
}
}
```
Model declarations: [ApiInteractionSummary](https://cayu.dev/docs/reference/http/schemas/models-1/#apiinteractionsummary), [HTTPValidationError](https://cayu.dev/docs/reference/http/schemas/models-5/#httpvalidationerror).
## GET /api/sessions/{session_id}/events
List Session Events
Operation ID: `list_session_events_api_sessions__session_id__events_get`.
| Parameter | Location | Required | Schema |
| --- | --- | --- | --- |
| `session_id` | path | True | `{"type":"string","minLength":1,"title":"Session Id"}` |
| `event_id` | query | False | `{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Return the event with this exact session-scoped event ID, if it exists.","title":"Event Id"}` |
| `event_type` | query | False | `{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Event Type"}` |
| `interaction_id` | query | False | `{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Return only events attributed to this interaction.","title":"Interaction Id"}` |
| `exclude_event_type` | query | False | `{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Exclude one event type before applying pagination.","title":"Exclude Event Type"}` |
| `tool_name` | query | False | `{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Tool Name"}` |
| `agent_name` | query | False | `{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Agent Name"}` |
| `environment_name` | query | False | `{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Environment Name"}` |
| `workflow_name` | query | False | `{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Workflow Name"}` |
| `after_sequence` | query | False | `{"anyOf":[{"type":"integer","minimum":0},{"type":"null"}],"description":"Return only events with a greater durable sequence.","title":"After Sequence"}` |
| `before_sequence` | query | False | `{"anyOf":[{"type":"integer","minimum":1},{"type":"null"}],"description":"Return only events with a smaller durable sequence.","title":"Before Sequence"}` |
| `order_by` | query | False | `{"$ref":"#/components/schemas/EventOrder","description":"Return events in durable sequence order.","default":"sequence_asc"}` |
| `limit` | query | False | `{"type":"integer","maximum":1000,"minimum":1,"default":100,"title":"Limit"}` |
| `wait_seconds` | query | False | `{"anyOf":[{"type":"number","maximum":25.0,"minimum":0},{"type":"null"}],"description":"Long-poll fallback for clients that cannot hold the follow stream open. Wait up to this many seconds for a matching event after `after_sequence` while the session is pending, running, or interrupting, then return the page (possibly empty).","title":"Wait Seconds"}` |
### Responses
```json
{
"200": {
"description": "Successful Response",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ListSessionEventsResponse"
}
}
}
},
"422": {
"description": "Validation Error",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/HTTPValidationError"
}
}
}
}
}
```
Model declarations: [HTTPValidationError](https://cayu.dev/docs/reference/http/schemas/models-5/#httpvalidationerror), [ListSessionEventsResponse](https://cayu.dev/docs/reference/http/schemas/models-6/#listsessioneventsresponse).
## GET /api/sessions/{session_id}/events/stream
Follow Session Events
Follow one session's durable events over Server-Sent Events without admitting or re-running any mutation. Start after `after_sequence`, or resume after the event named by the standard `Last-Event-ID` header, which takes precedence so a browser EventSource reconnects natively. Filters match `GET /sessions/{session_id}/events`. The stream sends SSE comment heartbeats and, after the session's terminal event, one `event: end` frame before closing; an already-terminal session drains and closes immediately. Concurrent streams are capped per caller and per session and return `429` with `Retry-After` when exceeded.
Operation ID: `follow_session_events_api_sessions__session_id__events_stream_get`.
| Parameter | Location | Required | Schema |
| --- | --- | --- | --- |
| `session_id` | path | True | `{"type":"string","minLength":1,"title":"Session Id"}` |
| `after_sequence` | query | False | `{"anyOf":[{"type":"integer","minimum":0},{"type":"null"}],"description":"Stream only events with a greater durable sequence. Ignored when a `Last-Event-ID` header is present.","title":"After Sequence"}` |
| `event_type` | query | False | `{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Send only events of this type.","title":"Event Type"}` |
| `exclude_event_type` | query | False | `{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Do not send events of this type.","title":"Exclude Event Type"}` |
| `interaction_id` | query | False | `{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Send only events attributed to this interaction.","title":"Interaction Id"}` |
### Responses
```json
{
"200": {
"description": "Read-only Server-Sent Events stream of one session's durable events. Each event is an SSE frame with an `id:` marker and a JSON SseEventEnvelope `data:` value. Heartbeats are SSE comment lines. After the session's terminal event the stream sends `event: end` with a SseSessionFollowEndEnvelope and closes. A classified observer condition is emitted as `event: error` with a SseErrorEnvelope payload.",
"content": {
"text/event-stream": {
"schema": {
"type": "string",
"description": "SSE stream. Event `data:` frames contain SseEventEnvelope JSON; `event: end` frames contain SseSessionFollowEndEnvelope JSON; `event: error` frames contain SseErrorEnvelope JSON."
}
}
}
},
"404": {
"description": "The session does not exist.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ApiErrorResponse"
}
}
}
},
"409": {
"description": "The Last-Event-ID event marker is unknown in this session.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ApiErrorResponse"
}
}
}
},
"429": {
"description": "The caller or session already holds the configured maximum number of follow streams. Retry after the `Retry-After` header.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ApiErrorResponse"
}
}
}
},
"422": {
"description": "Validation Error",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/HTTPValidationError"
}
}
}
}
}
```
Model declarations: [ApiErrorResponse](https://cayu.dev/docs/reference/http/schemas/models-1/#apierrorresponse), [HTTPValidationError](https://cayu.dev/docs/reference/http/schemas/models-5/#httpvalidationerror).
## GET /api/sessions/{session_id}/transcript
Get Session Transcript
Operation ID: `get_session_transcript_api_sessions__session_id__transcript_get`.
| Parameter | Location | Required | Schema |
| --- | --- | --- | --- |
| `session_id` | path | True | `{"type":"string","minLength":1,"title":"Session Id"}` |
| `role` | query | False | `{"anyOf":[{"$ref":"#/components/schemas/MessageRole"},{"type":"null"}],"title":"Role"}` |
| `interaction_id` | query | False | `{"anyOf":[{"type":"string","minLength":1},{"type":"null"}],"description":"Return only transcript records attributed to this interaction.","title":"Interaction Id"}` |
| `offset` | query | False | `{"type":"integer","minimum":0,"default":0,"title":"Offset"}` |
| `limit` | query | False | `{"type":"integer","maximum":1000,"minimum":1,"default":100,"title":"Limit"}` |
| `include_thinking` | query | False | `{"type":"boolean","default":true,"title":"Include Thinking"}` |
### Responses
```json
{
"200": {
"description": "Successful Response",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/SessionTranscriptResponse"
}
}
}
},
"422": {
"description": "Validation Error",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/HTTPValidationError"
}
}
}
}
}
```
Model declarations: [HTTPValidationError](https://cayu.dev/docs/reference/http/schemas/models-5/#httpvalidationerror), [SessionTranscriptResponse](https://cayu.dev/docs/reference/http/schemas/models-9/#sessiontranscriptresponse).
## GET /api/sessions/{session_id}
Get Session
Operation ID: `get_session_api_sessions__session_id__get`.
| Parameter | Location | Required | Schema |
| --- | --- | --- | --- |
| `session_id` | path | True | `{"type":"string","minLength":1,"title":"Session Id"}` |
### Responses
```json
{
"200": {
"description": "Successful Response",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ApiSessionDetail"
}
}
}
},
"422": {
"description": "Validation Error",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/HTTPValidationError"
}
}
}
}
}
```
Model declarations: [ApiSessionDetail](https://cayu.dev/docs/reference/http/schemas/models-2/#apisessiondetail), [HTTPValidationError](https://cayu.dev/docs/reference/http/schemas/models-5/#httpvalidationerror).
## DELETE /api/sessions/{session_id}
Delete Session
Operation ID: `delete_session_api_sessions__session_id__delete`.
| Parameter | Location | Required | Schema |
| --- | --- | --- | --- |
| `session_id` | path | True | `{"type":"string","minLength":1,"title":"Session Id"}` |
### Responses
```json
{
"204": {
"description": "Successful Response"
},
"422": {
"description": "Validation Error",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/HTTPValidationError"
}
}
}
}
}
```
Model declarations: [HTTPValidationError](https://cayu.dev/docs/reference/http/schemas/models-5/#httpvalidationerror).
## PATCH /api/sessions/{session_id}/labels
Update Session Labels
Operation ID: `update_session_labels_api_sessions__session_id__labels_patch`.
| Parameter | Location | Required | Schema |
| --- | --- | --- | --- |
| `session_id` | path | True | `{"type":"string","minLength":1,"title":"Session Id"}` |
### Request body
```json
{
"required": true,
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/UpdateSessionLabelsBody"
}
}
}
}
```
Model declarations: [UpdateSessionLabelsBody](https://cayu.dev/docs/reference/http/schemas/models-12/#updatesessionlabelsbody).
### Responses
```json
{
"200": {
"description": "Successful Response",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ApiSession"
}
}
}
},
"422": {
"description": "Validation Error",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/HTTPValidationError"
}
}
}
}
}
```
Model declarations: [ApiSession](https://cayu.dev/docs/reference/http/schemas/models-2/#apisession), [HTTPValidationError](https://cayu.dev/docs/reference/http/schemas/models-5/#httpvalidationerror).
## PATCH /api/sessions/{session_id}/metadata
Update Session Metadata
Operation ID: `update_session_metadata_api_sessions__session_id__metadata_patch`.
| Parameter | Location | Required | Schema |
| --- | --- | --- | --- |
| `session_id` | path | True | `{"type":"string","minLength":1,"title":"Session Id"}` |
### Request body
```json
{
"required": true,
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/UpdateSessionMetadataBody"
}
}
}
}
```
Model declarations: [UpdateSessionMetadataBody](https://cayu.dev/docs/reference/http/schemas/models-12/#updatesessionmetadatabody).
### Responses
```json
{
"200": {
"description": "Successful Response",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ApiSession"
}
}
}
},
"422": {
"description": "Validation Error",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/HTTPValidationError"
}
}
}
}
}
```
Model declarations: [ApiSession](https://cayu.dev/docs/reference/http/schemas/models-2/#apisession), [HTTPValidationError](https://cayu.dev/docs/reference/http/schemas/models-5/#httpvalidationerror).
## POST /api/sessions/{session_id}/interrupt
Interrupt Session
Operation ID: `interrupt_session_api_sessions__session_id__interrupt_post`.
| Parameter | Location | Required | Schema |
| --- | --- | --- | --- |
| `session_id` | path | True | `{"type":"string","minLength":1,"title":"Session Id"}` |
| `Cayu-Mutation-ID` | header | False | `{"anyOf":[{"type":"string","minLength":1,"maxLength":128,"pattern":"^[A-Za-z0-9][A-Za-z0-9._~-]*$"},{"type":"null"}],"description":"Client-generated mutation identity used to correlate an ambiguous SSE reconnect with its durable server acceptance event. Send the same value on the initial mutation and every Last-Event-ID replay request; it is a replay correlation key, not permission to repeat the POST.","title":"Cayu-Mutation-Id"}` |
### Request body
```json
{
"content": {
"application/json": {
"schema": {
"anyOf": [
{
"$ref": "#/components/schemas/InterruptSessionBody"
},
{
"type": "null"
}
],
"title": "Body"
}
}
}
}
```
Model declarations: [InterruptSessionBody](https://cayu.dev/docs/reference/http/schemas/models-5/#interruptsessionbody).
### Responses
```json
{
"200": {
"description": "Server-Sent Events stream. Each runtime event is emitted as an SSE frame whose `data:` value is a JSON SseEventEnvelope. A classified terminal runtime or observer condition is emitted as `event: error` with a SseErrorEnvelope payload.",
"content": {
"text/event-stream": {
"schema": {
"type": "string",
"description": "SSE stream. Runtime `data:` frames contain SseEventEnvelope JSON; `event: error` frames contain SseErrorEnvelope JSON."
}
}
}
},
"404": {
"description": "The replay session or mutation target does not exist.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ApiErrorResponse"
}
}
}
},
"409": {
"description": "The replay event marker is unknown or the mutation conflicts with the current session state.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ApiErrorResponse"
}
}
}
},
"500": {
"description": "The mutation could not open an accepted durable stream.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ApiErrorResponse"
}
}
}
},
"413": {
"description": "The control-plane request exceeds its encoded byte limit.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ApiErrorResponse"
}
}
}
},
"422": {
"description": "Validation Error",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/HTTPValidationError"
}
}
}
}
}
```
Model declarations: [ApiErrorResponse](https://cayu.dev/docs/reference/http/schemas/models-1/#apierrorresponse), [HTTPValidationError](https://cayu.dev/docs/reference/http/schemas/models-5/#httpvalidationerror).
## POST /api/sessions/{session_id}/messages
Enqueue Session Message
Operation ID: `enqueue_session_message_api_sessions__session_id__messages_post`.
| Parameter | Location | Required | Schema |
| --- | --- | --- | --- |
| `session_id` | path | True | `{"type":"string","minLength":1,"title":"Session Id"}` |
| `Cayu-Mutation-ID` | header | False | `{"anyOf":[{"type":"string","minLength":1,"maxLength":128,"pattern":"^[A-Za-z0-9][A-Za-z0-9._~-]*$"},{"type":"null"}],"description":"Client-generated mutation identity used to correlate an ambiguous SSE reconnect with its durable server acceptance event. Send the same value on the initial mutation and every Last-Event-ID replay request; it is a replay correlation key, not permission to repeat the POST.","title":"Cayu-Mutation-Id"}` |
### Request body
```json
{
"required": true,
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/EnqueueSessionMessageBody"
}
}
}
}
```
Model declarations: [EnqueueSessionMessageBody](https://cayu.dev/docs/reference/http/schemas/models-4/#enqueuesessionmessagebody).
### Responses
```json
{
"200": {
"description": "Server-Sent Events stream. Each runtime event is emitted as an SSE frame whose `data:` value is a JSON SseEventEnvelope. A classified terminal runtime or observer condition is emitted as `event: error` with a SseErrorEnvelope payload.",
"content": {
"text/event-stream": {
"schema": {
"type": "string",
"description": "SSE stream. Runtime `data:` frames contain SseEventEnvelope JSON; `event: error` frames contain SseErrorEnvelope JSON."
}
}
}
},
"400": {
"description": "The request attempts to override authenticated actor identity.",
"headers": {
"Cache-Control": {
"schema": {
"type": "string",
"const": "private, no-store"
}
}
},
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ApiErrorResponse"
}
}
}
},
"401": {
"description": "Authentication is required.",
"headers": {
"Cache-Control": {
"schema": {
"type": "string",
"const": "private, no-store"
}
}
},
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ApiErrorResponse"
}
}
}
},
"403": {
"description": "Session-message access is not authorized.",
"headers": {
"Cache-Control": {
"schema": {
"type": "string",
"const": "private, no-store"
}
}
},
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ApiErrorResponse"
}
}
}
},
"409": {
"description": "The request conflicts with exact durable authority or terminal state.",
"headers": {
"Cache-Control": {
"schema": {
"type": "string",
"const": "private, no-store"
}
}
},
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ApiErrorResponse"
}
}
}
},
"413": {
"description": "The request exceeds the private request byte limit.",
"headers": {
"Cache-Control": {
"schema": {
"type": "string",
"const": "private, no-store"
}
}
},
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ApiErrorResponse"
}
}
}
},
"422": {
"description": "Invalid session-message request; rejected input is not reflected.",
"headers": {
"Cache-Control": {
"schema": {
"type": "string",
"const": "private, no-store"
}
}
},
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ApiErrorResponse"
}
}
}
},
"500": {
"description": "The session-message operation failed.",
"headers": {
"Cache-Control": {
"schema": {
"type": "string",
"const": "private, no-store"
}
}
},
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ApiErrorResponse"
}
}
}
},
"503": {
"description": "The store does not support this session-message operation.",
"headers": {
"Cache-Control": {
"schema": {
"type": "string",
"const": "private, no-store"
}
}
},
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ApiErrorResponse"
}
}
}
},
"404": {
"description": "The replay session or mutation target does not exist.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ApiErrorResponse"
}
}
}
}
}
```
Model declarations: [ApiErrorResponse](https://cayu.dev/docs/reference/http/schemas/models-1/#apierrorresponse).
## GET /api/sessions/{session_id}/messages
Inspect Session Messages
Operation ID: `inspect_session_messages_api_sessions__session_id__messages_get`.
| Parameter | Location | Required | Schema |
| --- | --- | --- | --- |
| `session_id` | path | True | `{"type":"string","title":"Session Id"}` |
| `limit` | query | False | `{"type":"integer","maximum":100,"minimum":1,"default":50,"title":"Limit"}` |
| `cursor_session_instance_id` | query | False | `{"anyOf":[{"type":"string","minLength":1,"maxLength":512},{"type":"null"}],"title":"Cursor Session Instance Id"}` |
| `cursor_through_ordering_key` | query | False | `{"anyOf":[{"type":"integer","maximum":9223372036854775807,"minimum":0},{"type":"null"}],"title":"Cursor Through Ordering Key"}` |
| `cursor_after_priority` | query | False | `{"anyOf":[{"type":"integer","maximum":2,"minimum":0},{"type":"null"}],"title":"Cursor After Priority"}` |
| `cursor_after_ordering_key` | query | False | `{"anyOf":[{"type":"integer","maximum":9223372036854775807,"minimum":0},{"type":"null"}],"title":"Cursor After Ordering Key"}` |
### Responses
```json
{
"200": {
"description": "Successful Response",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/SessionMessageInspection"
}
}
}
},
"400": {
"description": "The request attempts to override authenticated actor identity.",
"headers": {
"Cache-Control": {
"schema": {
"type": "string",
"const": "private, no-store"
}
}
},
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ApiErrorResponse"
}
}
}
},
"401": {
"description": "Authentication is required.",
"headers": {
"Cache-Control": {
"schema": {
"type": "string",
"const": "private, no-store"
}
}
},
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ApiErrorResponse"
}
}
}
},
"403": {
"description": "Session-message access is not authorized.",
"headers": {
"Cache-Control": {
"schema": {
"type": "string",
"const": "private, no-store"
}
}
},
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ApiErrorResponse"
}
}
}
},
"409": {
"description": "The request conflicts with exact durable authority or terminal state.",
"headers": {
"Cache-Control": {
"schema": {
"type": "string",
"const": "private, no-store"
}
}
},
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ApiErrorResponse"
}
}
}
},
"413": {
"description": "The request exceeds the private request byte limit.",
"headers": {
"Cache-Control": {
"schema": {
"type": "string",
"const": "private, no-store"
}
}
},
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ApiErrorResponse"
}
}
}
},
"422": {
"description": "Invalid session-message request; rejected input is not reflected.",
"headers": {
"Cache-Control": {
"schema": {
"type": "string",
"const": "private, no-store"
}
}
},
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ApiErrorResponse"
}
}
}
},
"500": {
"description": "The session-message operation failed.",
"headers": {
"Cache-Control": {
"schema": {
"type": "string",
"const": "private, no-store"
}
}
},
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ApiErrorResponse"
}
}
}
},
"503": {
"description": "The store does not support this session-message operation.",
"headers": {
"Cache-Control": {
"schema": {
"type": "string",
"const": "private, no-store"
}
}
},
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ApiErrorResponse"
}
}
}
}
}
```
Model declarations: [ApiErrorResponse](https://cayu.dev/docs/reference/http/schemas/models-1/#apierrorresponse), [SessionMessageInspection](https://cayu.dev/docs/reference/http/schemas/models-9/#sessionmessageinspection).
## POST /api/sessions/{session_id}/messages/source-snapshot
Snapshot Session Message Source
Operation ID: `snapshot_session_message_source_api_sessions__session_id__messages_source_snapshot_post`.
| Parameter | Location | Required | Schema |
| --- | --- | --- | --- |
| `session_id` | path | True | `{"type":"string","title":"Session Id"}` |
### Request body
```json
{
"required": true,
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/SessionMessageSourceBody"
}
}
}
}
```
Model declarations: [SessionMessageSourceBody](https://cayu.dev/docs/reference/http/schemas/models-9/#sessionmessagesourcebody).
### Responses
```json
{
"200": {
"description": "Successful Response",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/SessionMessageSource"
}
}
}
},
"400": {
"description": "The request attempts to override authenticated actor identity.",
"headers": {
"Cache-Control": {
"schema": {
"type": "string",
"const": "private, no-store"
}
}
},
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ApiErrorResponse"
}
}
}
},
"401": {
"description": "Authentication is required.",
"headers": {
"Cache-Control": {
"schema": {
"type": "string",
"const": "private, no-store"
}
}
},
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ApiErrorResponse"
}
}
}
},
"403": {
"description": "Session-message access is not authorized.",
"headers": {
"Cache-Control": {
"schema": {
"type": "string",
"const": "private, no-store"
}
}
},
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ApiErrorResponse"
}
}
}
},
"409": {
"description": "The request conflicts with exact durable authority or terminal state.",
"headers": {
"Cache-Control": {
"schema": {
"type": "string",
"const": "private, no-store"
}
}
},
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ApiErrorResponse"
}
}
}
},
"413": {
"description": "The request exceeds the private request byte limit.",
"headers": {
"Cache-Control": {
"schema": {
"type": "string",
"const": "private, no-store"
}
}
},
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ApiErrorResponse"
}
}
}
},
"422": {
"description": "Invalid session-message request; rejected input is not reflected.",
"headers": {
"Cache-Control": {
"schema": {
"type": "string",
"const": "private, no-store"
}
}
},
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ApiErrorResponse"
}
}
}
},
"500": {
"description": "The session-message operation failed.",
"headers": {
"Cache-Control": {
"schema": {
"type": "string",
"const": "private, no-store"
}
}
},
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ApiErrorResponse"
}
}
}
},
"503": {
"description": "The store does not support this session-message operation.",
"headers": {
"Cache-Control": {
"schema": {
"type": "string",
"const": "private, no-store"
}
}
},
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ApiErrorResponse"
}
}
}
}
}
```
Model declarations: [ApiErrorResponse](https://cayu.dev/docs/reference/http/schemas/models-1/#apierrorresponse), [SessionMessageSource](https://cayu.dev/docs/reference/http/schemas/models-9/#sessionmessagesource).
## POST /api/sessions/{session_id}/messages/{queue_id}/withdraw
Withdraw Session Message
Operation ID: `withdraw_session_message_api_sessions__session_id__messages__queue_id__withdraw_post`.
| Parameter | Location | Required | Schema |
| --- | --- | --- | --- |
| `session_id` | path | True | `{"type":"string","title":"Session Id"}` |
| `queue_id` | path | True | `{"type":"string","title":"Queue Id"}` |
### Request body
```json
{
"required": true,
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/SessionMessageActionBody"
}
}
}
}
```
Model declarations: [SessionMessageActionBody](https://cayu.dev/docs/reference/http/schemas/models-8/#sessionmessageactionbody).
### Responses
```json
{
"200": {
"description": "Successful Response",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/SessionMessageActionResult"
}
}
}
},
"400": {
"description": "The request attempts to override authenticated actor identity.",
"headers": {
"Cache-Control": {
"schema": {
"type": "string",
"const": "private, no-store"
}
}
},
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ApiErrorResponse"
}
}
}
},
"401": {
"description": "Authentication is required.",
"headers": {
"Cache-Control": {
"schema": {
"type": "string",
"const": "private, no-store"
}
}
},
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ApiErrorResponse"
}
}
}
},
"403": {
"description": "Session-message access is not authorized.",
"headers": {
"Cache-Control": {
"schema": {
"type": "string",
"const": "private, no-store"
}
}
},
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ApiErrorResponse"
}
}
}
},
"409": {
"description": "The request conflicts with exact durable authority or terminal state.",
"headers": {
"Cache-Control": {
"schema": {
"type": "string",
"const": "private, no-store"
}
}
},
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ApiErrorResponse"
}
}
}
},
"413": {
"description": "The request exceeds the private request byte limit.",
"headers": {
"Cache-Control": {
"schema": {
"type": "string",
"const": "private, no-store"
}
}
},
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ApiErrorResponse"
}
}
}
},
"422": {
"description": "Invalid session-message request; rejected input is not reflected.",
"headers": {
"Cache-Control": {
"schema": {
"type": "string",
"const": "private, no-store"
}
}
},
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ApiErrorResponse"
}
}
}
},
"500": {
"description": "The session-message operation failed.",
"headers": {
"Cache-Control": {
"schema": {
"type": "string",
"const": "private, no-store"
}
}
},
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ApiErrorResponse"
}
}
}
},
"503": {
"description": "The store does not support this session-message operation.",
"headers": {
"Cache-Control": {
"schema": {
"type": "string",
"const": "private, no-store"
}
}
},
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ApiErrorResponse"
}
}
}
}
}
```
Model declarations: [ApiErrorResponse](https://cayu.dev/docs/reference/http/schemas/models-1/#apierrorresponse), [SessionMessageActionResult](https://cayu.dev/docs/reference/http/schemas/models-8/#sessionmessageactionresult).
## POST /api/sessions/{session_id}/messages/{queue_id}/quarantine
Quarantine Session Message
Operation ID: `quarantine_session_message_api_sessions__session_id__messages__queue_id__quarantine_post`.
| Parameter | Location | Required | Schema |
| --- | --- | --- | --- |
| `session_id` | path | True | `{"type":"string","title":"Session Id"}` |
| `queue_id` | path | True | `{"type":"string","title":"Queue Id"}` |
### Request body
```json
{
"required": true,
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/SessionMessageActionBody"
}
}
}
}
```
Model declarations: [SessionMessageActionBody](https://cayu.dev/docs/reference/http/schemas/models-8/#sessionmessageactionbody).
### Responses
```json
{
"200": {
"description": "Successful Response",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/SessionMessageActionResult"
}
}
}
},
"400": {
"description": "The request attempts to override authenticated actor identity.",
"headers": {
"Cache-Control": {
"schema": {
"type": "string",
"const": "private, no-store"
}
}
},
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ApiErrorResponse"
}
}
}
},
"401": {
"description": "Authentication is required.",
"headers": {
"Cache-Control": {
"schema": {
"type": "string",
"const": "private, no-store"
}
}
},
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ApiErrorResponse"
}
}
}
},
"403": {
"description": "Session-message access is not authorized.",
"headers": {
"Cache-Control": {
"schema": {
"type": "string",
"const": "private, no-store"
}
}
},
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ApiErrorResponse"
}
}
}
},
"409": {
"description": "The request conflicts with exact durable authority or terminal state.",
"headers": {
"Cache-Control": {
"schema": {
"type": "string",
"const": "private, no-store"
}
}
},
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ApiErrorResponse"
}
}
}
},
"413": {
"description": "The request exceeds the private request byte limit.",
"headers": {
"Cache-Control": {
"schema": {
"type": "string",
"const": "private, no-store"
}
}
},
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ApiErrorResponse"
}
}
}
},
"422": {
"description": "Invalid session-message request; rejected input is not reflected.",
"headers": {
"Cache-Control": {
"schema": {
"type": "string",
"const": "private, no-store"
}
}
},
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ApiErrorResponse"
}
}
}
},
"500": {
"description": "The session-message operation failed.",
"headers": {
"Cache-Control": {
"schema": {
"type": "string",
"const": "private, no-store"
}
}
},
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ApiErrorResponse"
}
}
}
},
"503": {
"description": "The store does not support this session-message operation.",
"headers": {
"Cache-Control": {
"schema": {
"type": "string",
"const": "private, no-store"
}
}
},
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ApiErrorResponse"
}
}
}
}
}
```
Model declarations: [ApiErrorResponse](https://cayu.dev/docs/reference/http/schemas/models-1/#apierrorresponse), [SessionMessageActionResult](https://cayu.dev/docs/reference/http/schemas/models-8/#sessionmessageactionresult).
# HTTP: system
Default control-plane operations. [Complete OpenAPI schema](https://cayu.dev/docs/openapi.json) · [Authentication and tenancy](https://cayu.dev/docs/operate/authentication/).
## GET /api/system/diagnostics
Get System Diagnostics
Return bounded protected Cayu configuration and registration diagnostics. This is an explicit operator snapshot, not readiness, infrastructure monitoring, or an authorization token. It performs no dependency probes.
Operation ID: `get_system_diagnostics_api_system_diagnostics_get`.
### Responses
```json
{
"200": {
"description": "Successful Response",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/SystemDiagnosticsResponse"
}
}
}
}
}
```
Model declarations: [SystemDiagnosticsResponse](https://cayu.dev/docs/reference/http/schemas/models-10/#systemdiagnosticsresponse).
# HTTP: tasks
Default control-plane operations. [Complete OpenAPI schema](https://cayu.dev/docs/openapi.json) · [Authentication and tenancy](https://cayu.dev/docs/operate/authentication/).
## GET /api/tasks
List Tasks
Operation ID: `list_tasks_api_tasks_get`.
| Parameter | Location | Required | Schema |
| --- | --- | --- | --- |
| `q` | query | False | `{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Q"}` |
| `status` | query | False | `{"anyOf":[{"$ref":"#/components/schemas/TaskStatus"},{"type":"null"}],"title":"Status"}` |
| `type` | query | False | `{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Type"}` |
| `session_id` | query | False | `{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Session Id"}` |
| `parent_task_id` | query | False | `{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Parent Task Id"}` |
| `assigned_agent_name` | query | False | `{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Assigned Agent Name"}` |
| `order_by` | query | False | `{"$ref":"#/components/schemas/TaskOrder","default":"updated_at_desc"}` |
| `limit` | query | False | `{"type":"integer","default":50,"title":"Limit"}` |
| `offset` | query | False | `{"type":"integer","default":0,"title":"Offset"}` |
### Responses
```json
{
"200": {
"description": "Successful Response",
"content": {
"application/json": {
"schema": {
"type": "array",
"items": {
"$ref": "#/components/schemas/ApiTaskListItem"
},
"title": "Response List Tasks Api Tasks Get"
}
}
}
},
"422": {
"description": "Validation Error",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/HTTPValidationError"
}
}
}
}
}
```
Model declarations: [ApiTaskListItem](https://cayu.dev/docs/reference/http/schemas/models-2/#apitasklistitem), [HTTPValidationError](https://cayu.dev/docs/reference/http/schemas/models-5/#httpvalidationerror).
## GET /api/tasks/{task_id}/schedule/events
List Task Schedule Events
Operation ID: `list_task_schedule_events_api_tasks__task_id__schedule_events_get`.
| Parameter | Location | Required | Schema |
| --- | --- | --- | --- |
| `task_id` | path | True | `{"type":"string","minLength":1,"title":"Task Id"}` |
| `after_sequence` | query | False | `{"type":"integer","maximum":9007199254740991,"minimum":0,"default":0,"title":"After Sequence"}` |
| `limit` | query | False | `{"type":"integer","maximum":1000,"minimum":1,"default":100,"title":"Limit"}` |
### Responses
```json
{
"200": {
"description": "Successful Response",
"content": {
"application/json": {
"schema": {
"type": "array",
"items": {
"$ref": "#/components/schemas/ApiTaskScheduleEvent"
},
"title": "Response List Task Schedule Events Api Tasks Task Id Schedule Events Get"
}
}
}
},
"422": {
"description": "Validation Error",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/HTTPValidationError"
}
}
}
}
}
```
Model declarations: [ApiTaskScheduleEvent](https://cayu.dev/docs/reference/http/schemas/models-2/#apitaskscheduleevent), [HTTPValidationError](https://cayu.dev/docs/reference/http/schemas/models-5/#httpvalidationerror).
## POST /api/tasks/schedule/reschedule
Reschedule Task
Operation ID: `reschedule_task_api_tasks_schedule_reschedule_post`.
### Request body
```json
{
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/TaskRescheduleRequest"
}
}
},
"required": true
}
```
Model declarations: [TaskRescheduleRequest](https://cayu.dev/docs/reference/http/schemas/models-11/#taskreschedulerequest).
### Responses
```json
{
"200": {
"description": "Successful Response",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ApiTaskScheduleReceipt"
}
}
}
},
"422": {
"description": "Validation Error",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/HTTPValidationError"
}
}
}
}
}
```
Model declarations: [ApiTaskScheduleReceipt](https://cayu.dev/docs/reference/http/schemas/models-2/#apitaskschedulereceipt), [HTTPValidationError](https://cayu.dev/docs/reference/http/schemas/models-5/#httpvalidationerror).
## POST /api/tasks/schedule/cancel
Cancel Scheduled Task
Operation ID: `cancel_scheduled_task_api_tasks_schedule_cancel_post`.
### Request body
```json
{
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/TaskScheduleCancelRequest"
}
}
},
"required": true
}
```
Model declarations: [TaskScheduleCancelRequest](https://cayu.dev/docs/reference/http/schemas/models-11/#taskschedulecancelrequest).
### Responses
```json
{
"200": {
"description": "Successful Response",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ApiTaskScheduleReceipt"
}
}
}
},
"422": {
"description": "Validation Error",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/HTTPValidationError"
}
}
}
}
}
```
Model declarations: [ApiTaskScheduleReceipt](https://cayu.dev/docs/reference/http/schemas/models-2/#apitaskschedulereceipt), [HTTPValidationError](https://cayu.dev/docs/reference/http/schemas/models-5/#httpvalidationerror).
## GET /api/tasks/{task_id}
Get Task
Operation ID: `get_task_api_tasks__task_id__get`.
| Parameter | Location | Required | Schema |
| --- | --- | --- | --- |
| `task_id` | path | True | `{"type":"string","minLength":1,"title":"Task Id"}` |
### Responses
```json
{
"200": {
"description": "Successful Response",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ApiTaskDetail"
}
}
}
},
"422": {
"description": "Validation Error",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/HTTPValidationError"
}
}
}
}
}
```
Model declarations: [ApiTaskDetail](https://cayu.dev/docs/reference/http/schemas/models-2/#apitaskdetail), [HTTPValidationError](https://cayu.dev/docs/reference/http/schemas/models-5/#httpvalidationerror).
## POST /api/tasks/{task_id}/pause
Pause Task
Operation ID: `pause_task_api_tasks__task_id__pause_post`.
| Parameter | Location | Required | Schema |
| --- | --- | --- | --- |
| `task_id` | path | True | `{"type":"string","minLength":1,"title":"Task Id"}` |
### Request body
```json
{
"content": {
"application/json": {
"schema": {
"anyOf": [
{
"$ref": "#/components/schemas/TaskHoldBody"
},
{
"type": "null"
}
],
"title": "Body"
}
}
}
}
```
Model declarations: [TaskHoldBody](https://cayu.dev/docs/reference/http/schemas/models-10/#taskholdbody).
### Responses
```json
{
"200": {
"description": "Successful Response",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ApiTaskDetail"
}
}
}
},
"422": {
"description": "Validation Error",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/HTTPValidationError"
}
}
}
}
}
```
Model declarations: [ApiTaskDetail](https://cayu.dev/docs/reference/http/schemas/models-2/#apitaskdetail), [HTTPValidationError](https://cayu.dev/docs/reference/http/schemas/models-5/#httpvalidationerror).
## POST /api/tasks/{task_id}/block
Block Task
Operation ID: `block_task_api_tasks__task_id__block_post`.
| Parameter | Location | Required | Schema |
| --- | --- | --- | --- |
| `task_id` | path | True | `{"type":"string","minLength":1,"title":"Task Id"}` |
### Request body
```json
{
"content": {
"application/json": {
"schema": {
"anyOf": [
{
"$ref": "#/components/schemas/TaskHoldBody"
},
{
"type": "null"
}
],
"title": "Body"
}
}
}
}
```
Model declarations: [TaskHoldBody](https://cayu.dev/docs/reference/http/schemas/models-10/#taskholdbody).
### Responses
```json
{
"200": {
"description": "Successful Response",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ApiTaskDetail"
}
}
}
},
"422": {
"description": "Validation Error",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/HTTPValidationError"
}
}
}
}
}
```
Model declarations: [ApiTaskDetail](https://cayu.dev/docs/reference/http/schemas/models-2/#apitaskdetail), [HTTPValidationError](https://cayu.dev/docs/reference/http/schemas/models-5/#httpvalidationerror).
## POST /api/tasks/{task_id}/needs-attention
Mark Task Needs Attention
Operation ID: `mark_task_needs_attention_api_tasks__task_id__needs_attention_post`.
| Parameter | Location | Required | Schema |
| --- | --- | --- | --- |
| `task_id` | path | True | `{"type":"string","minLength":1,"title":"Task Id"}` |
### Request body
```json
{
"content": {
"application/json": {
"schema": {
"anyOf": [
{
"$ref": "#/components/schemas/TaskHoldBody"
},
{
"type": "null"
}
],
"title": "Body"
}
}
}
}
```
Model declarations: [TaskHoldBody](https://cayu.dev/docs/reference/http/schemas/models-10/#taskholdbody).
### Responses
```json
{
"200": {
"description": "Successful Response",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ApiTaskDetail"
}
}
}
},
"422": {
"description": "Validation Error",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/HTTPValidationError"
}
}
}
}
}
```
Model declarations: [ApiTaskDetail](https://cayu.dev/docs/reference/http/schemas/models-2/#apitaskdetail), [HTTPValidationError](https://cayu.dev/docs/reference/http/schemas/models-5/#httpvalidationerror).
## POST /api/tasks/{task_id}/resume
Resume Task
Operation ID: `resume_task_api_tasks__task_id__resume_post`.
| Parameter | Location | Required | Schema |
| --- | --- | --- | --- |
| `task_id` | path | True | `{"type":"string","minLength":1,"title":"Task Id"}` |
### Responses
```json
{
"200": {
"description": "Successful Response",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ApiTaskDetail"
}
}
}
},
"422": {
"description": "Validation Error",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/HTTPValidationError"
}
}
}
}
}
```
Model declarations: [ApiTaskDetail](https://cayu.dev/docs/reference/http/schemas/models-2/#apitaskdetail), [HTTPValidationError](https://cayu.dev/docs/reference/http/schemas/models-5/#httpvalidationerror).
# HTTP: tool-approvals
Default control-plane operations. [Complete OpenAPI schema](https://cayu.dev/docs/openapi.json) · [Authentication and tenancy](https://cayu.dev/docs/operate/authentication/).
## POST /api/tool-approvals/resolve
Resolve Tool Approval
Operation ID: `resolve_tool_approval_api_tool_approvals_resolve_post`.
| Parameter | Location | Required | Schema |
| --- | --- | --- | --- |
| `Cayu-Mutation-ID` | header | False | `{"anyOf":[{"type":"string","minLength":1,"maxLength":128,"pattern":"^[A-Za-z0-9][A-Za-z0-9._~-]*$"},{"type":"null"}],"description":"Client-generated mutation identity used to correlate an ambiguous SSE reconnect with its durable server acceptance event. Send the same value on the initial mutation and every Last-Event-ID replay request; it is a replay correlation key, not permission to repeat the POST.","title":"Cayu-Mutation-Id"}` |
### Request body
```json
{
"required": true,
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ToolApprovalBody"
}
}
}
}
```
Model declarations: [ToolApprovalBody](https://cayu.dev/docs/reference/http/schemas/models-11/#toolapprovalbody).
### Responses
```json
{
"200": {
"description": "Server-Sent Events stream. Each runtime event is emitted as an SSE frame whose `data:` value is a JSON SseEventEnvelope. A classified terminal runtime or observer condition is emitted as `event: error` with a SseErrorEnvelope payload.",
"content": {
"text/event-stream": {
"schema": {
"type": "string",
"description": "SSE stream. Runtime `data:` frames contain SseEventEnvelope JSON; `event: error` frames contain SseErrorEnvelope JSON."
}
}
}
},
"404": {
"description": "The replay session or mutation target does not exist.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ApiErrorResponse"
}
}
}
},
"409": {
"description": "The replay event marker is unknown or the mutation conflicts with the current session state.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ApiErrorResponse"
}
}
}
},
"500": {
"description": "The mutation could not open an accepted durable stream.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ApiErrorResponse"
}
}
}
},
"413": {
"description": "The control-plane request exceeds its encoded byte limit.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ApiErrorResponse"
}
}
}
},
"422": {
"description": "Validation Error",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/HTTPValidationError"
}
}
}
}
}
```
Model declarations: [ApiErrorResponse](https://cayu.dev/docs/reference/http/schemas/models-1/#apierrorresponse), [HTTPValidationError](https://cayu.dev/docs/reference/http/schemas/models-5/#httpvalidationerror).
## POST /api/tool-approvals/recover
Recover Tool Approval
Operation ID: `recover_tool_approval_api_tool_approvals_recover_post`.
| Parameter | Location | Required | Schema |
| --- | --- | --- | --- |
| `Cayu-Mutation-ID` | header | False | `{"anyOf":[{"type":"string","minLength":1,"maxLength":128,"pattern":"^[A-Za-z0-9][A-Za-z0-9._~-]*$"},{"type":"null"}],"description":"Client-generated mutation identity used to correlate an ambiguous SSE reconnect with its durable server acceptance event. Send the same value on the initial mutation and every Last-Event-ID replay request; it is a replay correlation key, not permission to repeat the POST.","title":"Cayu-Mutation-Id"}` |
### Request body
```json
{
"required": true,
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ToolApprovalRecoveryBody"
}
}
}
}
```
Model declarations: [ToolApprovalRecoveryBody](https://cayu.dev/docs/reference/http/schemas/models-11/#toolapprovalrecoverybody).
### Responses
```json
{
"200": {
"description": "Server-Sent Events stream. Each runtime event is emitted as an SSE frame whose `data:` value is a JSON SseEventEnvelope. A classified terminal runtime or observer condition is emitted as `event: error` with a SseErrorEnvelope payload.",
"content": {
"text/event-stream": {
"schema": {
"type": "string",
"description": "SSE stream. Runtime `data:` frames contain SseEventEnvelope JSON; `event: error` frames contain SseErrorEnvelope JSON."
}
}
}
},
"404": {
"description": "The replay session or mutation target does not exist.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ApiErrorResponse"
}
}
}
},
"409": {
"description": "The replay event marker is unknown or the mutation conflicts with the current session state.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ApiErrorResponse"
}
}
}
},
"500": {
"description": "The mutation could not open an accepted durable stream.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ApiErrorResponse"
}
}
}
},
"413": {
"description": "The control-plane request exceeds its encoded byte limit.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ApiErrorResponse"
}
}
}
},
"422": {
"description": "Validation Error",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/HTTPValidationError"
}
}
}
}
}
```
Model declarations: [ApiErrorResponse](https://cayu.dev/docs/reference/http/schemas/models-1/#apierrorresponse), [HTTPValidationError](https://cayu.dev/docs/reference/http/schemas/models-5/#httpvalidationerror).
# HTTP: tool-rounds
Default control-plane operations. [Complete OpenAPI schema](https://cayu.dev/docs/openapi.json) · [Authentication and tenancy](https://cayu.dev/docs/operate/authentication/).
## POST /api/tool-rounds/recover
Recover Tool Round
Operation ID: `recover_tool_round_api_tool_rounds_recover_post`.
| Parameter | Location | Required | Schema |
| --- | --- | --- | --- |
| `Cayu-Mutation-ID` | header | False | `{"anyOf":[{"type":"string","minLength":1,"maxLength":128,"pattern":"^[A-Za-z0-9][A-Za-z0-9._~-]*$"},{"type":"null"}],"description":"Client-generated mutation identity used to correlate an ambiguous SSE reconnect with its durable server acceptance event. Send the same value on the initial mutation and every Last-Event-ID replay request; it is a replay correlation key, not permission to repeat the POST.","title":"Cayu-Mutation-Id"}` |
### Request body
```json
{
"required": true,
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ToolRoundRecoveryBody"
}
}
}
}
```
Model declarations: [ToolRoundRecoveryBody](https://cayu.dev/docs/reference/http/schemas/models-12/#toolroundrecoverybody).
### Responses
```json
{
"200": {
"description": "Server-Sent Events stream. Each runtime event is emitted as an SSE frame whose `data:` value is a JSON SseEventEnvelope. A classified terminal runtime or observer condition is emitted as `event: error` with a SseErrorEnvelope payload.",
"content": {
"text/event-stream": {
"schema": {
"type": "string",
"description": "SSE stream. Runtime `data:` frames contain SseEventEnvelope JSON; `event: error` frames contain SseErrorEnvelope JSON."
}
}
}
},
"404": {
"description": "The replay session or mutation target does not exist.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ApiErrorResponse"
}
}
}
},
"409": {
"description": "The replay event marker is unknown or the mutation conflicts with the current session state.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ApiErrorResponse"
}
}
}
},
"500": {
"description": "The mutation could not open an accepted durable stream.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ApiErrorResponse"
}
}
}
},
"413": {
"description": "The control-plane request exceeds its encoded byte limit.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ApiErrorResponse"
}
}
}
},
"422": {
"description": "Validation Error",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/HTTPValidationError"
}
}
}
}
}
```
Model declarations: [ApiErrorResponse](https://cayu.dev/docs/reference/http/schemas/models-1/#apierrorresponse), [HTTPValidationError](https://cayu.dev/docs/reference/http/schemas/models-5/#httpvalidationerror).
# HTTP: usage
Default control-plane operations. [Complete OpenAPI schema](https://cayu.dev/docs/openapi.json) · [Authentication and tenancy](https://cayu.dev/docs/operate/authentication/).
## POST /api/usage/rollup
Get Usage Rollup
Aggregate usage-bearing events in a UTC-normalized half-open event-time window. Session filters apply to current session attributes. Totals remain exact when provider/model detail is bounded into an explicit remainder; callers may opt into a bounded per-session breakdown with its own exact remainder; cost is omitted from evaluation rather than reported partially when its price-input bound is exceeded. Scope is the configured session store; authentication does not add tenant filtering.
Operation ID: `get_usage_rollup_api_usage_rollup_post`.
### Request body
```json
{
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/UsageRollupRequest"
}
}
},
"required": true
}
```
Model declarations: [UsageRollupRequest](https://cayu.dev/docs/reference/http/schemas/models-12/#usagerolluprequest).
### Responses
```json
{
"200": {
"description": "Successful Response",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/UsageRollupResponse"
}
}
}
},
"501": {
"description": "The configured store does not implement this aggregate read.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ApiErrorResponse"
}
}
}
},
"409": {
"description": "A per-session identity cannot cross the configured redaction boundary.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ApiErrorResponse"
}
}
}
},
"413": {
"description": "The usage-rollup request or serialized response exceeds its byte limit.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ApiErrorResponse"
}
}
}
},
"500": {
"description": "The configured store returned an inconsistent usage projection.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ApiErrorResponse"
}
}
}
},
"422": {
"description": "Validation Error",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/HTTPValidationError"
}
}
}
}
}
```
Model declarations: [ApiErrorResponse](https://cayu.dev/docs/reference/http/schemas/models-1/#apierrorresponse), [HTTPValidationError](https://cayu.dev/docs/reference/http/schemas/models-5/#httpvalidationerror), [UsageRollupResponse](https://cayu.dev/docs/reference/http/schemas/models-12/#usagerollupresponse).
# HTTP: user-input
Default control-plane operations. [Complete OpenAPI schema](https://cayu.dev/docs/openapi.json) · [Authentication and tenancy](https://cayu.dev/docs/operate/authentication/).
## POST /api/user-input/resolve
Resolve User Input
Operation ID: `resolve_user_input_api_user_input_resolve_post`.
| Parameter | Location | Required | Schema |
| --- | --- | --- | --- |
| `Cayu-Mutation-ID` | header | False | `{"anyOf":[{"type":"string","minLength":1,"maxLength":128,"pattern":"^[A-Za-z0-9][A-Za-z0-9._~-]*$"},{"type":"null"}],"description":"Client-generated mutation identity used to correlate an ambiguous SSE reconnect with its durable server acceptance event. Send the same value on the initial mutation and every Last-Event-ID replay request; it is a replay correlation key, not permission to repeat the POST.","title":"Cayu-Mutation-Id"}` |
### Request body
```json
{
"required": true,
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/UserInputResolveBody"
}
}
}
}
```
Model declarations: [UserInputResolveBody](https://cayu.dev/docs/reference/http/schemas/models-13/#userinputresolvebody).
### Responses
```json
{
"200": {
"description": "Server-Sent Events stream. Each runtime event is emitted as an SSE frame whose `data:` value is a JSON SseEventEnvelope. A classified terminal runtime or observer condition is emitted as `event: error` with a SseErrorEnvelope payload.",
"content": {
"text/event-stream": {
"schema": {
"type": "string",
"description": "SSE stream. Runtime `data:` frames contain SseEventEnvelope JSON; `event: error` frames contain SseErrorEnvelope JSON."
}
}
}
},
"404": {
"description": "The replay session or mutation target does not exist.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ApiErrorResponse"
}
}
}
},
"409": {
"description": "The replay event marker is unknown or the mutation conflicts with the current session state.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ApiErrorResponse"
}
}
}
},
"500": {
"description": "The mutation could not open an accepted durable stream.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ApiErrorResponse"
}
}
}
},
"413": {
"description": "The control-plane request exceeds its encoded byte limit.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ApiErrorResponse"
}
}
}
},
"422": {
"description": "Validation Error",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/HTTPValidationError"
}
}
}
}
}
```
Model declarations: [ApiErrorResponse](https://cayu.dev/docs/reference/http/schemas/models-1/#apierrorresponse), [HTTPValidationError](https://cayu.dev/docs/reference/http/schemas/models-5/#httpvalidationerror).
## POST /api/user-input/recover
Recover User Input
Operation ID: `recover_user_input_api_user_input_recover_post`.
| Parameter | Location | Required | Schema |
| --- | --- | --- | --- |
| `Cayu-Mutation-ID` | header | False | `{"anyOf":[{"type":"string","minLength":1,"maxLength":128,"pattern":"^[A-Za-z0-9][A-Za-z0-9._~-]*$"},{"type":"null"}],"description":"Client-generated mutation identity used to correlate an ambiguous SSE reconnect with its durable server acceptance event. Send the same value on the initial mutation and every Last-Event-ID replay request; it is a replay correlation key, not permission to repeat the POST.","title":"Cayu-Mutation-Id"}` |
### Request body
```json
{
"required": true,
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/UserInputRecoveryBody"
}
}
}
}
```
Model declarations: [UserInputRecoveryBody](https://cayu.dev/docs/reference/http/schemas/models-13/#userinputrecoverybody).
### Responses
```json
{
"200": {
"description": "Server-Sent Events stream. Each runtime event is emitted as an SSE frame whose `data:` value is a JSON SseEventEnvelope. A classified terminal runtime or observer condition is emitted as `event: error` with a SseErrorEnvelope payload.",
"content": {
"text/event-stream": {
"schema": {
"type": "string",
"description": "SSE stream. Runtime `data:` frames contain SseEventEnvelope JSON; `event: error` frames contain SseErrorEnvelope JSON."
}
}
}
},
"404": {
"description": "The replay session or mutation target does not exist.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ApiErrorResponse"
}
}
}
},
"409": {
"description": "The replay event marker is unknown or the mutation conflicts with the current session state.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ApiErrorResponse"
}
}
}
},
"500": {
"description": "The mutation could not open an accepted durable stream.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ApiErrorResponse"
}
}
}
},
"413": {
"description": "The control-plane request exceeds its encoded byte limit.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ApiErrorResponse"
}
}
}
},
"422": {
"description": "Validation Error",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/HTTPValidationError"
}
}
}
}
}
```
Model declarations: [ApiErrorResponse](https://cayu.dev/docs/reference/http/schemas/models-1/#apierrorresponse), [HTTPValidationError](https://cayu.dev/docs/reference/http/schemas/models-5/#httpvalidationerror).
# Reference
These references describe the latest published Cayu release. Use the same version when reproducing an example. The [release manifest](https://cayu.dev/docs/release.json) names that version and records its source checksum and generated coverage.
| Reference | Contents |
| --- | --- |
| [Python API](https://cayu.dev/docs/reference/python/) | Declared imports, classes, fields, methods, signatures and release source |
| [HTTP API](https://cayu.dev/docs/reference/http/) | Default control-plane paths, parameters, request and response schemas |
| [CLI](https://cayu.dev/docs/reference/cli/) | Every top-level command and recursive subcommand help |
| [Configuration](https://cayu.dev/docs/reference/configuration/) | Setting owners, project tables, environment inputs and typed defaults |
| [Project convention](https://cayu.dev/docs/reference/project-convention/) | Factory, construction and authoring contract |
| [Runtime guarantees](https://cayu.dev/docs/reference/runtime-guarantees/) | Ownership, authority, durability and failure boundaries |
| [Capabilities](https://cayu.dev/docs/reference/capabilities/) | Package-shipped guide and public API navigation |
| [Glossary](https://cayu.dev/docs/reference/glossary/) | Shared runtime terms |
## Machine-readable downloads
- [Python API index](https://cayu.dev/docs/python-api.json): namespace import names and definition records.
- [OpenAPI](https://cayu.dev/docs/openapi.json): default server request/response components.
- [Page index](https://cayu.dev/docs/pages.json): titles, routes and plain-text documentation URLs.
- [llms.txt](https://cayu.dev/llms.txt): starting points for coding agents.
- [Complete manual](https://cayu.dev/llms-full.txt): authored guides and reference overviews in plain text; detailed definitions are linked individually.
Source declarations document available API shape. Use the associated guides and guarantees for lifecycle behavior, failure evidence and deployment requirements. Optional imports require their capability's dependencies even though static generation includes them without loading those dependencies.
# Cayu generated applications
This guide ships with the Cayu package, so the installed version is always available offline with `cayu guide applications`. [Release source](https://github.com/cayu-dev/cayu/blob/v0.9.1/src/cayu/guides/applications.md).
`cayu new` compiles a normalized application plan into ordinary explicit
Python. The generated tree is executable architecture for people and coding
agents; it is not a runtime plugin system, service locator, or authority grant.
`cayu new` refuses a parent directory that other users can write: one that is
world-writable without the sticky bit, or group-writable by a shared group. A
group-writable parent (the default home directory under umask 002) is accepted
when you own it and its group is your private group: named after you, your
primary group, with no other members and no other account using it as a
primary group. Cayu reads this from the local user and group databases; with a
directory service that does not enumerate its users (some LDAP or SSSD setups)
it cannot see every account, so prefer a parent that is not group-writable there.
## Convention
Normal agent, service, and coding presets share these ownership boundaries:
| Concern | Canonical home |
| --- | --- |
| Agent identity and registration | `agents/` |
| Prompt material | `prompts/` |
| Native model-callable capabilities | `tools/` |
| Exposure, authorization, execution, egress, budgets, and retries | `policies/` |
| Workspaces, runners, artifacts, knowledge bindings, and lifecycle | `environments/` |
| Deterministic orchestration | `workflows/` |
| Tasks, workers, approvals, completion, and recovery | `operations/` |
| Reviewed retrieval and curation | `knowledge/` |
| Context, recall, compaction, and memory attribution | `memory/` |
| Business rules | `domain/` |
| External protocols and MCP adapters | `integrations/` |
| Behavioral evidence | `evals/` |
| Event sinks and tracing | `observability/` |
| Final construction and registration only | `app.py` |
Implement in the owning module first, then connect it through
`agents/registration.py` and the composition root. Explicit imports and
`register_*` calls remain authoritative. Do not infer permission from placement,
prompts, scaffold metadata, or capability selection.
`[tool.cayu.scaffold]` records the convention version, preset, adapters,
capabilities, and minimal choice. It is source-controlled creation intent used
by compatible generators and read-only diagnostics. It never selects runtime
objects. Projects without this declaration remain freeform.
For declared convention projects, `cayu check --fail-on warning --json` reports
missing ownership seams, implementation collapsed into `app.py`, top-level
composition work, and agent registration that no longer originates from the
explicit registration module. Removing the contract is an intentional custom
layout migration, not a supported way to silence a finding.
`cayu check` never imports application code that failed the import-safety check
(or whose scaffold contract is too invalid for that check to run). It still lists
every source finding and adds `SCAFFOLD_APPLICATION_NOT_IMPORTED` naming the
checks that did not run. Any other source error, such as plan drift, no longer
stops the check: the application is loaded and checked as usual, and if its
factory fails, `SCAFFOLD_APPLICATION_LOAD_FAILED` is reported next to the source
findings that likely caused it.
The import check accepts inert application class hierarchies, including exception
classes and subclasses of a shared `Tool` base. Define bases before their uses
and use explicit named imports, such as `from tools.base import ProductTool`;
relative imports, import aliases, and explicit package re-exports are supported.
The checker reads local source without executing it to establish inheritance
safety, including explicit local import dependencies and their package
initializers. These dependencies must also be declarative: a helper import can
install subclass hooks after a class is defined. The proof is bounded to 64 local
source modules and fails closed beyond that limit. Rebindings, import cycles,
opaque descriptors, custom metaclasses,
class decorators on a base, and application `__init_subclass__` hooks remain
unproven and fail closed. Dynamic base expressions and class namespaces also
require a stronger proof than this static check supports. Keep lifecycle and
external work in methods or explicit builders. External roots of a local class
hierarchy must also have reviewed subclass-creation behavior: builtin exceptions,
`object`, `abc.ABC`, and Cayu's `Tool` are supported. Enum-derived application
bases remain unproven because enum metaclasses can execute inherited member
initializers; mixing a local base with an enum has the same restriction.
Module and class bodies may also use these reviewed declarative forms, which
the check accepts without executing them:
- A class-level `spec = ToolSpec(...)` whose values are literals, `ToolEffect`
members, and a nested literal `ExecutionProfileBehaviorIdentity(...)` or a
module-level constant holding one. This is the shape `cayu generate tool`
emits.
- `logging.getLogger(__name__)` or with a literal name.
- Dataclass fields `field(default=)` or
`field(default_factory=list|dict|set|tuple|frozenset)` with literal flags.
- `Enum`/`StrEnum`/`IntEnum`, `TypedDict` and `NamedTuple` declarations with
annotations and literal values.
- `@functools.cache`, `@functools.lru_cache` and `@lru_cache(maxsize=...)` with
literal options.
Environment reads at import (`os.environ[...]`, `os.environ.get(...)`,
`os.getenv(...)`) are deliberately unsupported, with `reason:
import_time_configuration`: read settings at call time in a
`configuration/settings.py` function, as the generated `configured_*` helpers
do, so tests and deployments can change them.
Anything else that runs at import is reported as `SCAFFOLD_IMPORT_SIDE_EFFECT`.
The finding names the construct and says why: `reason: import_time_effect` for a
call that clearly performs I/O, network, process or file work (for example
`subprocess.run(...)` or `Path(...).write_text(...)`), and
`reason: unsupported_expression` for a shape the check cannot prove inert (for
example an application function call such as `load(...)`, including one nested
inside a `ToolSpec`). Move either kind into a builder, lifecycle hook or tool
`run`.
Pydantic `BaseModel` declarations (including named import aliases) may register `@field_validator` and
`@model_validator` methods. Import these helpers explicitly from `pydantic`
(named aliases and `import pydantic as pd` are supported). Field names must be
literal strings; `mode` must be a supported literal string and `check_fields`
a literal boolean or `None`. Model validators require an explicit `mode` of
`before`, `after`, or `wrap`; field validators also accept `plain` and default
to `after`. Validator bodies run during validation, not registration. Model
assignments may also use explicitly imported `ConfigDict` and `Field` with
literal data arguments; callbacks and computed defaults remain unproven.
Unknown options, computed arguments, unpacking, shadowed/rebound imports,
project-local substitutes for Pydantic, opaque/quoted model field annotations,
explicit `__annotations__`, `__annotate__`, or `__annotate_func__` bindings,
and model construction/schema hooks
remain unproven. Inherited models retain these checks through explicit named
imports, aliases and package re-exports; literal `ConfigDict` configuration does
not prevent subclassing. Mixing models with ordinary application mixins remains
unproven because the model metaclass could activate inherited hooks. Attribute
and subscription base expressions such as `BaseModel.__mro__[0]` remain unproven.
Standard-library `TypeVar`, `ParamSpec`, and `TypeVarTuple` declarations accept a
single literal string name. `TypeVar` and `ParamSpec` also accept literal boolean
variance options. Bounds, constraints, computed arguments and unpacking remain
unproven. `ContextVar` construction accepts a literal string name and an optional
literal-data `default`. These process-wide identities can remain at module scope.
Standard `contextlib.contextmanager` and `asynccontextmanager` decorators on
functions and methods are declarations; their bodies are not executed by the
scanner. Named import aliases and module aliases are supported for these standard
library forms, while shadowed imports and project-local substitutes fail closed.
## Planning
Discover the package-shipped catalog before choosing a plan:
```console
uv run --no-sync cayu new --list-presets --json
uv run --no-sync cayu new --list-capabilities --json
uv run --no-sync cayu new --explain knowledge --json
```
Resolve the full plan without writing:
```console
uv run --no-sync cayu new my_agent --preset agent --provider neutral --dry-run --json
```
Dry-run and apply use the same normalized plan. Presets select a coherent
application shape; provider and execution flags select maintained adapters; `--with` and `--without` change only package-shipped selectable
capabilities. Extension-only concerns retain their canonical homes but cannot be
claimed active through a flag. Invalid combinations fail before target creation.
Service profiles require `tasks`; Docker coding profiles require `artifacts` for
their durable product results. Excluding either produces `CAPABILITY_REQUIRED`
before writing files. Coding includes artifacts in its declared defaults; local
coding can exclude artifacts, tasks, delegation, and human input independently.
An excluded store cannot be restored through a generated factory's injection seam.
For a normalized declared profile, `inspect` and `check` report
`SCAFFOLD_CAPABILITY_DRIFT` when constructed stores or built-in tool families
disagree with the capability selection. For agent starters with
knowledge, this also compares the starter proposal tool's effective coverage
(`approval_required` with `approvals`, `denied` without it), including maintained
policy compositions; independent extension approval policies do not imply that the
starter capability is enabled. `cayu inspect --json` includes both `manifest`
and `diagnostics` when drift is found and still exits non-zero. These are structural diagnostics, not
proof of provider access or arbitrary custom policy behavior. The default agent's
`search_knowledge` uses its configured project/agent namespace when the caller
omits the namespace; normal knowledge access and active-entry filters still apply.
`NAME` is always the project directory's basename and `--dir` is always its
existing parent. To inspect a maintained variant while changing an existing
project, create a disposable reference instead of passing a path as `NAME` or
running `cayu new` over the current repository:
```console
reference_parent="$(mktemp -d)"
uv run --no-sync cayu new my_agent_reference --agent-name my_agent --preset agent \
--provider neutral --execution none \
--dir "$reference_parent" --json
```
The reference is comparison material, not an automatic migration. Review the
owning-file diff and update the existing source plus `[tool.cayu.scaffold]`
explicitly. `cayu new`, `cayu check`, and `cayu inspect` never migrate a project.
All presets use the same application convention. Use `--preset` and `--execution`
to select the application and execution environment.
## Durable data
Every preset selects its database at runtime, not at generation time. The storage
module (`configuration/storage.py`, or `configuration/coding_storage.py` for
coding) calls `open_application_stores(configured_database_url(), sqlite_path=...)`:
`CAYU_DATABASE_URL` selects PostgreSQL, and without it the stores use local SQLite
at an absolute path under the project (`data/cayu.db`, or `.cayu/runtime/cayu.db`
for coding). `[tool.cayu.session_store]` names that same local file for Cayu CLI
tooling, and `CAYU_DATABASE_URL` overrides it for the app and the CLI alike. Every
generated project depends on `cayu[postgres,server]`. `cayu new --database` is deprecated
and ignored. The service preset keeps its product operation records (tenant
authorization, claims, and settlement) in the same database with
`product_operations=True`, so several service processes can share PostgreSQL.
- Keep application-owned durable records, such as orders, cases, ledgers, and
sync cursors, in the configured database. Application tables may share it with
their own table prefix; Cayu reserves `cayu_` for its tables.
- `data/` locally and `/data` in a deployment hold files: artifacts, uploads, and
fixtures. Do not open SQLite files there for durable application state. Cayu Cloud
blocks such code at deploy time; acknowledge a rebuildable cache or index under
`/data` with `[storage] local_files = [{ path = "...", reason = "..." }]` in
`cayu-cloud.toml`.
- Deployments set `CAYU_DATABASE_URL` to a migrated PostgreSQL database, run
`cayu storage migrate` as a deploy step, and set `CAYU_REQUIRE_POSTGRES=1` so
every Cayu SQLite store refuses to open.
- `CAYU_DATABASE_POOL_MAX` (default 5) bounds the shared connection pool. Behind a
transaction-pooling proxy such as PgBouncer, `CAYU_DATABASE_DIRECT_URL` gives the
task-admission `LISTEN` connection a direct server address.
- Deployments of applications with automatic memory must set
`CAYU_MEMORY_EVIDENCE_KEY`; the local `data/memory-evidence.key` is git-ignored
and never reaches a deployment.
`cayu check` reports `SCAFFOLD_PLAN_DRIFT` with field `storage` when the storage
module constructs a fixed SQLite or Postgres store, or when
`[tool.cayu.session_store]` names anything other than the local SQLite file.
Projects generated before this convention may keep `database` in
`[tool.cayu.scaffold]`; it is ignored. Migrate them by replacing the storage module
with the one from a disposable `cayu new` reference.
## Explicit service extensions
Services can declare application-owned `artifacts`, `knowledge`, and `delegation`
extensions without changing preset or maintained authentication. The catalog's
`extension_presets` lists where this declaration is supported; `supported_presets`
continues to describe generator support. `cayu new --with artifacts --preset service`
therefore points to this manual extension path rather than generating unreviewed
service wiring. `cayu new --explain artifacts --json` exposes both contracts.
Keep the normalized generated `capabilities` and add a separate sorted, unique
`extensions` list in the existing `[tool.cayu.scaffold]` table. For example, an
otherwise default service with an explicit artifact extension declares:
```toml
[tool.cayu.scaffold]
convention = 1
preset = "service"
provider = "neutral"
execution = "none"
capabilities = ["approvals", "evals", "observability", "tasks"]
extensions = ["artifacts"]
```
Existing projects may omit `extensions` (equivalent to `[]`). If an earlier
attempt added an extension to `capabilities`, move just that entry to `extensions`.
Retain the project's actual provider, adapters, and generated capability choices.
To declare all three, use `extensions = ["artifacts", "delegation", "knowledge"]`.
Unknown, duplicate, unsorted, and unsupported declarations are errors.
Implement and review the public constructors in their canonical homes:
- **Artifacts:** register `ListArtifactsTool()` in `tools/registration.py` and
construct the `LocalArtifactStore` and `Environment` in `environments/local.py`.
Update the environment's explicit behavior identity with the implementation.
- **Knowledge:** construct the scoped store in `configuration/storage.py`, define
access and retrieval scope in `knowledge/retrieval.py`, and wire the environment
in `environments/local.py`. Register `ListKnowledgeTool`, `SearchKnowledgeTool`,
`ReadKnowledgeTool`, and `RememberKnowledgeTool` together for one agent in
`tools/registration.py`; authorize proposal effects in `policies/tools.py` and
give the changed tool and policy behavior explicit versioned identities for recovery.
- **Delegation:** define and explicitly register child agents in `agents/`,
construct `SubagentTool` and `SubagentResultTool` with the owning app and stores
through `agents/registration.py`, and keep lifecycle work in `operations/`.
Declare child targets, limits, result access, exposure, and effect policy explicitly.
Generated code contains only the capabilities that were selected; a disabled
concern leaves no guard or placeholder to flip. Add its wiring to the relevant
owning builders with their collaborators and identities, using the public
constructors above, or compare with a disposable `cayu new --with `
reference. Do not read scaffold metadata
at runtime to enable tools or stores. Retain import safety and composition-only
`app.py`; keep service routes, product authentication, tenant lookup, and operator
policy intact. Artifact visibility, knowledge namespaces, and child-result access
must follow authenticated application scopes. A project-wide knowledge namespace
is suitable only for intentionally shared knowledge, not tenant-private records.
An extension declaration does not establish tenant isolation for custom behavior.
Run `cayu check --deploy --fail-on warning --json` with the service's authentication
configuration and run its security tests plus tests for the new scope boundaries.
The checker compares the union of generated capabilities and declared extensions
against the constructed stores and complete tool families. An undeclared store or
tool remains drift; declaration alone also fails when constructors are absent or
a tool family is incomplete. Source ownership, import safety, and maintained
service/authentication checks still run. Metadata neither changes runtime objects
nor grants model exposure, execution authority, or provider access.
For `cayu cloud deploy`, run `uv lock` in the selected project root and include both
`pyproject.toml` and `uv.lock` in the uploaded source. Preflight checks the actual bundle,
including Git ignore rules; a local wheel build alone does not establish Cloud build
readiness. The deploy command does not generate locks or modify the source for you.
For a public service with a `[web]` process, `cayu cloud deploy` also runs the same
deploy check in-process before uploading and refuses (`deploy_check_failed`) when it
reports a `PUBLIC_SERVICE_*` finding, because `cayu serve` would refuse to start the
release. The check sees only the local environment, so set the service's authentication
configuration for the deploy as for `cayu check`, or pass `--skip-deploy-check` on purpose.
## Cloud build diagnostics
An identical `cayu cloud deploy .` may replay an earlier immutable attempt. Failed or
destroyed attempts whose recorded failure is automatically retryable receive one fresh
retry by default; the JSON `retry` receipt identifies both attempts. In-progress and
successful replays remain idempotent. Use `--no-retry-failed` to report the retained
attempt, or `cayu cloud deployment retry ID --application SLUG --idempotency-key KEY`
to submit a deliberate retry. Reuse KEY after an uncertain HTTP outcome. Source errors
still require corrected source, and expired source bundles require a new upload.
When `cayu cloud deploy` fails, inspect the structured `error.failure`: its code,
phase, repair hint, retry classification, and diagnostic evidence describe the
failure. Supported versioned diagnostics include a bounded redacted build excerpt,
attempt, exit code, and evidence availability. For example, missing build inputs
require fixing the source bundle and submitting a new revision; a temporary
infrastructure failure may permit retrying the same source.
Retrieve deployment-scoped evidence without platform credentials:
```console
cayu cloud deployment timeline DEPLOYMENT_ID --application AGENT_SLUG
cayu cloud deployment logs DEPLOYMENT_ID --application AGENT_SLUG
```
For servers supporting diagnostic pagination, use `--diagnostic-offset` with the
returned `next_diagnostic_offset` and optionally `--diagnostic-limit 20`. If evidence
is unavailable or unsupported, the deploy error retains the failed outcome and
provides a logs command when possible. A CLI upgrade cannot recover evidence the
server never recorded. Verify a later deployment succeeds before claiming recovery.
## Generator compatibility
`cayu generate tool` and `cayu generate slice` inspect the declared scaffold
contract. Convention projects update only delimited regions in
`agents/registration.py` and the narrow agent contract. Legacy generated
projects retain their `app.py` seams. A custom or drifted source shape produces
a reviewable conflict or manual action instead of an arbitrary rewrite.
Use this authoring loop:
```text
understand -> inspect -> plan -> change -> test -> eval -> exercise -> report evidence
```
After a change, run the exact commands emitted by `cayu new`; at minimum:
```console
uv run --no-sync cayu inspect --json
uv run --no-sync cayu check --fail-on warning --json
uv run --no-sync pytest
uv run --no-sync cayu eval run
```
Run application-constructing proof commands sequentially when they share the
generated local SQLite store. A first construction may initialize or migrate
that schema.
Runtime sessions, events, checkpoints, tasks, approvals, receipts, knowledge
entries, usage, artifacts, eval results, and snapshots belong in configured
stores or artifact backends. They do not belong in generated source packages.
For runtime timing around tool work, use
`await app.inspect_recent_tool_round_timing(session_id)` and
`await app.inspect_recent_model_step_preparation_timing(session_id)`.
`CayuApp(runtime_timing=RuntimeTimingConfig(...), timing_sinks=[...])` controls
the bounded recent view and optional best-effort export. Outside the Cayu
server, `await app.aclose()` (or `async with app:`) stops timing delivery at
shutdown along with the app's other work. These observations
are process-local and add no durable writes. Phase definitions, native store
costs and slow-storage interpretation are in `cayu guide durable-service-tools`.
# Python API reference
The supported Cayu Python API, organized by import namespace. Every declared lazy export has a linked definition. Root imports remain supported conveniences; use a capability namespace when it makes ownership clearer.
## Start with the core contracts
- [CayuApp](https://cayu.dev/docs/reference/python/definitions/applications/#CayuApp)
- [CayuConfig](https://cayu.dev/docs/reference/python/definitions/configuration/#CayuConfig)
- [AgentSpec](https://cayu.dev/docs/reference/python/definitions/agents/#AgentSpec)
- [Message](https://cayu.dev/docs/reference/python/definitions/messages/#Message)
- [RunRequest](https://cayu.dev/docs/reference/python/definitions/sessions/base/runrequest/)
- [RunOutcome](https://cayu.dev/docs/reference/python/definitions/sessions/outcomes/#RunOutcome)
- [run_to_completion](https://cayu.dev/docs/reference/python/definitions/sessions/outcomes/#run_to_completion)
- [Tool](https://cayu.dev/docs/reference/python/definitions/tools/base/#Tool)
- [ToolSpec](https://cayu.dev/docs/reference/python/definitions/tools/base/#ToolSpec)
- [ToolContext](https://cayu.dev/docs/reference/python/definitions/tools/base/#ToolContext)
- [ToolResult](https://cayu.dev/docs/reference/python/definitions/tools/base/#ToolResult)
- [EvalCase](https://cayu.dev/docs/reference/python/definitions/evals/runner/#EvalCase)
- [EvalSuite](https://cayu.dev/docs/reference/python/definitions/evals/runner/#EvalSuite)
## Public namespaces
| Namespace | Declared imports |
| --- | --- |
| [cayu](https://cayu.dev/docs/reference/python/core/) | 3471 |
| [cayu.agents](https://cayu.dev/docs/reference/python/agents/) | 3 |
| [cayu.applications](https://cayu.dev/docs/reference/python/applications/) | 1 |
| [cayu.approvals](https://cayu.dev/docs/reference/python/approvals/) | 38 |
| [cayu.artifacts](https://cayu.dev/docs/reference/python/artifacts/) | 68 |
| [cayu.budgets](https://cayu.dev/docs/reference/python/budgets/) | 125 |
| [cayu.collaboration](https://cayu.dev/docs/reference/python/collaboration/) | 263 |
| [cayu.configuration](https://cayu.dev/docs/reference/python/configuration/) | 6 |
| [cayu.context](https://cayu.dev/docs/reference/python/context/) | 66 |
| [cayu.delivery](https://cayu.dev/docs/reference/python/delivery/) | 67 |
| [cayu.egress](https://cayu.dev/docs/reference/python/egress/) | 82 |
| [cayu.environments](https://cayu.dev/docs/reference/python/environments/) | 121 |
| [cayu.evals](https://cayu.dev/docs/reference/python/evals/) | 819 |
| [cayu.events](https://cayu.dev/docs/reference/python/events/) | 3 |
| [cayu.exceptions](https://cayu.dev/docs/reference/python/exceptions/) | 2 |
| [cayu.extensions.egress](https://cayu.dev/docs/reference/python/extensions-egress/) | 9 |
| [cayu.extensions.runners](https://cayu.dev/docs/reference/python/extensions-runners/) | 17 |
| [cayu.mcp](https://cayu.dev/docs/reference/python/mcp/) | 53 |
| [cayu.memory](https://cayu.dev/docs/reference/python/memory/) | 193 |
| [cayu.messages](https://cayu.dev/docs/reference/python/messages/) | 15 |
| [cayu.observability](https://cayu.dev/docs/reference/python/observability/) | 31 |
| [cayu.providers](https://cayu.dev/docs/reference/python/providers/) | 112 |
| [cayu.proxies](https://cayu.dev/docs/reference/python/proxies/) | 5 |
| [cayu.runners](https://cayu.dev/docs/reference/python/runners/) | 74 |
| [cayu.runtime](https://cayu.dev/docs/reference/python/runtime/) | 1102 |
| [cayu.server](https://cayu.dev/docs/reference/python/server/) | 50 |
| [cayu.sessions](https://cayu.dev/docs/reference/python/sessions/) | 275 |
| [cayu.snapshots](https://cayu.dev/docs/reference/python/snapshots/) | 121 |
| [cayu.storage](https://cayu.dev/docs/reference/python/storage/) | 153 |
| [cayu.tasks](https://cayu.dev/docs/reference/python/tasks/) | 170 |
| [cayu.testing](https://cayu.dev/docs/reference/python/testing/) | 41 |
| [cayu.tools](https://cayu.dev/docs/reference/python/tools/) | 267 |
| [cayu.vaults](https://cayu.dev/docs/reference/python/vaults/) | 24 |
| [cayu.verification](https://cayu.dev/docs/reference/python/verification/) | 18 |
| [cayu.webhooks](https://cayu.dev/docs/reference/python/webhooks/) | 3 |
| [cayu.workflows](https://cayu.dev/docs/reference/python/workflows/) | 29 |
| [cayu.workspaces](https://cayu.dev/docs/reference/python/workspaces/) | 95 |
## Read an entry
Each entry provides the supported import, source declaration, documented behavior and release source. Method signatures preserve defaults and async return annotations. Fields include inherited declarations; defaults are source expressions rather than evaluated values. Pydantic and dataclass construction follows those frameworks' semantics. A missing method docstring is not a guarantee that the method cannot fail: consult the linked runtime guarantees and guide for lifecycle, authority and recovery behavior.
## Optional imports
`dir(package)` discovers declared names without loading implementations. `__all__` is the smaller wildcard-safe surface and omits imports that require extras. Explicit optional imports need their documented dependencies. The generator reads export maps and source statically so reference coverage does not depend on installed extras.
## Machine-readable reference
Start with the small [Python API index](https://cayu.dev/docs/python-api.json), select an import namespace, then fetch its alphabetic symbol index. Each name links to its HTML reference and plain Markdown. Signatures, fields and source links live in those pages rather than a multi-megabyte duplicate JSON export. Coding agents can use this together with the package-shipped `cayu guide` topics.
# Runtime guarantees and boundaries
This is a guide to the documented release's contracts. Exact type and implementation details are linked from the [Python reference](https://cayu.dev/docs/reference/python/) and the [release's runtime contracts](https://github.com/cayu-dev/cayu/blob/v0.9.1/docs/runtime-contracts.md).
## Construction and ownership
One factory call creates one process-scoped application graph. A session has its own recorded execution identity. Multiple processes coordinate through configured durable stores, claims and fences; sharing a Python object is not the coordination mechanism.
Imports remain inert with respect to active services. Server, worker and run entrypoints explicitly own long-running execution. Construction can perform bounded setup such as opening the selected local store.
## Authority
Tool discovery, argument validation, effect classification and execution authorization have different roles. Schema validity does not grant permission. Approval is scoped to its exact action and checks the normal actor/policy boundary. External content cannot authorize capabilities just because it appears in the model context.
Host-process tools are trusted application Python. Use the appropriate execution boundary for untrusted commands. Authentication establishes caller identity; application/store design establishes data isolation.
## Durable continuation
Recorded sessions retain history and continuation state. Recovery respects execution ownership and blockers. A pause awaiting input or approval belongs to that exact pending action; an unrelated message is not a replacement resolution.
External mutations need their own stable operation identities, reconciliation and receipts. A missing result may mean the mutation succeeded but acknowledgement was lost. Durable runtime state alone does not prove exactly-once business effects.
## Completion and failure
Consume terminal session evidence. `session.failed` can appear in the event stream; do not rely only on Python exceptions. Structured output success establishes schema validation, while domain assertions establish whether the content is correct.
Cancellation and cleanup are bounded lifecycle work. When cleanup cannot settle, preserve the outcome as unknown or retryable rather than inventing success. A process shutdown does not prove that a remote environment or service was removed.
## Evaluation evidence
Candidate outcome, evaluator outcome, runtime health and evidence availability remain distinct. Historical trajectory grading does not rerun a changed agent. Deterministic providers prove execution behavior for their fixtures; live quality requires a fresh candidate run and appropriate assertions.
Read [execution](https://cayu.dev/docs/concepts/execution-model/), [authority](https://cayu.dev/docs/concepts/authority/), [durability](https://cayu.dev/docs/concepts/durability/), [recovery](https://cayu.dev/docs/operate/recovery/) and [evaluation](https://cayu.dev/docs/evaluate/) for concrete applications of these boundaries.
# Build an agent that buys the right potatoes.
Build with Cayu’s Python framework. Give the model a job and assemble its harness from tools, context, and domain rules. The integrated runtime carries the work through.
We’ll rebuild the logic of the campus dining demo. One agent helps a university choose a feasible purchase, drafts a proposal, and waits for approval before recording an order.
A walkthrough of the actual repository, using its native-tool path to keep the code readable. All supplier data and purchases are fictional. Code excerpts show parts of the application; they are not a complete script to paste into one file.
Start the walkthroughView the demo code
## Context
**Dinner for 400. A shortage to solve.**
A university dining team needs to serve dinner tomorrow, but its usual potatoes are unavailable. The kitchen has some stock left and fewer staff than usual. Someone must find a replacement that provides enough food, arrives on time, and meets the budget and dietary requirements.
### The problem
The cheapest box is not always the best purchase. Whole potatoes cost less but take longer to prepare. Peeled potatoes cost more but may be the only option the available kitchen team can handle. Choosing well means checking supplier information against the meal plan, inventory, purchasing rules, and preparation time.
### Our solution
We build a dining purchasing agent with Cayu. It reads the kitchen’s requirements, investigates products, uses Python tools to calculate quantities and check constraints, and explains its recommendation. It saves a proposal for the team to review.
If the team later says more staff are available, the agent revisits the choice in the same session. When asked to place the order, it pauses for approval of the exact proposal. After approval, the application rechecks the facts, records the order, and the agent checks the receipt before reporting success. All purchases in this demo are fictional.
### What tools can the agent use?
The agent gets specific tools for the job:
- **Read the dining plan:** inspect the recipe, stock on hand, budget, delivery deadline, and staffing schedule.
- **Find supplier products:** search the catalog in the native-tool path used here. The browser version instead inspects a local supplier portal.
- **Check a product:** calculate whole cases needed, usable cooked quantity, total cost, and preparation time; check whether the option meets the purchasing rules.
- **Read guidance and ask questions:** find purchasing knowledge and ask the user for missing information. It can also request approval to save new knowledge and list available artifacts.
- **Update the plan:** record changes the user explicitly requests, such as staffing or attendance, and invalidate an outdated proposal.
- **Prepare and submit a proposal:** save a specific product and quantity for review, then request approval before recording an order.
- **Verify the result:** read the receipt for that proposal to confirm the purchase was recorded.
See the [registered tools](https://github.com/cayu-tech/campus-dining-demo/blob/d297d9eb4978a087d95e39b6672156101f2559e9/tools/registration.py) and their [business operations](https://github.com/cayu-tech/campus-dining-demo/blob/d297d9eb4978a087d95e39b6672156101f2559e9/tools/business.py).
Cayu supplies the framework and integrated runtime. The dining tools, knowledge, instructions, and approval rules form the domain-specific harness. The steps below show how those pieces become a working agent.
## The whole idea
| Part | What it does |
| --- | --- |
| Framework | Gives you the Python building blocks. |
| Harness | Combines those blocks with your domain code, tools, knowledge, and rules. |
| Runtime | Executes that setup, records progress, and handles pauses and continuation. |
| Agent | The product the user interacts with. |
Cayu includes the runtime. The harness is the system you assemble with Cayu. These are responsibilities within one application.
## Part 1 / Framework
**Get the building blocks.**
Cayu gives you types for agents, tools, policies, environments, and sessions. You decide what work they should do.
### Step 1: Start a Cayu project.
For a new application, generate the project structure with the current release:
Commands for a new project
```sh
uvx cayu new dining-agent
cd dining-agent
uv sync --extra dev
uv run --no-sync cayu check --fail-on warning
```
The generated folders give each responsibility a home. Put the agent in `agents/`, its instructions in `prompts/`, actions in `tools/`, and rules in `policies/`. Keep business calculations in domain code.
To use the existing demo instead
The demo repository already has this structure. Clone it and use its dependency lock:
Existing demo · requires Python 3.11+ and uv
```sh
git clone https://github.com/cayu-tech/campus-dining-demo.git
cd campus-dining-demo
uv sync --extra dev --extra viewer --frozen
uv run --no-sync python demo.py init
```
The demo pins a specific Cayu Git revision. It does not simply track the newest PyPI package. The full browser showcase also needs Node, Docker, browser installation, and viewer setup; follow its [README](https://github.com/cayu-tech/campus-dining-demo/blob/d297d9eb4978a087d95e39b6672156101f2559e9/README.md) for that path. Live model calls require a separately configured provider credential.
### Step 2: Define the job before the agent.
The user’s request is concrete:
> Dinner for 400 students tomorrow. Our usual potatoes are unavailable. We’re short-staffed. Prepare a draft only.
A good result is a purchase proposal that covers the meal shortfall, arrives on time, fits the kitchen’s capacity, and respects the budget and dietary requirements.
Write down what “done” means:
- **Draft request:** save a feasible proposal and explain the choice.
- **Submission request:** get approval for the exact proposal, record the order, and verify its receipt.
That definition drives the tools and tests you build next.
### Step 3: Give the agent an identity and a model.
`AgentSpec` is a Cayu building block. The demo uses it for the agent’s name and model defaults. The factory adds the configured provider and prompt.
Source excerpt · [agents/agent.py](https://github.com/cayu-tech/campus-dining-demo/blob/d297d9eb4978a087d95e39b6672156101f2559e9/agents/agent.py#L1)
```python
"""Agent identity and settings; no application construction at import time."""
from cayu import AgentSpec
from prompts.agent import system_prompt
AGENT = AgentSpec(name="cayu-campus-dining-demo", model="gpt-5.6-sol")
def build_agent(settings, provider_name):
return AGENT.model_copy(
update={
"model": settings.model,
"provider_name": provider_name,
"system_prompt": system_prompt(settings.customer, settings.browser),
}
)
```
This defines the agent. It does not give it access to meal plans or permission to place orders. You attach those capabilities next.
## Part 2 / Harness
**Give the model a way to do the job.**
Your harness is the combination of instructions, tools, data, policies, and checks around the model. This is where the agent becomes specific to campus dining.
### Step 4: Put business truth in Python.
The meal needs 80 kg of cooked potatoes. The kitchen has 20 kg. The purchase must cover the remaining 60 kg.
The application calculates the number of cases using each product’s cooked yield. It rounds up to whole cases.
Source excerpt · [catalog-app/catalog\_app/planning.py](https://github.com/cayu-tech/campus-dining-demo/blob/d297d9eb4978a087d95e39b6672156101f2559e9/catalog-app/catalog_app/planning.py#L8)
```python
def assess(product, plan, cases=None):
needed = max(
0, plan["portions"] * plan["cooked_grams_per_portion"] - plan["available_cooked_grams"]
)
usable_per_case = (
product["pack_count"] * product["grams_each"] * product["cooked_yield_bps"] // 10000
)
minimum_cases = (needed + usable_per_case - 1) // usable_per_case
cases = minimum_cases if cases is None else cases
if type(cases) is not int or not 0 <= cases <= 100:
raise ValueError("Cases must be an integer from 0 to 100")
total = cases * product["case_price_cents"]
usable = cases * usable_per_case
prep = cases * product["prep_minutes_per_case"]
```
The rest of `assess` checks recipe compatibility, supplier approval, dietary evidence, stock, budget, preparation time, and delivery.
| Option | Purchase | Cooked yield | Prep |
| --- | --- | --- | --- |
| Peeled potatoes | 7 cases / $175 | 66.5 kg | 35 minutes |
| Whole potatoes | 4 cases / $96 | 64 kg | 180 minutes |
With 45 minutes of kitchen capacity, peeled potatoes fit. Whole potatoes do not. The model gets these results from code, then explains the tradeoff.
**Your contribution:** the meaning of a feasible purchase. **Cayu’s contribution:** a way to run this calculation as an agent tool and return its result.
### Step 5: Turn business operations into tools.
A tool gives the model a named action with defined inputs. Here is the assessment tool:
Source excerpt · [tools/business.py](https://github.com/cayu-tech/campus-dining-demo/blob/d297d9eb4978a087d95e39b6672156101f2559e9/tools/business.py#L119)
```python
class AssessProduct(BusinessTool):
@property
def spec(self):
return ToolSpec(
name="assess_product",
description="Calculate the minimum whole cases required after inventory and cooked yield, exact cost, prep time and eligibility against the current meal plan and purchasing policy. Compare plausible options; this tool does not rank or choose for you.",
input_schema=schema({"meal_id": MEAL, "sku": SKU}),
effect=ToolEffect.NONE,
)
def execute(self, ctx, args):
return {
"sku": args["sku"],
**self.store.assess(self.customer, ctx.session_id, args["meal_id"], args["sku"]),
}
```
Read it in three pieces:
1. `ToolSpec` tells Cayu and the model what the tool does and what arguments it accepts.
2. `ToolEffect.NONE` declares its read-only effect contract.
3. `execute` calls the business implementation.
`self.customer` is bound by the application. `ctx.session_id` comes from Cayu. The model supplies the meal and product IDs, not arbitrary customer scope or SQL.
The shared `BusinessTool` base class wraps the return value in a Cayu `ToolResult`. Cayu can then record it and pass it back to the model.
The demo exposes other narrow actions too: inspect a plan, revise it, prepare a proposal, request submission, and verify a receipt.
### Step 6: Tell the model how to work.
The prompt supplies a method. It tells the model to read the policy, inspect the meal, and use actual quantities and constraints.
Source excerpt · [prompts/agent.py](https://github.com/cayu-tech/campus-dining-demo/blob/d297d9eb4978a087d95e39b6672156101f2559e9/prompts/agent.py#L22)
```python
Read the known knowledge entry "purchasing-policy" with read_knowledge. Inspect the relevant meal
with inspect_dining_plan before asking questions: dinner is the potato side, rice-lunch the rice side,
and vegetable-side the carrot side. These are current meal records, not a rule limiting purchases
to one ingredient. If the request does not match a meal, clarify its use and requirements rather than
pretending the existing meal plan describes it.
Use the plan's recipe quantities, inventory, remaining budget, receiving deadline, staffing capacity,
and verified dietary requirements. Do not invent missing information or silently override a customer's
explicit quantity. Existing kitchen stock reduces what must be purchased. A cheap case can cost more
per usable cooked kilogram or require more labor. Price, delivery, product form and preparation effort
all matter. Explain meaningful tradeoffs using the actual data you obtain.
```
The prompt does not name a winning SKU. The model investigates candidates, calls the assessment tool, and recommends an option using the returned evidence.
In native development mode, `search_catalog` finds products. In the browser showcase, `browser_session` inspects a local supplier portal. Both paths use the same purchasing calculations.
Instructions guide the model. Checks in the tool and business code enforce the rules when an action runs.
### Step 7: Add knowledge and control what enters context.
The demo stores purchasing guidance in Cayu’s knowledge store. The agent can read the known `purchasing-policy` entry. Its context policy can also recall relevant knowledge.
The recall source is configured explicitly:
Source excerpt · [memory/context.py](https://github.com/cayu-tech/campus-dining-demo/blob/d297d9eb4978a087d95e39b6672156101f2559e9/memory/context.py#L45)
```python
sources=AutomaticRecallSourceConfig(
include_knowledge=True,
include_transcript=False,
knowledge_required=True,
transcript_required=False,
knowledge_namespace=(settings or settings_from_environment()).namespace,
),
```
This selects knowledge from the configured namespace. It does not enable retrieval of older transcript episodes. The current session still has its conversation history.
Keep three things separate:
- **Knowledge:** durable purchasing guidance.
- **Business records:** inventory, plans, proposals, and receipts.
- **Context:** what the model sees for its next decision.
Cayu supplies storage and recall components. You decide what to store, who can access it, and what the model should receive.
### Step 8: Make approval a real gate.
A proposal is a draft. Submitting it is a separate action. The demo’s tool policy requires approval for that action:
Source excerpt · [policies/tools.py](https://github.com/cayu-tech/campus-dining-demo/blob/d297d9eb4978a087d95e39b6672156101f2559e9/policies/tools.py#L13)
```python
def build_tool_policy(names):
rules = {
"ask_user": (RequiredFieldRule("question"),),
"remember_knowledge": (DenyPatternRule("text", patterns=(r"(?s).*",)),),
"submit_proposal": (DenyPatternRule("proposal_id", patterns=(r"(?s).*",)),),
}
if "browser_session" in names:
rules["browser_session"] = (RequiredFieldRule("operation"),)
return ParameterConstrainedToolPolicy(
rules,
decision=ToolPolicyDecision.REQUIRE_APPROVAL,
execution_profile_identity=ExecutionProfileBehaviorIdentity(
name="campus-demo:approval-policy",
behavior_version="1",
implementation_version="2026-09-09.1",
),
)
```
The catch-all rule on `proposal_id` combines with `REQUIRE_APPROVAL`. When the model requests submission, Cayu pauses before the tool executes.
In current releases, write that rule as `"submit_proposal": (EveryCallRule(),)`, which applies the policy's decision to every call without a deny pattern. A call that is missing a required field, such as `ask_user` without `question`, is denied outright rather than paused for approval.
The application also defines the review fields: product, quantity, cost, plan revision, preparation, delivery, proposal ID, and digest. The digest is a fingerprint of the proposal’s contents.
The model saying “approved” does not resolve the gate. The application submits a structured decision for the exact pending call.
Tool visibility is configured separately. The demo uses `StaticToolExposurePolicy` to decide which registered tools the model can see.
### Step 9: Assemble the harness.
This is the key call in the example:
Source excerpt · [agents/registration.py](https://github.com/cayu-tech/campus-dining-demo/blob/d297d9eb4978a087d95e39b6672156101f2559e9/agents/registration.py#L8)
```python
def register_agents(
app, settings, provider_name, store, context_policy, bridge=None, recorded_tools=()
):
tools = build_agent_tools(store, settings)
if bridge is not None:
tools.extend(recorded_tools or bridge.tools)
names = [t.spec.name for t in tools]
app.register_agent(
build_agent(settings, provider_name),
tools=tools,
execution_requirements=bridge.execution_requirements if bridge else None,
context_policy=context_policy,
tool_policy=build_tool_policy(names),
tool_exposure_policy=build_tool_exposure_policy(names),
)
```
Each argument attaches a part of the system:
- `build_agent(...)`: identity, model, and instructions.
- `tools`: the actions this agent can request.
- `context_policy`: how context is assembled.
- `tool_policy`: what may execute and when approval is needed.
- `tool_exposure_policy`: what the model can see.
**This composition is the domain-specific harness.** It lives across normal Python modules. It does not require one large class named “Harness.”
## Part 3 / Runtime
**Run the system you assembled.**
The harness defines the behavior. Cayu’s runtime carries out the model-and-tool loop and maintains the execution record.
### Step 10: Wire the application and durable stores.
`build_app()` creates stores, configures the provider, selects an environment, and registers the dining agent.
The demo passes the stores into Cayu explicitly:
Source excerpt · [app.py](https://github.com/cayu-tech/campus-dining-demo/blob/d297d9eb4978a087d95e39b6672156101f2559e9/app.py#L42)
```python
app = CayuApp(
human_review_policy=OrderReview(settings),
browser_control=browser_control_options(settings),
session_store=stores.session_store,
task_store=stores.task_store,
knowledge_store=stores.knowledge_store,
knowledge_access_scope=scope,
config=runtime.config,
enable_logging=runtime.enable_logging,
request_footprint=runtime.request_footprint,
knowledge_review_namespace=runtime.knowledge_review_namespace,
)
```
The stores come from `configuration/storage.py`. It constructs `SQLiteSessionStore`, `SQLiteTaskStore`, and `SQLiteKnowledgeStore`. Projects generated by current releases call `open_application_stores(configured_database_url(), sqlite_path=...)` instead, which selects PostgreSQL when `CAYU_DATABASE_URL` is set and SQLite otherwise.
A bare `CayuApp()` has an in-memory session store and no task or knowledge store. Durable behavior depends on configuring persistent stores and keeping the underlying data.
In current releases, close the application when the process is done: `async with app:` or `await app.aclose()` waits for work in flight and shuts down its subsystems. Wrap an event stream you stop reading early in `aclosing(...)`.
| Cayu records | The dining app records |
| --- | --- |
| Session identity, transcript, events, checkpoints, pending approval. | Meal revisions, product stock, proposals, order receipts. |
The application’s `PortalStore` owns purchasing transactions. Cayu’s session store owns execution state. Both are needed.
An environment supplies tool resources. The native path uses artifacts and knowledge; the browser path adds a restricted browser environment. Native Python business tools are trusted application code, not automatically sandboxed by Cayu.
### Step 11: Start a session and consume its events.
The demo constructs a request with the user’s message and execution limits:
Source excerpt · [operations/interaction.py](https://github.com/cayu-tech/campus-dining-demo/blob/d297d9eb4978a087d95e39b6672156101f2559e9/operations/interaction.py#L25)
```python
def request(message, session_id=None):
return RunRequest(
agent_name="cayu-campus-dining-demo",
session_id=session_id,
messages=[Message.text("user", message)],
max_steps=28,
limits=run_limits(),
retry_policy=RetryPolicy(max_attempts=1, max_unknown_attempts=1),
)
```
It then calls `app.run(request)`. Cayu repeats this loop:
Build context → Call model → Check requested tool → Run tool → Record result → Next decision
The model may request another action, ask for input, or finish. Your application consumes the event stream to display progress and outcomes.
The demo limits its main interaction path to 24 tool calls, 28 steps, and 300 elapsed seconds, plus a configured token ceiling. Those runtime limits are separate from the meal’s purchasing budget.
For the original request, the agent should finish with a seven-case peeled-potato draft for $175. No order should exist.
### Step 12: Continue when the facts change.
> We now have the full kitchen team. Reconsider.
The demo loads the existing session and uses `app.resume` for the new message:
Source excerpt · [operations/interaction.py](https://github.com/cayu-tech/campus-dining-demo/blob/d297d9eb4978a087d95e39b6672156101f2559e9/operations/interaction.py#L36)
```python
async def run_or_resume(app, message, session_id=None):
existing = await app.session_store.load(session_id) if session_id else None
events = (
app.resume(ResumeRequest(session_id=session_id, messages=[Message.text("user", message)], max_steps=28, limits=run_limits(), retry_policy=RetryPolicy(max_attempts=1, max_unknown_attempts=1)))
if existing is not None else app.run(request(message, session_id))
)
async for event in events:
yield event
```
The model requests a plan revision. Application code changes preparation capacity from 45 to 240 minutes and invalidates the old proposal.
Whole potatoes now fit: four cases, 64 kg cooked, 180 minutes of preparation, $96. The agent can recommend a new draft that saves $79.
Cayu preserves the conversation and execution record. The domain code controls plan revisions and stale proposals. The model uses the new evidence to make the next decision.
### Step 13: Approve, execute, and verify.
When the user asks to submit, the model calls `submit_proposal` with the exact proposal ID and digest.
1. **Cayu pauses.** The configured policy requires approval.
2. **The application presents the proposal.** The reviewer sees the exact business facts.
3. **The human decides.** The application resolves the pending call with `app.resolve_tool_approval(...)`.
4. **Business code rechecks.** The plan, policy, product facts, stock, and proposal must still match.
5. **Business code writes a receipt.** The stock decrement and receipt are recorded in one transaction.
6. **The agent verifies.** It reads the receipt for that proposal before claiming success.
Source excerpt · [tools/business.py](https://github.com/cayu-tech/campus-dining-demo/blob/d297d9eb4978a087d95e39b6672156101f2559e9/tools/business.py#L185)
```python
class VerifySubmission(BusinessTool):
@property
def spec(self):
return ToolSpec(
name="verify_submission",
description="Independently read the purchase receipt for the exact proposal and current customer/session. Only a matching recorded receipt proves purchase.",
input_schema=schema({"proposal_id": {"type": "string", "maxLength": 64}}),
effect=ToolEffect.NONE,
)
def execute(self, ctx, args):
self.store.proposal(args["proposal_id"], self.customer, ctx.session_id)
return {
"orders": [
o
for o in self.store.orders(self.customer)
if o["proposal_id"] == args["proposal_id"]
]
}
```
Repeated submission of the same proposal returns the existing receipt. That safe repeat behavior is implemented by the dining application.
#### Where “long-horizon” enters
Work can span decisions, new facts, human waits, and process boundaries. Durable records let the application continue from what is known instead of relying on one live process.
A production integration must still handle uncertain external effects. If a supplier accepted an order but its response was lost, query the supplier or reconcile before retrying.
Cayu offers optional worker, task, and recovery machinery. This demo’s core dining flow uses direct run/resume calls. Registering a task store does not start a background worker, and this short example does not prove days-long production execution.
## Part 4 / Verification
**Prove the business outcome.**
A plausible answer is easy to generate. Test whether the agent’s actions produce the right result.
### Step 14: Test the rules, then test the model.
Start with deterministic business tests. The demo checks the exact short-staffed outcome:
Source excerpt · [tests/test\_agent.py](https://github.com/cayu-tech/campus-dining-demo/blob/d297d9eb4978a087d95e39b6672156101f2559e9/tests/test_agent.py#L14)
```python
def test_short_staffed_meal_requires_prepared_option(settings):
store = PortalStore(settings.portal_database)
options = {
p["sku"]: store.assess(CUSTOMER, "s1", "dinner", p["sku"])
for p in store.products("potatoes")
}
assert [sku for sku, r in options.items() if r["eligible"]] == ["POT-PEELED-10KG"]
result = options["POT-PEELED-10KG"]
assert (
result["shortfall_cooked_grams"],
result["cases"],
result["usable_cooked_grams"],
result["total_cents"],
result["prep_minutes"],
) == (60000, 7, 66500, 17500, 35)
assert any("preparation" in r for r in options["POT-WHOLE-20KG"]["reasons"])
assert any("deadline" in r for r in options["POT-FROZEN-10KG"]["reasons"])
assert any("dietary" in r for r in options["POT-MASH-10KG"]["reasons"])
```
Then test the runtime path with a scripted provider. It emits predetermined tool calls so you can check that submission pauses, denial creates no order, and approval produces the expected receipt.
Credential-free checks in the configured demo checkout
```sh
uv run --no-sync pytest -q tests/test_agent.py tests/test_application.py
uv run --no-sync python -m evals.run
```
The nine-case contract eval suite checks exact purchasing outcomes and the submission gate. It does not approve an order.
Finally, run live-model evaluations to check whether the model chooses the right actions from the user’s request. These require provider credentials and incur usage.
Scripted tests prove execution behavior. Live evaluations test the model’s decisions. You need evidence for both.
## What you built
You used Cayu’s framework to assemble a purchasing harness: instructions, knowledge, business tools, approval policy, and verification.
You configured its runtime to execute that harness with persistent sessions, controlled tool calls, and human pauses.
The result is an agent that can investigate a shortage, produce a feasible proposal, adapt to new facts, and record a purchase only through the configured approval path.
Cayu provides the machinery. You define what good work means in your domain.
## Source and scope
This guide follows the [campus dining demo](https://github.com/cayu-tech/campus-dining-demo/blob/d297d9eb4978a087d95e39b6672156101f2559e9/README.md) at `d297d9eb…`. Its Cayu dependency is pinned to `c5816d06…`. Every source excerpt links to the inspected revision.
The excerpts are tied to the inspected demo revision. Use the demo’s dependency lock and README to reproduce that version. The browser showcase and live-model path require additional setup; the tests described above cover different parts of the application.
Read next: [application composition](https://github.com/cayu-tech/campus-dining-demo/blob/d297d9eb4978a087d95e39b6672156101f2559e9/app.py), [proposal and receipt transactions](https://github.com/cayu-tech/campus-dining-demo/blob/d297d9eb4978a087d95e39b6672156101f2559e9/catalog-app/catalog_app/store.py), [approval tests](https://github.com/cayu-tech/campus-dining-demo/blob/d297d9eb4978a087d95e39b6672156101f2559e9/tests/test_application.py), and [Cayu’s documentation](https://cayu.dev/docs/).
This walkthrough does not execute an agent or place an order. Follow the linked source and setup instructions to run the fictional demo yourself.
# Why Cayu?
**Your expertise. An agent that puts it to work.**
Domain-specific long-horizon agents need a system shaped around the work. Cayu provides a Python framework for building that system, with model adapters, tools, context, memory, verification and evals. Its integrated runtime carries the resulting agent through execution and recovery.
Explore the frameworkStart building
## Start with the work
### The domain gives the agent its job.
A document review agent needs to know which fields matter, which sources to trust, what counts as an exception, and when a person needs to decide. Those choices determine whether the work is useful.
The model proposes a next step. The harness supplies the context, tools, domain logic, memory, policies, and checks around it. With Cayu, you build that harness using Python components and run it on the framework’s integrated runtime.
The result is your agent: a product that performs a defined job in your domain.
## Two parts of the same job
### Specific to your domain. Built for a long horizon.
**Domain-specific** describes how the agent works: the knowledge, tools, rules, and verification tailored to its job. **Long-horizon** describes how it executes: many decisions over hours or days, with new evidence, feedback, and interruptions along the way.
> “Review these 5,000 documents”
> has a finish line. Getting there takes a system.
The agent may need to inspect thousands of records, investigate exceptions, wait for an approval, and continue after a worker stops. Its state and evidence must remain available throughout that process.
[See how execution continues ↗](https://cayu.dev/framework/#runtime)
## Why one framework
### Build the behavior and its execution together.
Domain decisions have operational consequences. A tool needs an authority policy. A human review needs a durable pause. A completion check needs evidence from the run. Cayu gives those concerns a place in the same application.
Compose the context, tools, memory, policies, and verification that define the job. Keep business logic in Python, alongside the libraries your team already uses.
Sessions, state, workers, recovery, approvals, and budgets are integrated. Configure them as you build the harness, so they support the same application.
Start with defaults, extend or replace components, and choose providers and infrastructure. Evaluate the effect of each change against work from your domain.
## Why Python
### Build on the expertise your team already has.
Bring existing data pipelines, calculations, models, integrations, and evaluation code into the agent. The harness remains ordinary Python that your team can read, test, and change.
Models and providers can evolve while your domain logic stays in the application. Cayu exposes replaceable components for those choices; your evaluations tell you whether a change improves the work.
## When Cayu fits
### When the work needs more than one response.
Cayu is especially useful when a domain-specific job involves real systems, persistent context, many model and tool calls, human decisions, cost limits, or recovery across a long execution.
For a single request with no persistent state or operational lifecycle, you may need fewer of these capabilities. Start from the requirements of the job.
[Inspect the implementation comparison ↗](https://cayu.dev/comparison/)
## From our work to yours
### Built while building agents.
Cayu was extracted from the production runtime behind an agent-operated software factory that built and deployed thousands of business applications. Specialized agents worked together as its SRE, product manager, coder, and forward-deployed engineering assistant.
We started with agent SDKs and frameworks, which made the model-and-tool loop quick to build. Production quality needed control of that loop and of everything around it: durable state, credentials, execution environments, human intervention, recovery, cost attribution, and evaluation. We brought those pieces together in an open-source framework.
Run Cayu on your own infrastructure, with its packaged operator dashboard. Cayu Cloud is an optional shared workspace for building, deploying, operating, and evaluating agents across a team. It is currently invite-only.
[Cayu for the enterprise ↗](https://cayu.ai/)
## Domain-specific long-horizon agents
### Build around your work.
Get startedExplore the framework