# 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 walkthrough View 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 framework Start 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 started Explore the framework