Recurse docs / 14

agent.yaml

Your coding agent writes agent.yaml as the contract for a Recurse agent. Use this reference to review the identity exposed to callers, runtime, prompt, input and output schemas, and registered tools. Field names are case-sensitive, and unknown fields in the manifest's structural mappings are rejected.

The manifest has eight required top-level fields: apiVersion, kind, metadata, runtime, agent, inputs, outputs, and tools.

Complete example

This manifest shows the complete shape. The prompt, Python tools, project metadata, and lockfile are separate project files.

YAML
apiVersion: recurse.run/v1alpha1
kind: Agent
metadata:
  name: tiny-tuner
  title: Tiny Model Tuner
  summary: Tunes a tiny classifier across feature and training configurations.
runtime:
  python: "3.14"
  lockfile: uv.lock
agent:
  prompt: prompt.md
  model: gpt-5.6-luna
inputs:
  $schema: https://json-schema.org/draft/2020-12/schema
  type: object
  required: [target_f1, max_trials]
  properties:
    target_f1:
      type: number
      minimum: 0
      maximum: 1
    max_trials:
      type: integer
      minimum: 1
    task:
      type: string
      default: |
        Find a model with validation F1 >= {{ input.target_f1 }}.
        Run no more than {{ input.max_trials }} trials.
  additionalProperties: false
outputs:
  $schema: https://json-schema.org/draft/2020-12/schema
  type: object
  required: [best_f1, target_reached]
  properties:
    best_f1:
      type: number
      minimum: 0
      maximum: 1
    target_reached:
      type: boolean
  additionalProperties: false
tools:
  source: tools.py
  built_in: true
  register:
    extract_features: ~
    train_model:
      volatile: true
    validate_model: ~
    save_best_model:
      storable: false

Quote values that YAML could interpret as numbers or booleans. ~ is YAML null and has a specific meaning for tool registration.

Before approving the manifest, review:

  • Whether the name, summary, inputs, and outputs form a clear interface for callers.
  • Whether the prompt, model, runtime, and lockfile identify the intended revision.
  • Whether every public tool is registered with settings its implementation guarantees.
  • Whether paths stay inside the packaged project.
  • Whether defaults and optional fields behave correctly when omitted.

Define the agent identity

FieldTypeRequiredContract
apiVersionString literalYesExactly recurse.run/v1alpha1.
kindString literalYesExactly Agent.
metadataMappingYesContains name, title, and summary; no other fields.
metadata.nameNon-empty stringYesIdentifier for the Recurse agent and deployed MCP tool.
metadata.titleNon-empty stringYesHuman-readable title.
metadata.summaryNon-empty stringYesDescription presented to callers of a deployed tool.

Review metadata.summary as tool-selection guidance. A calling agent should be able to tell when the tool is relevant without reading the project.

Pin the runtime

FieldTypeRequiredContract
runtimeMappingYesContains python and lockfile; no other fields.
runtime.pythonString choiceYes"3.14" is the current supported value.
runtime.lockfileString literalYesExactly uv.lock.

CPU, memory, and secret bindings are run or deployment options, not fields under runtime. See Project files and dependencies for the package and exact lockfile.

Select the prompt and model

FieldTypeRequiredContract and omission behavior
agentMappingYesContains the prompt and optional model selection.
agent.promptNon-empty relative pathYesPrompt file inside the project.
agent.modelNon-empty stringNoChoose a model listed on the pricing section, or omit it to use the current service default.

Preparation checks the model against the service's enabled models and pricing, then records the resolved selection. An existing deployment keeps that selection when the service default changes. Unsupported or unavailable models are rejected without substitution.

Changing the model requires a new Recurse agent revision and deployment. Stable instructions belong in the prompt file. Caller-specific values belong in inputs.

Define the caller interface

inputs is a required mapping containing a valid JSON Schema Draft 2020-12 document. For an object-shaped interface, review:

  • properties for every caller-supplied value.
  • required for values callers must provide.
  • default for values resolved when omitted.
  • Constraints such as minimum, maximum, and string or array limits.
  • additionalProperties: false when unknown caller keys should be rejected.

