Step Agent (:agent)
Quick Answer
Use :agent when you want to give an AI model an objective, tools, and
inputs and let it work toward the goal autonomously. The agent can call
:files, :table, :search, the allowlisted :breyta control-plane tool,
and any packaged step tools you provide.
Use :llm instead if you need direct control over messages and prompts.
Canonical Shape
Core fields:
| Field | Type | Required | Notes |
|---|---|---|---|
:type | keyword | Yes | Must be :agent |
:objective | string | Yes | Plain-language goal for the step |
:instructions | string | No | Extra agent guidance appended to the system prompt |
:inputs | map | No | Typed scalar/resource inputs available to the agent |
:template | keyword | No | Optional :llm-prompt template id |
:data | map | No | Extra template data to merge with :objective, :inputs, :input-summary, :tool-summary, and :instructions |
:expect | map | No | Optional output expectation/assertion metadata |
:connection | keyword/string | Recommended | Slot or direct connection id for the model provider |
:model / :provider | scalar | No | Model/provider overrides |
:temperature, :top-p, :stop | scalar | No | Generation controls forwarded to the model provider |
:max-tokens | int | No | Response token cap for each model turn |
:seed, :presence-penalty, :frequency-penalty | scalar | No | Provider-supported generation controls |
:reasoning-effort | keyword/string | No | Reasoning effort for providers that support it |
:prompt-cache-key, :previous-response-id, :cache-system? | scalar/boolean | No | Provider-specific prompt caching and response continuation controls |
:provider-opts | map | No | Provider-specific escape hatch |
:base-url, :deployment, :api-version | string | No | Custom/Azure/Chat Completions-compatible/OpenRouter endpoint overrides |
:tools | map/vector | No | Canonical :llm tools envelope, including :steps [...] for packaged steps and :agents [...] for flow-level agent definitions |
:available-steps | vector | No | Built-in step types to expose as tools |
:max-iterations | int | No | Agentic loop cap when executing tools; default 20, max 100 |
:max-tool-calls | int | No | Total executed tool-call cap; default 100, max 1000 |
:max-repeated-tool-calls | int | No | Optional cap for identical tool name+arguments calls; unset by default, max 100 |
:max-tokens-budget | int | No | Cumulative token limit; loop exits gracefully when exceeded |
:output / :response-format | map/keyword | No | Structured output handling; :output :schema accepts Malli schemas or raw JSON Schema maps |
:json-schema | map | No | Legacy raw JSON Schema field |
:output-persist | boolean/map | No | Auto-persist agent output as a workspace resource (blob) after completion |
:memory | string/map | No | Agent memory backed by a :table resource (see Agent Memory section) |
:cost | boolean/map | No | Cost tracking and aggregate budget enforcement across runs |
:evaluate | boolean/map | No | Post-run output evaluation via a second LLM pass |
:approval | boolean/keyword/map | No | Approval gates for mutation tools or plan steps |
:trace | boolean/map | No | Capture full execution trace as a blob resource for debugging |
:before-tool-call | function | No | Hook called before a proposed tool batch executes |
:after-tool-call | function | No | Hook called after a tool batch executes |
:auth | map | No | Explicit auth block when not using :connection |
:workspace-id | string | No | Runtime context override; normally supplied by Breyta |
Agent-safe step types available through :available-steps or :tools {:allowed [...]}:
:files— changeset overlays, read/search/edit/diff over existing source-tree or changeset refs:table— table-resource query, aggregate, export, and bounded mutation:search— workspace search across resources, flows, runs, and connections
Control-plane tools configured separately in the canonical :tools envelope:
:breyta— allowlisted workspace-scoped operations for flows, runs, docs,
and resources. Configure it as:tools {:breyta {:allow [...]}}so the
operation allowlist and mutation mode are explicit.
Step types that require a packaged :steps wrapper for agent tool use:
:http,:db,:kv,:function,:notify,:sleep,:wait,:ssh:filesoperations with broad side effects or external provider access:
:resolve-source,:capture,:publish, and:open-change-request- These have broad raw parameter surfaces. Wrap them in a flow-local packaged
step definition with an explicit:input-schemato limit what the model can
call. SeeREFERENCE_PACKAGED_STEPS.mdor Packaged Steps.
Tool config note:
:available-stepsuses built-in step keywords like[:files :table]:tools {:allowed [...]}uses string tool names like["files" "table"]:tools {:steps [...]}uses qualified packaged step ids like[:github/open-pr]:tools {:breyta {:allow [...]}}exposes Breyta workspace operations; see
Step Breyta for operation args and mutation modes
Full Config Example
This example intentionally shows the broad authored surface. In normal flows,
use only the fields you need.
(flow/step :agent :review
{:connection :ai
:provider :openai
:model "gpt-5.2"
:objective "Review the proposed changes and return JSON findings."
:instructions "Use tools for facts. Do not speculate."
:inputs {:draft changeset-ref
:risk-level "high"}
:template :agent-review
:data {:style "concise"}
:expect {:contains ["findings"]}
;; Model/provider controls inherited from :llm.
:temperature 0.2
:top-p 0.9
:stop ["\nDONE"]
:max-tokens 4000
:seed 42
:presence-penalty 0.0
:frequency-penalty 0.1
:reasoning-effort :medium
:prompt-cache-key "review-v1"
:previous-response-id "resp_previous"
:cache-system? true
:provider-opts {:openai {:responses {:store false}}}
:base-url "https://api.openai.com/v1"
:deployment "prod-reviewer"
:api-version "2025-04-01-preview"
;; Tools.
:available-steps [:files :table :search]
:tools {:mode :execute
:allowed ["files" "table" "search"]
:breyta {:allow [:flows/list :runs/show]
:mutations :none}
:steps [:review/publish-changeset]
:agents [:review/security]
:require {:tool-names ["files"]
:steps [:review/publish-changeset]
:agents [:review/security]}
:max-iterations 8
:max-tool-calls 80
:max-repeated-tool-calls 3}
:max-iterations 20
:max-tool-calls 100
:max-repeated-tool-calls 4
:max-tokens-budget 120000
;; Output and post-processing. Prefer Malli schemas in :output :schema.
:output {:format :json
:schema [:map
[:findings [:vector :string]]
[:risk [:enum "low" "medium" "high"]]]
:style :deterministic
:strict? true}
;; Legacy compatibility fields. Omit these when using :output above unless
;; you intentionally want the top-level values to override canonical output.
:response-format :json
:json-schema {"type" "object"}
:output-persist {:type :blob
:name "review-report"
:content-type "application/json"}
;; Agent runtime features.
:memory {:table memory-table
:scope :task
:scope-key "review-42"
:auto-recall true
:auto-summarize true
:recall-limit 20
:types ["observation" "decision"]
:plan {:enforce true :max-steps 6 :strict false}}
:cost {:enabled true
:budget {:max-tokens-total 500000 :window-hours 720}}
:evaluate {:criteria [:relevance :completeness :accuracy]
:model "gpt-4o-mini"
:persist-to-memory true}
:approval {:mode :tools :tool-names #{"files" "publish_changeset"}}
:trace {:name "review-trace"}
:before-tool-call (fn [{:keys [proposed-tool-calls]}] nil)
:after-tool-call (fn [{:keys [tool-calls last-results]}] nil)
;; Usually prefer :connection over explicit :auth.
:auth {:type :api-key :header "Authorization" :prefix "Bearer"}
:workspace-id "ws-runtime-override"})
Runtime Behavior
- Without
:template: the agent builds its own prompt from:objective,
:inputs, and:instructions. - With
:template::objective,:inputs,:input-summary,:tool-summary,
and:instructionsare merged into:datafor the template. - Packaged step tools (
:tools {:steps [...]}) run through their wrapped
step with all defaults and connection bindings applied. - Result shape is the same as
:llm. - Agentic loop defaults:
:max-iterationsdefaults to20and may be set up to100.:max-tool-callsdefaults to100and may be set up to1000.:max-repeated-tool-callsis unset by default; set it to stop repeated identical tool calls.- When a value is present both top-level and inside
:tools, the:tools
envelope value wins for that run.
Tool Guidance
The agent's system prompt tells the model which tools are available and what
they do. This includes both built-in step tools and packaged step tools.
Template-backed runs receive the same information under :tool-summary.
Example :tool-summary:
{:enabled [{:tool :files
:name "files"
:description "Inspect, search, diff, and edit existing source-tree and changeset resources."
:ops ["init-changeset" "list" "read" "search" "write-file" "apply-edit" "replace" "replace-lines" "delete-file" "move-file" "diff"]}
{:tool :table
:name "table"
:description "Query, aggregate, inspect, export, and mutate bounded table resources."
:ops ["query" "get-row" "aggregate" "schema" "export" "update-cell" "update-cell-format" "set-column" "recompute" "materialize-join"]}
{:tool :breyta
:name "breyta"
:description "Call allowed Breyta workspace control-plane operations in the current workspace."
:ops ["flows/list" "runs/show"]}]
:enabled-steps [:files :table]
:missing []}
Input Enrichment
Resource refs in :inputs are automatically summarized for the model:
- Source trees → compact handles with
:filestool access hints - Changesets → handles with both read and write hints
- Tables → handles with
:tabletool access hints - Image resources → visual context in the first model message when the agent is
not using a prompt template and the selected provider/model supports image input - Missing tools are flagged (e.g. "files tool is not enabled for this run")
- Long strings are truncated in the summary
Template-backed runs receive both raw :inputs and the enriched
:input-summary.
Image Resource Inputs
Pass uploaded or persisted image resources in :inputs when an agent should
inspect visual context:
(flow/step :agent :review-screenshot
{:connection :ai
:model "gpt-4o"
:objective "Review the screenshot and list UX issues."
:inputs {:screenshot uploaded-image-ref}})
For non-template agent runs, image resources are added to the agent's initial
multipart model message. The prompt still includes the normal input summary,
and the resource bytes are resolved later inside the :llm activity.
You can also pass an explicit image resource content part when you need detail,
transport, or processing options:
:inputs {:screenshot {:type :image-resource
:resource uploaded-image-ref
:detail :high
:transport :data-url
:processing {:max-width 1600
:max-height 1600
:format :jpeg
:quality 85}}}
Notes:
- Image inputs require a vision-capable provider/model and fail before the
provider call if the selected model is text-only. - Template-backed agent runs receive raw
:inputsand:input-summary; they
do not automatically add image parts to the initial message. - If a tool returns an image resource during an agentic loop, Breyta can append
it as visual context on the next model turn when the model supports image
input. Text-only models keep the image reference in the normal tool result. - Image resources follow the same guardrails as
:llm: resource-system-only
reads, content type/signature checks for data URLs, byte and pixel limits,
optional re-encoding, and short-lived signed URL transport when supported.
Example enriched input summary:
{:repo-tree {:kind :source-tree
:uri "res://v1/ws/ws-acme/bundle/source-tree/git/source-123"
:content-type "application/vnd.breyta.source-tree+json"
:recommended-tool :files
:tool-enabled? true
:access {:tool :files
:read-args {:source "res://v1/ws/ws-acme/bundle/source-tree/git/source-123"}
:common-ops [:list :read :search :materialize]}}
:draft {:kind :changeset
:uri "res://v1/ws/ws-acme/bundle/changeset/files/changeset-123"
:recommended-tool :files
:tool-enabled? true
:access {:tool :files
:read-args {:source "res://v1/ws/ws-acme/bundle/changeset/files/changeset-123"}
:write-args {:changeset "res://v1/ws/ws-acme/bundle/changeset/files/changeset-123"}
:common-ops [:list :read :search :diff :write-file :apply-edit :replace :replace-lines :delete-file :move-file :materialize :publish :open-change-request]}}
:incident-table {:kind :table
:uri "res://v1/ws/ws-acme/result/table/tbl_123"
:recommended-tool :table
:tool-enabled? true
:access {:tool :table
:args {:table {:ref "res://v1/ws/ws-acme/result/table/tbl_123"}}
:common-ops [:query :get-row :aggregate :schema :export]}}}
If a enriched input recommends a built-in tool that is not enabled, the summary
adds a :missing-tool constraint like:
{:repo-tree {:kind :source-tree
:recommended-tool :files
:tool-enabled? false
:missing-tool {:tool :files
:name "files"
:constraint "Hydrated inputs recommend the files built-in tool, but it is not enabled for this agent run. Do not invent files tool calls."}}}
Result Shape
: agent returns the same canonical result envelope as :llm. In the common
text case, the flow receives a map like:
{:content "final answer"
:tool-calls [{:tool-call-id "call_1"
:name "files"
:result {...}
:success true
:duration-ms 1250}]
:tool-call-stats {:call-count 3
:success-count 2
:error-count 1
:total-duration-ms 4200}
:usage {:prompt-tokens 123
:completion-tokens 45
:total-tokens 168}
:tokens-used 168
:model "gpt-5-mini"
:provider :openai
:finish-reason "stop"
:iterations 3}
:content— the agent's final answer. Use this in downstream steps.:tool-calls— each tool call with:name,:result,:duration-ms.:tool-call-stats— aggregate: call count, success/error counts, total duration.:iterations— how many turns the agent took.:usage/:tokens-used— token accounting.- With
:output {:format :json},:contentis parsed JSON data. - With
:output {:format :json :schema <malli-schema>}, the schema is compiled
to provider-facing JSON Schema before the final model turn.
Structured Output Schemas
For new agent flows, prefer a Malli schema in the canonical :output envelope:
(flow/step :agent :triage
{:connection :ai
:objective "Triage the support request."
:inputs {:email email-text}
:output {:format :json
:schema [:map
[:summary :string]
[:priority [:enum "low" "normal" "high"]]
[:recommended-actions [:vector :string]]]}})
Breyta validates the Malli schema form, converts it to provider-facing JSON
Schema, and sends that JSON Schema through the same provider path used by
:llm. This keeps :agent and :llm authoring consistent while preserving
provider-specific schema behavior.
Raw JSON Schema maps are still accepted in :output :schema and are passed
through unchanged. The older top-level :response-format and :json-schema
fields remain supported for existing flows. If both forms are present, the
top-level legacy fields win for backward compatibility.
Provider support is not uniform. DeepSeek supports JSON mode with
:output {:format :json}, but the standard DeepSeek chat endpoint does not
accept Breyta JSON Schema structured output. For DeepSeek agents, use JSON mode
without :schema, or add a deterministic validation/repair step after the
agent result.
OpenRouter supports agentic tool loops through Breyta's OpenRouter provider
when the selected routed model supports tool calling. For agent-to-agent
delegation, list flow-level agents under :tools :agents; add
:tools {:require {:agents [...]}} when the orchestrator must call specific
specialist agents before producing its final answer.
Generic Chat Completions-like endpoints are not enabled for agentic loops by
default. If you have verified that an endpoint/model can replay assistant
tool_calls and accept following role=tool messages, register a flow-level
LLM provider with :family :chat-completions-compatible and explicit
:capabilities #{:tool-calling :structured-output :agentic-tool-loop}.
Tool Call Hooks
Use :before-tool-call and :after-tool-call when authored flow logic needs
to inspect or gate the agentic loop. Both hooks can update available tools or
abort the loop early.
(flow/step :agent :fix-and-publish
{:connection :ai
:objective "Fix the issue, then publish when ready."
:inputs {:changeset changeset}
:available-steps [:files]
:before-tool-call (fn [{:keys [proposed-tool-calls usage]}]
(when (> (:total-tokens usage) 50000)
{:abort true
:content "Stopping: token budget exceeded"
:abort-reason "token-budget"}))
:after-tool-call (fn [{:keys [iteration tools tool-calls last-results usage]}]
;; After the first edit, make an authored packaged publish tool available.
(when (and (pos? iteration)
(some #(= "files" (:name %)) tool-calls))
{:tools (assoc tools :publish_changeset
{:type :review/publish-changeset
:name "publish_changeset"})}))
:max-iterations 8})
:before-tool-call receives:
:iteration— the upcoming iteration number (1-based):tools— current tool definitions map:tool-calls— all tool calls executed so far:proposed-tool-calls— tool calls proposed for this iteration:usage— cumulative token usage
:after-tool-call receives:
:iteration— the just-completed iteration number (1-based):tools— current tool definitions map:tool-calls— all tool calls executed so far:last-results— tool results from the most recent iteration:usage— cumulative token usage
It can return:
nil— continue unchanged{:tools <map>}— continue with updated tools{:abort true :content "..." :abort-reason "..."}— stop the loop early
Errors in the hook are caught and logged; the loop continues.
Agent Memory
When :memory is configured, the agent gets built-in memory capabilities
backed by a normal :table resource.
Memory Config
| Field | Type | Required | Notes |
|---|---|---|---|
:table | resource ref/URI | Yes | The memory table resource |
:scope | keyword | No | :flow (default), :task, or :global — filters recalled memories |
:auto-recall | boolean | No | Pre-load recent memories into context (default true) |
:auto-summarize | boolean | No | Persist a run summary after completion (default false) |
:recall-limit | int | No | Max memories to pre-load (default 20) |
:types | vector | No | Filter recalled memory types |
What Happens At Runtime
-
Before the loop: The runtime queries the memory table for recent entries
scoped to this flow and injects them into the system prompt as
"Prior memories." The agent starts with context from previous runs. -
During the loop: Two built-in tools are available automatically:
memory_recall— query the memory table by type, key, or freelymemory_record— record an observation, decision, or working-state entry
-
After the loop: If
:auto-summarizeis true, arun-summaryentry is
persisted with iteration count, tool call stats, and finish reason.
Memory Entry Types
"observation"— facts discovered during investigation (decays as code changes)"decision"— choices made and the reasoning behind them (persists until constraint changes)"working-state"— progress on an in-flight task (ephemeral, per-task)"run-summary"— auto-generated completion record (created by:auto-summarize)
Memory Example
'(let [;; Create or reuse a memory table
memory-table (flow/step :table :ensure-memory
{:op :schema
:table memory-table-ref
:persist {:type :table :name "agent-memory"}})
;; Agent with memory
result (flow/step :agent :review
{:connection :ai
:objective "Review the code and record your findings."
:inputs {:repo-tree repo-tree}
:available-steps [:files]
:memory {:table memory-table-ref
:scope :flow
:auto-summarize true}
:max-iterations 8})]
result)
The memory table is a normal Breyta workspace resource — visible, inspectable,
and exportable. Multiple flows can share the same memory table for cross-flow
agent context.
Plan / Checkpoint Support
When :memory {:plan {:enforce true}} is configured, the agent must declare a
plan before acting, and can checkpoint progress step by step.
Plan config: :enforce (require plan), :max-steps (default 10), :strict
(abort on deviation), :checkpoint (persist completion status).
Two built-in tools are registered: memory_plan (declare steps) and
memory_checkpoint (mark step done). Plan steps are persisted to the memory
table as plan-step entries, so the next run sees prior plan progress.
:memory {:table memory-ref
:plan {:enforce true :max-steps 6 :strict false}
:auto-summarize true}
In strict mode, the loop aborts if the agent's first tool call is not
memory_plan. In soft mode (default), a warning is logged but execution
continues.
Output Persistence
The optional :output-persist config auto-persists the agent's final
:content as a workspace resource after completion:
;; Simple: persist as blob with auto-detected content type
:output-persist true
;; Explicit: named blob with specific content type
:output-persist {:name "review-report" :content-type "text/markdown"}
When enabled, the result includes :persisted-ref with the resource reference.
If persistence fails, a warning is logged and the result is returned without
the ref.
This eliminates the common pattern of adding a separate :persist step after
every agent call.
Cost Controls
When :cost is configured, the agent tracks cumulative token usage across
runs through a KV-backed cost ledger.
:cost {:enabled true
:budget {:max-tokens-total 500000
:window-hours 720}} ;; 30-day window
- Pre-run: Checks the aggregate budget. If exceeded, throws
:resource-exhaustedwith the current usage and window details. - Post-run: Records the run's token count, iteration count, and tool
call count to the ledger. The ledger is scoped per flow slug and retains
the last 100 entries. - Per-run:
:max-tokens-budgeton the step config caps a single run
independently of the aggregate budget.
Evaluation
When :evaluate is configured, a second LLM pass scores the agent's output
after completion.
:evaluate {:model "gpt-4o-mini" ;; cheaper model for eval
:criteria [:relevance :completeness :accuracy]
:persist-to-memory true} ;; save scores to memory table
The result includes an :evaluation key:
{:evaluation {:scores {:relevance 5 :completeness 3 :accuracy 4}
:overall 4
:summary "Good finding, but missed the second injection point."
:evaluation-tokens 50}}
When :persist-to-memory is true and :memory is configured, evaluation
scores are persisted as run-summary entries for trend tracking.
The evaluation LLM call uses the same connection as the agent step. If the
author provides the connection via {:provided-by :author} in :requires,
the author pays for evaluation across all installations. Use :model in
the evaluate config to pick a cheaper model for scoring.
Approval Gates
When :approval is configured, certain tool calls pause the agent loop and
return an approval request instead of continuing.
;; Pause on any mutation (writes, edits, publishes, deletes)
:approval true
;; Pause on specific tools only
:approval {:mode :tools :tool-names #{"files" "table"}}
;; Pause on specific plan steps
:approval {:mode :plan-steps :step-names #{"publish" "open-pr"}}
When approval is triggered, the result includes :abort? true and an
:approval-request with the pending actions:
{:abort? true
:abort-reason "approval-required"
:approval-request {:key "approval:3:..."
:iteration 3
:actions [{:tool "files" :success true :duration-ms 1200}]
:mode :all-mutations}}
The flow author catches this and wires it into a :wait step:
'(let [result (flow/step :agent :fix {:approval true ...})]
(if (:abort? result)
(do (flow/step :wait :approve
{:key (get-in result [:approval-request :key])
:timeout "24h"})
;; Re-run the agent after approval
(flow/step :agent :fix-continue {...}))
result))
The agent's system prompt is updated to mention which actions require
approval, so the model knows to expect pauses.
Execution Trace
When :trace true (or :trace {:name "my-trace"}) is configured, the
runtime captures a full execution timeline and persists it as a single blob
resource after completion.
:trace true
The trace captures per-iteration: LLM call metadata (model, tokens), tool
calls with arguments/timing/results, memory operations, and plan state. The
final document includes result summary, evaluation scores, and cost data.
- During the loop: Zero storage writes — trace accumulates in memory
- After completion: Single blob write with content-type
application/vnd.breyta.agent-trace+json - On mid-loop crash: Trace is lost (the run's execution history is still
available for crash debugging)
The result includes :trace-ref with the resource URI. The trace blob is
visible in the workspace resource UI for inspection and debugging.
Agent Delegation
A coordinator agent can delegate bounded subtasks to specialist sub-agents
using packaged :steps with :type :agent:
{:steps [{:id :review/security-scan
:type :agent
:description "Run a focused security scan on the selected code area."
:input-schema [:map
[:area :string]
[:repo-tree :any]]
:defaults {:connection :ai
:model "gpt-5-mini"
:available-steps [:files]
:max-iterations 5}
:prepare :prepare-security-scan}]
:flow
'(let [result (flow/step :agent :coordinator
{:connection :ai
:objective "Review the repo, identify problem areas, then delegate a security scan for each."
:inputs {:repo-tree repo-tree}
:tools {:steps [:review/security-scan]
:allowed ["files"]}
:max-iterations 10})]
result)}
How this works:
- The coordinator agent sees
review/security-scanas a tool with a simple
[:map [:area :string] [:repo-tree :any]]input schema. - When the coordinator calls the tool, the packaged step runtime:
- Validates the input against
:input-schema - Calls
:prepareto transform input into the sub-agent's config - Merges
:defaults(connection, model, tools, iteration budget) - Executes
:type :agent— a full nested agentic loop - Applies
:project-resultto the sub-agent's output
- Validates the input against
- The sub-agent runs with its own iteration budget and tool set.
- The coordinator gets the projected result back as a tool call output.
This gives you multi-agent workflows without a new runtime concept — it's
packaged steps all the way down.
Not currently supported:
- agent profiles and skills
- automatic repo guidance loading (e.g.
AGENTS.md) - embedding-based memory recall ranking
Canonical Example
'(let [repo-tree (flow/step :files :resolve-repo
{:op :resolve-source
:source {:type :git
:repo "https://github.com/acme/service.git"
:ref "main"
:connection :github-api}})
findings (flow/step :agent :review-and-fix
{:connection :ai
:model "gpt-5-mini"
:objective "Inspect the repo and summarize the most likely fix for the incident."
:instructions "Use the files and table tools before making claims."
:inputs {:repo-tree repo-tree
:incident-table {:type :resource-ref
:uri "res://v1/ws/ws-acme/bundle/resource/table/incidents"}}
:available-steps [:files :table :search]
:max-iterations 6})]
findings)
Template-Backed Example
'(flow/step :agent :plan-remediation
{:connection :ai
:template :planner
:objective "Prepare a remediation plan for the failing run."
:inputs {:repo-tree repo-tree
:run-report run-report}
:data {:service "billing"}
:tools {:allowed ["files" "table" "search"]
:mode :execute}
:max-iterations 5})
Using Packaged Steps As Agent Tools
Top-level :steps definitions can be published directly as agent tools through
:tools {:steps [...]}. This lets you define a simplified, schema-backed tool
interface that the model calls, while the heavy built-in step config stays in
the packaged definition.
Step-to-Tool Example
{:functions [{:id :prepare-open-pr
:language :clojure
:code "(fn [{:keys [owner repo title head base]}]
{:method :post
:path (str \"/repos/\" owner \"/\" repo \"/pulls\")
:json {:title title :head head :base base :draft true}
:response-as :json})"}
{:id :project-pr-result
:language :clojure
:code "(fn [result]
{:url (get-in result [:body :html_url])
:number (get-in result [:body :number])})"}]
:steps [{:id :github/open-pr
:type :http
:description "Open a draft GitHub pull request."
:input-schema [:map
[:owner :string]
[:repo :string]
[:title :string]
[:head :string]
[:base :string]]
:output-schema [:map [:url :string] [:number :int]]
:defaults {:connection :github-api}
:prepare :prepare-open-pr
:project-result :project-pr-result
:tool {:name "github_open_pr"
:description "Open a draft GitHub pull request."}}]
:flow
'(let [result (flow/step :agent :fix-and-pr
{:connection :ai
:objective "Fix the issue, then open a draft PR with the fix."
:inputs {:repo-tree repo-tree
:changeset changeset}
:tools {:steps [:github/open-pr]
:allowed ["files" "table"]}
:max-iterations 8})]
result)}
How It Works
When the agent calls github_open_pr with {:owner "acme" :repo "service" ...}:
- The packaged step's
:input-schemavalidates the tool call arguments. :preparetransforms the simplified input into the:httpstep config.:defaults(including:connection :github-api) are merged.- The
:httpstep executes the API call. :project-resulttransforms the raw HTTP response into{:url ... :number ...}.- The projected result is returned to the model as the tool call output.
The model only sees the simplified interface — it never needs to know about
:method, :path, :json, :connection, or :response-as.
Mixing Built-In and Packaged Tools
:tools supports built-in steps, packaged steps, reusable agents, and MCP
tool adapters simultaneously:
:tools {:steps [:github/open-pr :billing/fetch-order]
:agents [:review/security]
:mcp [:linear/list_issues]
:allowed ["files" "table" "search"]
:mode :execute}
Built-in step tools use their native published interface. Packaged step tools
use the simplified :input-schema as their tool parameter schema.
Tool Summary
Packaged step tools appear in the :tool-summary alongside built-in tools:
{:enabled [{:tool :files :name "files" :description "..."}
{:tool :table :name "table" :description "..."}
{:tool :github/open-pr :name "github_open_pr"
:description "Open a draft GitHub pull request."}]}
For the full packaged steps reference, see REFERENCE_PACKAGED_STEPS.md or Packaged Steps.
Using MCP Tools
MCP tools are selected from top-level flow adapters. The remote endpoint is
still a normal :http-api requirement with :backend :mcp; the adapter
defines the MCP transport, allowlist, optional path, tool prefix, and known tool
schemas.
{:requires [{:slot :linear-mcp
:type :http-api
:label "Linear MCP"
:backend :mcp
:base-url "https://mcp.linear.app"
:auth {:type :bearer}}]
:mcp [{:id :linear/issues
:connection :linear-mcp
:transport :streamable-http
:allow-tools ["list_issues" "get_issue"]
:tool-prefix "linear"
:tools [{:name "list_issues"
:description "List Linear issues."
:input-schema {:type "object"
:properties {:team_id {:type "string"}}
:required ["team_id"]}}]}]
:flow
'(flow/step :agent :triage
{:connection :ai
:objective "Find the most relevant blocker issue."
:tools {:mode :execute
:mcp [:linear/list_issues]}})}
MCP tools use the same budgets, approval hooks, repeated-call limits, tracing,
and provider tool-loop behavior as other agentic tools. Breyta does not expose
all tools from an MCP endpoint by default; adapter-wide refs require
:allow-tools, and individual tool refs publish only the named tool. The
default 190-second MCP tool timeout gives headroom over Breyta MCP interfaces'
180-second synchronous wait; set adapter :timeout-ms when a remote MCP server
has a tighter or longer expected response window.
Installed Flow Behavior
An installed flow is the author's product. Internal agent state belongs
to the author, not the installer. The installer sees the flow's outputs,
not its working memory or debug traces.
| Feature | Belongs to | Why |
|---|---|---|
| Memory | Author's workspace | Internal working state — observations, decisions, plan progress |
| Cost ledger | Author's flow (KV) | The author's budget applies globally across all installations |
| Trace | Author's workspace | The author debugs agent behavior, not the installer |
| Evaluation | Author's workspace | Quality tracking is the author's concern |
| Output / result | Installer's workspace | The product output the installer consumes |
| Connections | Installer's bindings | LLM keys, GitHub tokens, etc. come from the installation |
Memory tables, trace blobs, and evaluation entries are persisted through the
flow's own resource context (the author's workspace). The installer never
sees or manages these resources.
Cost tracking is flow-scoped — the author sets a budget that applies globally
across all installations, because the author is responsible for the LLM spend
their flow generates.
Output persistence (:output-persist) writes to the runtime workspace (the
installer's) because that is the product output the installer paid for and
needs access to.
For a complete installable agent flow checklist and example, see the
Installable Agent Flows section in the
Installations guide.