Skip to content
Open Console

Agent manifest

16 min readUpdated

agent.yaml is the deployable contract for one agent version. This page describes the current authoring fields: what each setting accepts, what it defaults to, and what rejects a manifest at registration.

For a guided introduction to the fields you touch first, read Manifest essentials.

Two rules apply everywhere:

  • Unknown top-level keys are rejected. A typo fails the manifest rather than being ignored. The one exception is a key prefixed x-, which is accepted and carried through untouched.
  • A manifest is validated against a version, not an agent. Settings are captured at registration and are immutable for that version; you change them by publishing a new version.

Most agents need about this much. Every field links to the section that defines it.

agent.yaml
name: support-copilot # stable agent identity
version: 0.2.0 # labels one immutable build
framework: claude-agents-sdk # harness adapter
entrypoint: agent:create_agent # python_module:function
description: Triage and draft responses to support requests
kind: interactive # interactive | background
identity_mode: service # pinned by the first version
model: claude-sonnet
permitted_models: [claude-sonnet, claude-haiku]
persistent: true # required for hosted versions
session:
idle_timeout: 30m
max_lifetime: 24h
resources:
cpu: 100m
memory: 512Mi
network:
egress:
- api.zendesk.com # bare hostname, within the tenant ceiling
policy:
approval_required_tools: [Bash] # a person decides before these run
enable_ask_user: true
scope_ceiling: [llm:claude-sonnet:invoke]
allowed_audiences: [https://api.tanomis.com]
integrations:
support:
connector: global/support-desk # use an existing configured connector
required: true
tools:
allow: [search_tickets, draft_reply]
approval_required: [draft_reply]

Grouped by the section that defines each field. “Hosted: yes” means the hosted control plane requires it; lenient non-hosted profiles may default it.

Field Type Required Default
name string yes —
version string in practice —
framework enum yes —
entrypoint string yes —
description string no empty
kind enum yes —
Field Type Required Default
model string no resolved by the platform
permitted_models list of string no empty
permitted_tools list of string no empty
Field Type Required Default
identity_mode enum hosted: yes service only in lenient non-hosted profiles
inherits_user_permissions boolean no false
identity.client_id string no assigned at registration
Field Type Required Default
persistent boolean hosted: yes false only in lenient non-hosted profiles
cr_enabled boolean deprecated compatibility field ignored for runtime selection
settlement_mode enum no standard
session.idle_timeout duration yes —
session.max_lifetime duration yes —
resources.cpu quantity yes —
resources.memory quantity yes —
resources.cpu_limit quantity no platform burst default
resources.memory_limit quantity no platform burst default
Field Type Required Default
network.egress list of string no empty
network.egress_mode allowlist or public no allowlist
policy.* block no see the section
integrations map of requirement no empty
access.github block no none
delegation.* block no empty
Field Type Required Default
data_classification list of string no empty
cloud_access list of string no empty
cost_cap_usd number no 0
token_cap integer no 0
lifecycle_policy block no empty
harness.<framework> block no harness defaults
autonomy any no accepted, not yet applied
name: support-copilot
version: 0.1.0
framework: claude-agents-sdk
entrypoint: agent:create_agent
description: Triage and draft responses to support requests.
  • name — the stable agent identity, and the name every other command refers to. Must start with a letter or digit and contain only letters, digits, underscore, dot, or dash, up to 128 characters. A deploy that names a different agent than the manifest does is rejected.
  • version — the label for one immutable build. Validation does not require it, but deploy does: supply it here or pass --version on the command line. A published version is immutable, so publish a new label rather than rebuilding an existing one.
  • framework — the harness adapter. One of claude-agents-sdk or deepagents; confirm the set your control plane offers with tanomis agent harnesses.
  • entrypoint — module:function, resolved inside your packaged project. Exactly one colon, with both halves non-empty.
  • description — free text shown in listings. Optional, and worth writing: it is what a person scanning the agent catalog reads.
kind: interactive

kind is required and is one of:

Value Meaning
interactive Human in the loop, turn by turn. Latency-sensitive resource profile.
background Runs without a person waiting on each turn.

autonomy is accepted and stored verbatim but is not yet applied to behavior. Set it only if you want the declaration on record.

model: "your-workspace-model-id"
permitted_models:
- "your-workspace-model-id"
  • model — the default model resolved for a run. default follows the organization’s default model, resolved at each session start; any other value pins that model.
  • permitted_models — the runtime allowlist. A run that requests a model outside this list is refused. Leaving it empty does not widen access; it means the manifest declares no allowlist and the model decision falls to platform and tenant policy.

Use model IDs available to the selected tenant. Whenever you change model, update the matching llm:<model-family>:invoke entry in policy.scope_ceiling, or the declared ceiling stops describing the model the agent actually calls.

identity_mode: service
inherits_user_permissions: false
  • identity_mode — the agent’s intended authority posture. One of service (the agent’s own machine identity), delegated (intended user subject plus agent actor), or hybrid. Hosted deployments require an explicit value and reject omission; a lenient non-hosted profile may resolve a missing value to service. The first non-rejected version pins it across every later version of the agent; a rejected initial attempt does not establish the pin, so agree the mode with your organization administrator before the first deploy.
  • inherits_user_permissions — the gated impersonation opt-in. Illegal with identity_mode: service, because there is no user to inherit from; use delegated or hybrid. Turning it on seeds an impersonation-approval gate rather than granting anything on its own.
  • identity.client_id — the agent’s application identity. On a hosted control plane this is assigned at registration and an author-supplied value is rejected, so omit the identity block entirely.

Availability: Service-mode bind-time identity is enabled per tenant; delegated and scoped authority are held contracts. See What is available where.

For the actor model, hybrid-path boundary, denial sequence, and safe attribution guidance, read Agent identity and access.

persistent: true
session:
idle_timeout: 30m
max_lifetime: 24h
  • persistent — provisions durable tenant-scoped storage and gives each session its own directory. It is required for new hosted versions.
  • cr_enabled — deprecated. Older manifests may retain it during the internal rolling deployment, but true, false, and omission do not select a runtime in current runtime-api. The deploy pipeline and CLI still use true temporarily to request a golden build; that legacy consumer is removed by the golden-policy follow-up. RAM snapshot policy is platform-owned. Until universal-worker and golden-policy cutover, remote admission requires true for settlement or author-declared egress; that is a rollout barrier, not a tenant-facing compatibility period.
  • settlement_mode — one of standard, work, or settlement, in that order of strength; a missing value resolves to standard. standard is the existing behavior. work means the agent produces durable work records. settlement requires the session to establish the managed worker/proxy boundary or fail activation.
  • session.idle_timeout / session.max_lifetime — both required. Compact Go-style durations (45s, 30m, 24h), positive, and resolving to whole seconds. 1500ms is rejected.

Availability: A snapshot is an optimization, not the execution-boundary selector. Durable work and settlement are enabled per tenant; a settlement-mode session fails activation where the boundary is unavailable. See What is available where.

resources:
cpu: 100m
memory: 512Mi
cpu_limit: 2
memory_limit: 2Gi

cpu and memory are the requests — the reserved baseline that drives scheduling and density. They are required. cpu accepts a Kubernetes CPU quantity (500m, 2); memory accepts a binary quantity (512Mi, 2Gi) — decimal suffixes like 500M are rejected.

cpu_limit and memory_limit are the optional burst ceiling. When unset the platform applies a generous default so a run can spike above its request. Set them to raise or pin the ceiling for one agent, subject to tenant policy.

Leave the requests at their scaffolded values until a measurement gives you a reason to change them.

network:
egress:
- github.com
- api.example.com

network.egress_mode defaults to allowlist. Set it to public to permit arbitrary public hostnames through the managed proxy. In public mode, egress must be absent or empty. An unset tenant ceiling permits this mode; any configured hostname ceiling rejects it. Private and reserved addresses remain blocked. Public mode requires capable node/helper versions and the external route; see Restrict network egress.

network.egress is the list of external hostnames this agent may reach. It is part of the version’s contract: captured at registration, immutable for that version, and enforced at run time.

Availability is per tenant. Ask your administrator whether author-declared egress is enabled for the selected tenant before relying on it; where it is not, the list is still validated and recorded but no external route exists.

Each entry is a bare hostname, written in canonical form — lowercase, no surrounding whitespace, no trailing dot. Anything else is refused at deploy rather than quietly rewritten, so the value you wrote is the value that is enforced.

Refused, each with the reason named:

Not allowed Why
*.github.com Wildcards are not permitted in a declared ceiling. An entry already covers its subdomains.
https://github.com, github.com:443, github.com/path The match is on host alone; a scheme, port, or path would be silently ignored.
140.82.121.4 An address carries no DNS identity, and the allowlist matches the host an agent asks for.
the loopback name, svc.cluster.local, printer.local, vault.internal Names for the sandbox itself or the in-cluster world. Platform destinations are added for you and are not declarable.
GitHub.com, github.com. Not canonical. Write github.com.
github A hostname needs at least two labels.

An entry covers its subdomains. github.com admits api.github.com and a.b.github.com. It does not admit notgithub.com or github.com.example. Declare the narrowest name that covers what you need.

Your administrator can set a per-tenant ceiling that your declaration is intersected with. It only ever narrows: it cannot add a destination you did not declare, and it never affects the platform’s own routes.

An unconfigured tenant admits any public hostname, so an unset ceiling behaves as though only your manifest applied. Where a ceiling excludes an entry, the deploy fails naming both the entry and the ceiling that excluded it, before any image is built — so you find out in seconds rather than after a build.

A ceiling of github.com admits a declaration of api.github.com, because that is narrower than what the administrator permitted.

  • At deploy. The grammar and the tenant ceiling, before a build is created.
  • At rollout. The ceiling again, when the version is enabled for a tenant — the point at which a globally published agent first has a tenant to be measured against.
  • At run time. Every connection is mediated. A destination outside the list is refused. A declared hostname that resolves to a private, loopback, link-local, or in-cluster address is refused too, and the connection is made to the address that was checked, so a name whose record changes cannot redirect it.

The platform’s own destinations — the managed model gateway and the governed connector routes — are always available and are never declared here. Your list can only add to them.

Egress enforcement runs in the managed worker’s session-scoped proxy for both snapshot restore and clean bootstrap. If that boundary cannot be established, activation is refused instead of running unproxied. During the internal mixed-runtime rollout only, remote admission may require the legacy cr_enabled: true spelling so an older runtime replica cannot accept a contract it cannot enforce.

policy:
approval_required_tools:
- Bash
enable_ask_user: false
enable_claude_md: false
enable_context_files: false
scope_ceiling:
- "llm:your-model-family:invoke"
allowed_audiences:
- "https://your-workspace-api.example"
approval_thresholds:
- condition: "scope:payments.*"
approver: workspace-admin
  • approval_required_tools — every listed tool pauses the run and emits a permission request that the console renders as an allow/deny card. Names are the harness’s own tool names.
  • enable_ask_user — exposes the harness question flow, letting the agent ask a person a question mid-run and wait for the answer.
  • enable_claude_md — claude-agents-sdk only. Lets the agent read a project CLAUDE.md from its workspace as a context file. Ignored on other harnesses.
  • enable_context_files — accepted for forward compatibility with a broader context-file surface. Today the only such surface is gated by enable_claude_md.

approval_required_tools and enable_ask_user produce the durable runtime interactions described in Handle approval-required actions. That application flow is separate from approval_thresholds below, whose scoped-token completion path remains held.

  • scope_ceiling — the maximum set of concrete capabilities this agent’s tokens may ever carry. The broker intersects requested scopes with it; it never widens them. Each entry must match exactly one of three grammars, and wildcards are rejected:

    Form Example
    <resource>.<action> calendar.read
    mcp:<server>:<tool>:<verb> mcp:github:create_issue:write
    llm:<model>:<capability> llm:your-model-family:invoke

    On a hosted control plane the ceiling is required; where it is optional, an absent ceiling means allow-all with a warning.

    What the ceiling governs. It bounds what this agent may do acting as itself, in two places:

    • Agent-to-agent delegation. On a delegated call the caller’s ceiling is intersected with the callee’s, and only scopes inside both are granted. An agent with no ceiling cannot receive a delegated grant at all.
    • Token exchange (held contract). If a future supported flow exchanges a token against one of the agent’s allowed_audiences, a requested scope outside the ceiling is refused. No supported public runtime workflow currently exposes this scoped exchange.

    What it does not govern. Three settings look like “what may this agent do”, and they are separate mechanisms — sizing the ceiling for these will not work:

    Concern Where it is actually enforced
    Which model a run may use permitted_models
    Which MCP tools an agent may call integrations.<key>.tools.allow intersected with the current workspace grant
    What the agent may do as itself scope_ceiling

    In particular, an mcp:<server>:<tool>:<verb> entry is a well-formed ceiling scope, but adding one does not grant an MCP tool call and omitting one does not block it — see Governed MCP connectors. Entries are captured at registration and are immutable for that version, so declare the ceiling you need and widen it in a later version rather than padding it defensively.

  • allowed_audiences — the https:// resource indicators the agent’s minted tokens may target. Required once scope_ceiling is non-empty, and each entry must be an https:// URI. Use the audience issued for your tenant/environment; never copy one from another environment.

  • approval_thresholds — maps a sensitivity condition to an approver. In the held delegated exchange contract, matching a sensitive scope classifies the request as approval-required; no supported durable approval ID, decision endpoint, or resume path currently completes that exchange. condition is required and is a sensitivity pattern, so unlike a grant it may use wildcards. approver is optional and defaults to the invoking user. See Agent identity and access before designing around these fields.

Both scope_ceiling and allowed_audiences are re-checked for presence on each new version, so a new version can widen them — which is exactly the decision an agent’s author should be making deliberately.

permitted_tools: []
data_classification:
- internal
cloud_access:
- object-storage
cost_cap_usd: 25
token_cap: 2000000
lifecycle_policy:
required_gates:
- regression-suite
- prompt-injection-suite
gate_categories:
prompt-injection-suite: adversarial
rollback: manual
  • permitted_tools — the tool allowlist carried into the policy decision point. An empty list declares no manifest-level restriction; enumerate tools to narrow what the agent may run.
  • data_classification and cloud_access — the data sensitivity and cloud-resource classes the agent declares. Recorded on the version and surfaced through the catalog for review.
  • cost_cap_usd and token_cap — declared budgets for the version. Must not be negative.
  • lifecycle_policy.required_gates — the evaluation gates that must pass before the gate-guarded promotion. lifecycle_policy.rollback is manual or auto; empty resolves to manual.
  • lifecycle_policy.gate_categories maps a required gate to the kind of eval suite behind it: quality, robustness, or adversarial. A gate left out is quality. Each key must name a gate in required_gates, and any other value is refused at registration. The version’s gate listing (GET /v1/agents/{name}/versions/{version}/gates) returns the category on every gate so robustness and adversarial suites can be shown apart from quality evals.

data_classification, cloud_access, cost_cap_usd, and token_cap are recorded as version metadata and exposed for governance review. Treat them as the agent’s declaration of intent, and keep the enforcement in permitted_models, permitted_tools, and policy.scope_ceiling.

An agent references a configured connector directly. No catalog association, contract publication, or connection-selection step is needed.

agent.yaml
integrations:
finance:
connector: global/alphavantage-workspace
required: true
tools:
allow: [SYMBOL_SEARCH, COMPANY_OVERVIEW, EARNINGS]

Find the connector and its approved tools:

Terminal
tanomis mcp connector list
tanomis mcp connector tools CONNECTOR_ID

Use global/NAME for a platform connector, tenant/NAME for a connector owned by the agent’s workspace, or an exact connector ID. An unqualified name works when unambiguous. A global agent can reference global connectors; publish a tenant-scoped agent when it needs a tenant-owned connector. The integration map key is just the agent’s local name.

The connector supplies the endpoint, credentials and shared/personal account mode. Personal connectors still require each caller to connect their own account. Credentials never belong in the manifest. An optional identity: workspace or identity: user can assert the expected mode; it must match the connector.

tools.allow is the maximum set the agent may use. Optional tools.approval_required lists tools that require confirmation for each call and must be a subset of tools.allow. The connector’s current permitted tools and account access can narrow this set, never expand it.

At registration, the platform records the connector ID and approved tool schemas automatically. These platform-owned details appear separately from author configuration in the Manifest tab. Deleting and recreating a connector with the same name does not redirect an existing version.

An agent names the GitHub repositories it may use, and what it may do in each, under access.github. Each run gets a GitHub token for exactly those repositories and capabilities; git and gh use it automatically.

agent.yaml
access:
github:
repos:
anyfind/agent-platform-website-tanomis:
can: [read, push, pull_request]
anyfind/docs:
can: [read]
Capability Lets the agent GitHub App permission
read clone, fetch, read files and metadata (always included) contents: read
push push branches contents: write
pull_request open and update pull requests pull requests: write
issues create and comment on issues issues: write

There is no separate merge capability: push and pull_request together can merge a pull request unless branch protection forbids it.

A global agent cannot know a customer’s repositories, so it declares a named slot instead. Each workspace chooses the repository for the slot when it enables the agent (Agent → Integrations, or tanomis agent access NAME --bind target=owner/name):

agent.yaml
access:
github:
choose:
target:
can: [read, push, pull_request]

What decides access:

  • The tenant’s GitHub ceiling. A workspace admin sets which repositories agents may be given, and how far, in Settings → Organization → GitHub (or tanomis github ceiling set). A declaration outside the ceiling is refused at deploy or enablement with the gap named. Lowering the ceiling later makes affected runs fail with the same reason.
  • The GitHub App installation. A repository the workspace’s connected installation cannot see is refused at deploy.
  • Every run. The intersection is checked again when a run starts and on every token request, and tokens are revoked when the run ends.

Rules:

  • Repository names are GitHub owner/name; owner/* is a ceiling pattern, not a declaration.
  • Slot names use lowercase letters, digits, - and _.
  • github.com and api.github.com are added to network.egress automatically. That opens those hosts for every path: the run’s token limits what the agent can do on GitHub with platform credentials, not which GitHub repositories it can send data to.
  • asks_first is not supported yet and is rejected rather than ignored.
  • The retired top-level repository: block is rejected with a rewrite hint, including when an older version that carries it is cloned or fanned out.

See GitHub repositories for connecting GitHub and setting the ceiling.

  • Changing the connector or allowed tools requires a new agent version.
  • Rotating credentials on the same connector does not require a new version.
  • Changed tool schemas require review and a new agent version.
  • Agent → Integrations shows the declared connector, readiness and expandable allowed tools. Operators can disable or enable access for the agent; they cannot substitute another connector.
  • A required connector that is unavailable blocks execution. An unavailable optional connector is omitted.
  • Disabled access remains disabled when publishing another version or migrating an old declaration.

Existing catalog-based agent versions remain compatible. New agents should use the direct connector declaration above. No attachment creation or approval workflow is involved.

# caller
delegation:
calls:
- summarizer
- agent: reconciler
skills: [reconcile]
# callee
delegation:
accepts:
- tenant
skills: # optional
- name: summarize
description: Summarize a support thread and return the key facts and the customer's ask.

The block has two independent halves. An agent may call, be called, both, or neither, and declaring neither is the closed default. The manifest is the grant: a caller names the agents it may hand work to, a callee names who may call it, and the intersection of the two, evaluated against the versions currently enabled for the tenant, is the authority. Nothing is created out of band and nothing has to be redone when either side deploys a new version.

  • calls — the agents this agent may delegate to. Naming a target puts the delegation tools on this agent’s bind, and it is the caller-side ceiling: an agent cannot reach a callee it did not name. An entry is a bare agent name, or a mapping with agent and a skills list that narrows the target to those skills. No wildcards.

  • accepts — who may delegate to this agent. Empty means nobody.

    Value Meaning
    tenant Any agent in the same tenant. The widest class there is.
    self Other versions of this same agent only.
    <agent-name> That one named agent.
  • skills — optional. A callee that declares none exposes one implicit skill, default, whose input is a prompt plus any further fields the caller passes, described by the agent’s own description. Declare skills only when the agent offers several distinct typed operations. name is lowercase alphanumeric with underscores or dashes and unique within the list; renaming one breaks every caller that lists it. description is required and functional, not decoration: a calling model reads it to choose a skill. input_schema is optional; when present it is enforced on every call. Schema hashes are derived by the CLI, never written by hand.

skills without accepts is rejected: the skills would be unreachable. accepts without skills is fine and is the common case.

Keep only the block matching framework. Unknown keys inside a harness block are rejected at warmup with the offending key named.

harness:
claude-agents-sdk: {}

The Claude Agent SDK harness takes no manifest options today; the empty block is the marker. Its human-in-the-loop behavior comes from policy.approval_required_tools, policy.enable_ask_user, and policy.enable_claude_md.

harness:
deepagents:
is_async: true
stream_modes: [messages, updates, custom]
checkpointer: workspace-sqlite
backend: workspace-composite
recursion_limit: 50
Key Values Default
is_async boolean true
stream_modes any of messages, updates, custom, values [messages, updates, custom]
checkpointer workspace-sqlite, in-memory workspace-sqlite
checkpointer_options.delta_channels boolean true
store workspace-sqlite, none none
backend workspace-composite, author-provided workspace-composite
allow_agent_model_override boolean false
allow_author_checkpointer boolean false
allow_author_store boolean false
recursion_limit positive integer 200
recursion_limit_ceiling positive integer 1000
enable_repl_middleware boolean false
subagent_event_attribution nested, flat nested

recursion_limit must be less than or equal to recursion_limit_ceiling. The runtime clamps the effective limit to the ceiling, so the ceiling is the real bound on a single run’s step count.

Leave backend, checkpointer, and the three allow_author_* switches at their defaults unless you are deliberately taking ownership of workspace state; the defaults are what keep the workspace and lifecycle under the runtime’s control.

These are the checks that reject a manifest which is individually well-formed:

  • New hosted versions require persistent: true.
  • During the internal mixed-runtime rollout only, remote admission may require legacy cr_enabled: true for settlement or author-declared egress. The manifest validator itself does not make that field authoritative.
  • inherits_user_permissions: true is illegal with identity_mode: service.
  • A non-empty policy.scope_ceiling requires a non-empty policy.allowed_audiences.
  • integrations.<key>.tools.approval_required must be a subset of that integration’s tools.allow.
  • delegation.skills requires a non-empty delegation.accepts.
  • harness.deepagents.recursion_limit must not exceed recursion_limit_ceiling.
Terminal
tanomis agent deploy . --tenant northwind-sandbox --dry-run

--dry-run runs the full preflight, including the authorization contract, and packages locally without creating a build or uploading anything. Use it: on the remote build path, registration is the last step, so a manifest error found there costs you an image that has already been built and pushed.