Cayu generated applications
This guide ships with the Cayu package, so the installed version is always available offline with cayu guide applications. Release source.
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
Section titled “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,ToolEffectmembers, and a nested literalExecutionProfileBehaviorIdentity(...)or a module-level constant holding one. This is the shapecayu generate toolemits. logging.getLogger(__name__)or with a literal name.- Dataclass fields
field(default=<literal>)orfield(default_factory=list|dict|set|tuple|frozenset)with literal flags. Enum/StrEnum/IntEnum,TypedDictandNamedTupledeclarations with annotations and literal values.@functools.cache,@functools.lru_cacheand@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
Section titled “Planning”Discover the package-shipped catalog before choosing a plan:
uv run --no-sync cayu new --list-presets --jsonuv run --no-sync cayu new --list-capabilities --jsonuv run --no-sync cayu new --explain knowledge --jsonResolve the full plan without writing:
uv run --no-sync cayu new my_agent --preset agent --provider neutral --dry-run --jsonDry-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:
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" --jsonThe 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
Section titled “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/datain 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/datawith[storage] local_files = [{ path = "...", reason = "..." }]incayu-cloud.toml.- Deployments set
CAYU_DATABASE_URLto a migrated PostgreSQL database, runcayu storage migrateas a deploy step, and setCAYU_REQUIRE_POSTGRES=1so 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_URLgives the task-admissionLISTENconnection a direct server address.- Deployments of applications with automatic memory must set
CAYU_MEMORY_EVIDENCE_KEY; the localdata/memory-evidence.keyis 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
Section titled “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:
[tool.cayu.scaffold]convention = 1preset = "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()intools/registration.pyand construct theLocalArtifactStoreandEnvironmentinenvironments/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 inknowledge/retrieval.py, and wire the environment inenvironments/local.py. RegisterListKnowledgeTool,SearchKnowledgeTool,ReadKnowledgeTool, andRememberKnowledgeTooltogether for one agent intools/registration.py; authorize proposal effects inpolicies/tools.pyand give the changed tool and policy behavior explicit versioned identities for recovery. - Delegation: define and explicitly register child agents in
agents/, constructSubagentToolandSubagentResultToolwith the owning app and stores throughagents/registration.py, and keep lifecycle work inoperations/. 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 <capability>
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
Section titled “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:
cayu cloud deployment timeline DEPLOYMENT_ID --application AGENT_SLUGcayu cloud deployment logs DEPLOYMENT_ID --application AGENT_SLUGFor 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
Section titled “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:
understand -> inspect -> plan -> change -> test -> eval -> exercise -> report evidenceAfter a change, run the exact commands emitted by cayu new; at minimum:
uv run --no-sync cayu inspect --jsonuv run --no-sync cayu check --fail-on warning --jsonuv run --no-sync pytestuv run --no-sync cayu eval runRun 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.