Are you an LLM? You can read better optimized documentation at /docs/agent-guard/custom-runtimes/agent-definition.md for this page in Markdown format
Declare a runtime in an agent definition
The agent definition is where a custom runtime gets its authority. It decides which image runs, which models it may call and with which credentials, which tools and skills it gets, which policies apply, and which values reach it. For the definition format in general, see What is an agent definition.
A complete example
yaml
apiVersion: agentguard.jozu.dev/v1
kind: AgentDefinition
metadata:
name: triage-agent
spec:
framework: jozu.ml/acme/triage-agent:1.0.0
needs:
- name: OPENAI_API_KEY # read from the host environment
- name: TICKET_QUEUE
from:
text: platform-oncall # a literal
includes:
- name: assistant
type: llm
source:
model: openai/gpt-4o-mini
credential: OPENAI_API_KEY
- name: tickets
type: mcp
source:
modelkit: jozu.ml/acme/tickets-mcp:2.1.0
- name: runbooks
type: skill
source:
modelkit: jozu.ml/acme/runbook-skills:1.4.0
- name: triage-rules
type: policy
source:
modelkit: jozu.ml/acme/triage-policies:1.0.0The application in jozu.ml/acme/triage-agent asks for the model assistant, calls the tickets tools, reads the runbooks skills, and finds TICKET_QUEUE in its environment. It never sees OPENAI_API_KEY: that value goes to the Jozu AI Gateway.
spec.framework
spec.framework names either a framework Agent Guard integrates (claude-code, codex, openclaw, antigravity, the same names agentguard run takes) or a container image.
An image reference starts with its registry host: a name containing . or :, or localhost. Agent Guard assumes no default registry, so a short reference such as python:3.12 is refused.
yaml
framework: jozu.ml/acme/triage-agent@sha256:3b1f...Referencing the image by digest is recommended, so that every run of the definition uses the same image.
The image must meet the requirements in Build a runtime image. Agent Guard checks them before the microVM boots.
When a derived definition inherits from a base, it inherits spec.framework. If it restates it, the value must match the base's exactly, tag or digest included.
llm modules
Each llm module is a model the application may call, registered with the Jozu gateway by the module's name. The application asks the Jozu gateway for assistant; the Jozu gateway sends the request to the module's provider, as the module's model, with the module's credential. A model name that matches no module is refused.
yaml
- name: assistant
type: llm
source:
model: openai/gpt-4o-mini # <provider>/<model>
endpoint: https://api.openai.com # optional; the provider's server root
credential: OPENAI_API_KEY # a need declared in spec.needssource.model is <provider>/<model>. The Jozu gateway sends the part after the first / to the provider. It supports the providers anthropic, bedrock, cohere, gemini, huggingface, openai, and replicate, but only anthropic, openai, and gemini modules can be given a credential.
source.endpoint is optional and overrides the provider's default URL, for an OpenAI-compatible server of your own, for example. Give the server's root, such as https://llm.internal.example.com. The Jozu gateway appends the API path itself, so an endpoint ending in /v1 produces a request to /v1/v1/....
credential names the need the module authenticates with, declared in spec.needs by this definition or a base. The Jozu gateway asks Agent Guard on your Mac for the value before it serves the module and sends it with the module's requests. The need is not set in the container. credential is accepted only when spec.framework is an image, because an agent CLI reaches its provider itself, with its own key; a CLI agent gets its credentials through --pass-env or its own login (see Pass credentials). How the gateway holds the value, and what that does not protect against, is in Credentials for allowed hosts.
When a module declares a credential, the variables its provider's credential conventionally goes by are kept out of the container, whichever need holds them:
| Provider | Variables withheld |
|---|---|
anthropic | ANTHROPIC_API_KEY |
openai | OPENAI_API_KEY |
gemini | GEMINI_API_KEY, GOOGLE_API_KEY |
The run prints a line for each name it withheld, with the consumers that use it:
OPENAI_API_KEY withheld (llm:assistant)If the credential is an optional need that does not resolve, the run skips the module for the session and says so:
module assistant: skipped, credential need OPENAI_API_KEY did not resolveenv is read only as another spelling of credential. It is accepted when it holds exactly one variable the table lists for the module's provider and that variable's value is exactly one ${needs.NAME} reference: OPENAI_API_KEY: ${needs.OPENAI_API_KEY} under env is read as credential: OPENAI_API_KEY, with a warning. Any other env stops the run before the microVM boots: a literal value, a variable the table does not list for the provider, more than one variable, or env beside credential. A secret belongs in a need. env on a module of any other type is refused.
A module with no credential passes the request's model name to the provider unchanged, so the module's name must be the provider's model name. Module names allow only letters, digits, ., -, and _, so a model whose name has another character, such as meta-llama/Llama-3.1-8B-Instruct on an OpenAI-compatible server, needs a credential, even a need holding a placeholder the server ignores, such as a from.text literal.
A module the Jozu gateway cannot serve is dropped for the session, with a line in the boot log naming the module and the reason, and a request for it is refused like any undeclared model. That happens when a module has no provider prefix and no endpoint, names a provider outside the list above, declares a local ModelKit model (source.modelkit), or declares neither a credential nor an endpoint.
A module named after a provider, such as openai, gets a renamed entry inside the Jozu gateway. The application still calls it by the module name.
mcp modules
mcp modules work as they do for a CLI agent: a source.modelkit bundle runs inside the microVM, and a source.endpoint with transport: http reaches a remote server. tools: narrows a module to the tools it lists. Local MCP servers run outside the application's container, under their own account, and see the workspace at the same path the application does.
The application reaches every module's tools through one gateway endpoint, and every call is evaluated against the definition's policies. See Calling tools.
skill modules
Each skill module is copied into the container's skills directory, one directory per skill, read-only. The application reads them itself. If two skills have the same name, the second is dropped and the boot log says so.
policy modules
policy modules apply to a custom runtime as they do to any agent. GuardrailPolicy is evaluated on model requests and responses, and ToolPolicy on MCP tool calls, together with the policies installed with agentguard policy add. A derived definition cannot remove them. See How policies work.
Policy sees what passes through the Jozu gateway: model requests and MCP tool calls. An action the application takes directly, without the Jozu gateway, is governed only if the application asks for a decision itself. See Asking for a decision on your own actions.
spec.needs
spec.needs is the only way a value from the host reaches the application. Each need names a variable, and its value comes from a resolver:
yaml
needs:
- name: GITHUB_TOKEN # from the host variable GITHUB_TOKEN
- name: TICKET_API_TOKEN
from:
envFrom: ACME_TICKETS_PAT # from a differently named host variable
- name: TICKET_QUEUE
from:
text: platform-oncall # a literal
- name: SENTRY_DSN
optional: true # absent, rather than an error, if unset- A need with no
fromreads the host environment variable of the same name. - A required need that does not resolve stops the run before the microVM boots, naming the host variable to set.
- An optional need that does not resolve is absent from the container, not empty.
- A need an
llmmodule names incredentialgoes to the Jozu gateway and is not set in the container. - A need's
targetdefaults toenv, the only target that is delivered; see Limitations.
The container's environment is built only from the needs the definition declares, so nothing can be added to it at run time (--pass-env and --pass-cloud-creds do not reach it), and the definition is a complete list of what the application receives.
