API Reference normandy v#2.2.0
View SourceModules
Normandy builds Claude-powered agents in Elixir: typed I/O schemas, tools, conversation memory, hooks, guardrails and durable turns.
Wraps a remote A2A agent as a Normandy.Tool.
Helpers for discovering remote A2A agents and registering them as tools.
Exposes Normandy agents as A2A-compatible endpoints.
Core agent implementation providing conversational AI capabilities.
Configuration structure for BaseAgent instances.
Default input schema for agent interactions.
Default output schema for agent responses.
Splits a %BaseAgentConfig{} into a serializable, secret-free template
(persisted via SessionStore.save_config_template/3) and reconstructs a full
config on any node from template + node-local supplement + credential token.
The single chokepoint every agent tool call flows through.
A tool call ready to run: the tool as registered (never changed by the call), the
model's input validated against the tool's input schema, and the call's timeout (the
tool's registration :timeout, else Normandy.Tools.Executor.default_timeout/0).
Carries the behaviour functions the chokepoint consults.
The interface between Normandy and a model provider.
Response schema for agent outputs that may include tool calls.
The pure finite-state-machine core of an agent turn.
The injected side-effecting functions the driver consults per effect.
Eager-handoff trigger for distributed sessions.
Asynchronous :gen_statem interpreter of the pure Turn FSM — the async analog
of Turn.Driver. Coarse lifecycle states (:running, :awaiting_approval,
:idle) hang Tasks and state_timeouts off the turn; they are NOT a
re-encoding of the seven Turn.State statuses (the core stays the source of
truth). Blocking effects (:call_llm, :dispatch_tools, :execute_approved)
run in a monitored Task; non-blocking effects run synchronously in the handler.
The contract of the module a Normandy.Agents.Turn.Session starts and stops its
Turn.Server through (its :supervisor_mod).
Serializable data for one in-flight turn (the design's %TurnState{}). tokens_compacted is the estimated tokens of this turn's messages removed by in-turn compaction (feeds the task budget's remaining). trace_carrier is the W3C text-map carrier of the OpenTelemetry context the turn was started under ([] without OTel); the core never reads it, the durable shell persists it so a resumed turn keeps its trace.
DynamicSupervisor for Turn.Server processes (one per live session).
Horde.DynamicSupervisor for Turn.Server processes — cluster-wide placement
and supervision. Children start under the registry's :via name (atomic
registration). resume_policy maps to the child restart value: :lazy →
:temporary (a lost node's session is NOT redistributed; it is rebuilt on the
next request), :eager → :transient (Phase 7d; redistributed on node-down).
Batch processing utilities for handling multiple agent requests concurrently.
Resolves the node-local, non-serializable half of an agent's config from a
stable template_id: the toolset, before/after hooks, and a
client_builder that turns a credential token into an LLM client struct.
Optionally :mcp_servers (which may carry authorization tokens);
ConfigTemplate.rebuild/3 requires it when the original config had it set.
Optionally :pinned: the security settings this node fixes for the template,
[policy: ref, guardrails: list, credential: ref, ...]. A stored template whose value
differs is refused (see Normandy.Agents.ConfigTemplate's "Trust boundary").
Default node-local AgentTemplate: an Agent mapping template_id → supplement.
Contract for budget gating and accounting around tool calls.
Default BudgetTracker: no gating, no accounting (back-compat).
Contract for context-window compaction at the turn's :steering boundary.
Default Compactor: never compacts (back-compat, zero cost).
Opt-in Compactor that wraps Normandy.Context.WindowManager's truncation
strategies (:oldest_first | :summarize).
Explicit, per-agent selection of the pluggable behaviours.
Contract for resolving an LLM provider token.
Lifecycle hooks: modules an agent lists on Normandy.Behaviours.Config :hooks as
{module, opts} entries, run in list order.
Contract for agent long-term memory: path-addressed UTF-8 documents grouped in namespaces, with optimistic versioning.
ETS-backed MemoryStore, in-node. One row per document, {{namespace, path}, doc},
plus one totals row per namespace, {{namespace, 0}, {bytes, documents}}, in an
:ordered_set, so an operation costs what it touches, not the namespace's size. A
GenServer owns the :protected table and runs every write and every list/3, so
compare-and-set, rename and the put/6 limits are atomic and a listing never sees half
of a rename. get/3 reads the table directly, one document at a time, outside the
owner: during a rename/4 or delete_tree/3 it may see the document at its old path, its
new path or both (never neither, for a rename), or some of a tree's documents already
gone. The handle is {pid, table}.
Process-backed in-memory MemoryStore, for tests and single-node runs. One Agent
holds every namespace; each operation is one Agent call, so it is atomic. The handle
is the pid.
Distributed, durable MemoryStore over Mnesia, in an :ordered_set table with
attributes [:key, :value]: one row per document ({namespace, path} → the document)
plus one totals row per namespace ({namespace, 0} → {bytes, documents}). Every
operation is one :mnesia.transaction/1. A write first takes the write lock on its
namespace's totals row, so a namespace's writers run one at a time and compare-and-set,
rename, delete_tree and the put/6 limits are atomic across the cluster. get/3 reads
one row. list/3, delete_tree/3, rename/4 and delete_namespace/2 select the
namespace's rows, which locks the whole table for the transaction (a read lock for
list/3): a rename or tree delete in one namespace briefly holds writes in the others,
and a list/3 waits for a write in progress in any namespace.
Durable, cluster-shared MemoryStore over Postgres. The handle is the host's Ecto
Repo module: pass {Normandy.Behaviours.MemoryStore.Postgres, MyApp.Repo}, for
example as Normandy.Tools.Memory.new/1's :store. Its tables come from
Normandy.Behaviours.MemoryStore.Postgres.Migration: call its up/0 and down/0
from a host migration.
Migration for the Postgres MemoryStore. Call from a host migration
Durable MemoryStore over Redis. Each document is a hash; each namespace keeps a
sorted set of its paths (score 0, so lexical order) for listing and prefixes. Every
write is one Lua script, so compare-and-set, rename and delete_tree are atomic. All of
a namespace's keys share the {namespace} hash tag, so they live in one Redis Cluster
slot; the scripts derive document keys from the declared index key's prefix.
A namespace's content bytes live in a counter key beside its index (the document
count is the index's size), kept by the same scripts, so put/6's limits are checked
in the write's script.
Contract for model capability/limit lookup.
Default ModelCatalog: GET /v1/models/{model} through Claudio.Models.get/2;
the context window is the response's max_input_tokens.
Deprecated: use Normandy.Behaviours.ModelCatalog.ModelsAPI. Removed in 3.0.
Contract for per-tool-call policy decisions, consulted at the dispatch
chokepoint via Normandy.Behaviours.Config.to_pipeline/1.
Default PolicyEngine: allows every call (back-compat).
PolicyEngine that evaluates an ordered list of in-memory rules, first match
wins, with a configurable default action. The ruleset is supplied as a
keyword list on ctx.opts (the opts half of the bundle's
{module, opts} ref — always a keyword list by construction)
Maps a session_id to the live Turn.Server pid serving it, so a router can
decide route-to-existing vs rehydrate-and-start. :none means no live process
(the session may still have persisted state in a SessionStore). A lookup that
could not be made (a backend outage) is {:error, reason}, never :none:
taking it for "no process" would start a second server for a live session.
Distributed SessionRegistry over Horde.Registry (keys: :unique,
members: :auto). The handle is the registry's name (an atom). Cluster-wide
whereis; registration is atomic when servers start under the :via name from
child_name/2. Works as a cluster-of-one on a single node (and on
nonode@nohost) with the same config that serves N nodes.
Default SessionRegistry over Elixir's Registry (:unique keys). The
handle is the registry's name (an atom). register/3 registers the calling
process under session_id; the owner's death auto-unregisters it.
Distributed SessionRegistry using Redis as the shared session_id → pid name
table, with cross-node routing over Erlang distribution (Redis replaces Horde's
CRDT, not distribution). A per-node owner GenServer monitors locally registered pids
(clearing the key on :DOWN) and refreshes key TTLs so a crashed node's keys lapse on
their own.
A SessionRegistry.Redis handle: the owner's registered name, the Redix connection
the caller looks keys up on, and the key namespace. Build it with
Normandy.Behaviours.SessionRegistry.Redis.handle/1.
Contract for externalizing a session's conversation entries and turn state.
ETS-backed SessionStore — fast, in-node. A GenServer owns a private ETS table
and serializes every mutation through its mailbox, so concurrent appends and forks
to the same session_id cannot clobber each other. The handle is the owner pid.
Process-backed in-memory SessionStore — the reference impl and default
selection. Holds one AgentMemory per session_id plus an opaque turn-state
map, in an Agent. Used for tests and library/single-node runs.
Distributed, durable SessionStore over OTP-native Mnesia ("distributed ETS").
ram_copies tables are replicated ETS; :mnesia.transaction/1 serializes per-session
appends (the FOR-UPDATE equivalent), so concurrent/cross-node appends never lose an
entry. Mnesia stores Erlang terms natively, so content / turn_state /
config_template need no term_to_binary.
Durable, cluster-shared SessionStore over Postgres (Ecto). The handle is the
host's Ecto Repo module. Conversation content and opaque turn_state are stored
as Erlang terms (term_to_binary). Entries are a global parent-linked forest, so
fork/3 shares ancestors instead of copying.
Migration for the Postgres SessionStore. Call from a host migration
Adds Normandy 2.0's in-flight index to normandy_sessions: the inflight column
SessionStore.Postgres.save_turn_state/4 writes with the turn state, a partial index
over the rows where it is true (list_resumable/1), and an index on head_id
(delete_session/2 checks which entries another session still uses).
Adds the queryable resume_policy column to normandy_sessions (Phase 7d
resume reaper). Lets SessionStore.Postgres.list_resumable/1 filter eager
sessions without decoding the opaque config_template blob. Call from a host
migration alongside the other Postgres.Migration* modules.
Adds the config_template column to normandy_sessions (Phase 7c).
Durable SessionStore over Redis, modelled on Redis Streams. The conversation is an
append-only stream (XADD/XRANGE) so appends are atomic and O(1) with no
read-modify-write — concurrent appends never lose an entry. Session metadata
(turn state, config template, resume policy) lives in a hash; the ids of the sessions
with a turn in flight live in the SET "<namespace>:inflight" for list_resumable/1.
Raised when a changeset can't cast a value.
Optional convenience for wiring the distributed-session infra (Tier 2) into a host supervision tree. Returns child specs for
Conversation memory as a graph of parent-linked entries.
One message in the conversation graph.
How a value goes to the model: to_json/1 encodes it (a string, or a list of content
blocks), and get_schema/1 returns its schema. Implemented for strings, maps, lists,
content blocks and every io_schema struct.
Represents a document content block inside a multimodal message.
Represents an image content block inside a multimodal message.
What every consumer needs from a content block, answered by the block itself.
Represents a text content block inside a multimodal message.
Protocol for providing additional context information to agents.
Context provider that supplies today's UTC date to agents.
Represents a single message in agent conversation history.
Defines the structure of an agent's system prompt.
What a Normandy.Agents.BaseAgent.stream/3 callback receives, in stream order.
Utilities for processing streaming LLM responses.
Generates structured system prompts for agents from prompt specifications.
Represents a tool call request from the LLM.
Represents the result of executing a tool.
Handles conversation summarization for context window management.
Provides token counting utilities using the Anthropic token counting API.
Manages context window limits for conversations with automatic truncation strategies.
Raised by Normandy.Agents.BaseAgent.fetch_context_provider!/2 when the agent's prompt
specification has no context provider under the name. :name is the name that was
asked for; :registered lists the names that exist, sorted.
Message structure for agent-to-agent communication.
Pool manager for agent processes with automatic checkout/checkin.
GenServer wrapper for running BaseAgent instances as supervised processes.
Dynamic supervisor for managing agent processes.
Coordinates hierarchical multi-agent systems with manager-worker patterns.
The types shared by the orchestrators (Normandy.Coordination.SequentialOrchestrator and
Normandy.Coordination.ParallelOrchestrator): an agent, an agent spec and the result
shape. Its functions are internal to the orchestrators and not part of the public API.
Runs agents concurrently on one input.
Pattern matching utilities for agent results.
Reactive patterns for event-driven multi-agent coordination.
Runs agents one after another, each on the previous agent's output.
Manages shared context between multiple agents.
GenServer-backed shared context for multi-agent systems.
DSL for defining AI agents with a clean, declarative syntax.
DSL for defining multi-agent workflows with a declarative syntax.
Runs a list of Normandy.Guardrails.Guard modules against a value.
Rejects strings containing any of the configured forbidden substrings.
Rejects values whose string length exceeds :limit.
Rejects (or requires) regex matches against a string.
Rejects a struct or map when any of the named fields are nil.
A hybrid scope guard: a cheap deterministic fast path in front of an injected classifier.
Behaviour for guardrails that inspect agent input or output.
Raised by a guard when host code it calls fails: the :classifier or :fast_path
function of Normandy.Guardrails.Builtins.SemanticScope, or the same in a guard of your
own. Normandy.Guardrails.run/3 handles it under the guard's :on_error like any other
crash, so a guard's own configuration errors (an ArgumentError or KeyError from a
built-in guard) stay apart from failures of the code the host injected.
Exception raised when an input or output guardrail rejects an agent's input or answer.
An on_prompt or before_llm hook halted the turn. BaseAgent.run/2,3 returns
{:error, e} (run!/2,3 and the streaming paths raise it), and Turn.Server replies
{:error, e} for both. outcome/1 is what on_turn_end hooks receive in both shells:
{:rejected, e} for on_prompt and {:error, e} for before_llm.
What a hook callback knows about the agent it runs for: the event, the agent's name
(agent_name), model and session_id, the turn_id, and values (the agent's
tool_context). It carries no config, memory or client, so a hook that logs it leaks
nothing the agent was not told to share.
Raised when we cannot perform an action because the changeset is invalid.
The one place Normandy encodes and decodes JSON.
Adapter for integrating Claudio (Anthropic's Claude API client) with Normandy agents.
JSON deserialization helper with automatic error recovery.
Adapter implementing Normandy.Agents.Model against any OpenAI-compatible
Chat Completions endpoint (OpenAI, DigitalOcean Inference, etc.).
One call to a model: what Normandy.Agents.Model.converse/3 and
Normandy.Agents.Model.stream/3 receive.
A model's answer to one Normandy.LLM.Request.
Decides whether a request should use Anthropic structured outputs. Enabled
by default; disable globally via config :normandy, :structured_outputs, false or per client via client.options[:structured_outputs]. A schema that
the JSON Schema translator cannot express falls back (:skip).
A failed LLM call.
Helpers for discovering and registering MCP tools in a Normandy.Tools.Toolset.
Configuration for server-side MCP servers passed to the Anthropic API.
Wraps an MCP tool as a Normandy.Tool.
Raised when the model catalog can't produce a context window for a turn that
needs one (an agent with a real compactor and no pinned :context_window). The
turn fails; nothing is memoised, so the next turn retries the lookup.
Option validation for Normandy's public entry points.
One or more agents of a Normandy.Coordination.SequentialOrchestrator.execute/3 or
Normandy.Coordination.ParallelOrchestrator.execute/3 call failed.
Parameterized types are Normandy types that can be customized per field.
Circuit Breaker pattern implementation to prevent cascading failures.
Retry mechanism with exponential backoff for handling transient failures.
Provides a macro-based DSL for defining structured data schemas.
Utilities for introspecting Normandy schemas at runtime.
Exception raised when schema validation fails.
Runtime validation of data against Normandy schemas.
A JSON Schema as the model sees it.
The catalogue of every :telemetry event Normandy emits.
The [:normandy, :llm, :request] span: one per provider request made inside an
[:normandy, :agent, :llm_call] span, so retries, JSON parse retries and the
structured-outputs fallback show up individually.
:telemetry.span/3 for Normandy's own spans, except that the :exception
event's stacktrace is always [].
Normalizes a provider token-usage map into the token counts telemetry reports.
A scripted model client for tests: no network, no API key, no Normandy.Agents.Model
implementation of your own.
A Normandy.Test.FakeClient received more requests than it has script items.
The ExUnit tests every Normandy.Behaviours.MemoryStore implementation must pass.
Normandy runs them against its own five backends; run them against yours.
The ExUnit tests every Normandy.Behaviours.SessionRegistry implementation must pass.
Normandy runs them against Native, Horde and Redis; run them against yours.
The ExUnit tests every Normandy.Behaviours.SessionStore implementation must pass.
Normandy runs them against its own five backends; run them against yours.
The contract every tool implements.
Raised by Normandy.Tools.Toolset.fetch!/2 when no tool is registered under a name.
What a tool knows about the call it serves; the second argument of
Normandy.Tool.run/2.
Runs one tool call safely: Normandy.Tool.run/2 in a separate task, with a timeout and
error handling.
Validates a tool call's input before the tool runs.
Persistent agent memory: the memory tool. The model reads and writes text files under
/memories with Anthropic's memory-tool commands (view, create, str_replace,
insert, delete, rename); this tool executes them against a
Normandy.Behaviours.MemoryStore. Normandy.LLM.ClaudioAdapter sends it as Anthropic's
native memory tool (memory_20250818, native_definition/1), so the model gets the API's
own instructions for it; description/1 is what other consumers see (an A2A agent card).
A view of /memories names the mounts and their access.
The tools an agent can call, by name.
A turn that failed for a reason that is not the model, the input or a guardrail.
Raised by Normandy.Components.AgentMemory.delete_turn/2 when no entry in the memory
belongs to the turn. :turn_id is the id that was asked for.
Defines functions and the Normandy.Type behaviour for implementing
basic custom types.
Provides changeset-style validation for schemas.