Declaring a property does not make it required. Manifest loading validates the schema itself; caller values are validated and defaults are applied when a run is resolved.

Declare inputs.properties.task as a string with either a non-empty default or an entry in inputs.required. A caller must supply a required task; an omitted task uses its default. The resolved string becomes the Recurse agent's task, not a top-level manifest field. {{ input.NAME }} inserts another resolved caller value into that task. The task is reserved for the agent and is absent from the read-only recurse.context().inputs mapping seen by tools; other resolved values remain there. Missing, empty, non-string, or optional task declarations without a usable default are rejected before admission.

Define the result contract

outputs is a required, self-contained Draft 2020-12 JSON Schema with type: object. A successful final JSON value must match it, and a deployment exposes it as the MCP tool's output schema.

Review required fields and additionalProperties just as you would for inputs. Internal fragment references such as #/$defs/Receipt are allowed; external document references are rejected.

A valid output is not necessarily a successful quality outcome. For example, target_reached: false can be schema-valid. Artifact files are separate from the result: a filename in JSON does not create a file, and fields describing artifacts should agree with what the completion tool saved.

Register the tool module

FieldTypeRequiredContract and omission behavior
toolsMappingYesContains source, register, and optional built_in and defaults.
tools.sourceNon-empty relative pathYesPython source file that defines the tools.
tools.built_inBooleanNoDefaults to true; enables harness built-ins.
tools.defaultsSettings mappingNoShared settings; per-tool values override them.
tools.registerNon-empty mappingYesExact public function names mapped to ~ or settings.

Every public top-level function in the source file must be registered, and every registration must name a public function. Helpers begin with _. Type annotations and Google-style docstrings define the interface the Recurse agent sees.

tool_name: ~ means the tool has no per-tool overrides. It does not disable the tool. Disabling built-in tools does not remove registered custom tools.

Configure tool behavior

The following settings are valid under tools.defaults and an individual registration:

SettingAccepted valueBehavior when omittedConsequence
storableBooleanInferred from the return annotation when not inherited.Allows the return value to be retained and used by later tools. It does not create a file artifact.
no_storageList of parameter namesEmpty list when not inherited.Listed parameters must receive inline values rather than stored-object references.
volatileBooleanfalse when not inherited.Identical arguments may produce different results, so repeated calls are not evidence of a stuck loop.
pure_argsList of parameter names or nullEmpty list when not inherited.Listed parameters are not mutated. null declares every parameter pure.

Precedence is an explicit per-tool value, then tools.defaults, then the harness default or inference. tool_name: ~ and pure_args: null use null for different purposes.

Only declare purity the implementation guarantees. An incorrect declaration can allow unsafe concurrent work. Shared parameter-name settings must refer to parameters that exist on every affected tool.

Review paths and validation stages

Declared paths are portable paths relative to the project directory. Do not use absolute paths, backslashes, empty segments, . or .. segments, or control characters.

Different checks establish different things:

Validation pointWhat it checksWhat it does not prove
Manifest loadingYAML structure, required and unknown fields, literal values, types, and JSON Schema validity.File existence, packaging, execution, or result quality.
PackagingDeclared files, public tool registrations, annotations, docstrings, build backend, and exact lockfile in the archive.Successful serverless preparation or a correct run.
Serverless preparation and executionModel availability, runtime tool contracts, input resolution, and final output conformance.That a valid result meets your substantive objective.

Common corrections

Invalid declarationCorrection
Snake-case API version field or tools.built-inUse apiVersion and tools.built_in.
runtime.python: 3.14Use the string "3.14".
Missing outputs, or outputs.type: arrayDeclare an object result schema.
tools.register: {} or a list of namesSupply a non-empty mapping from names to null or settings.
pure_args: trueSupply a list, [], or null.
A registration whose function is absentMatch the public function's exact name.
A prompt path outside the projectPackage the file and use a relative path.

See Write prompts and Python tools for tool design and Troubleshooting for recovery from a rejected manifest or package.