Agents Overview¶
loom.ai turns a declarative, vendor-neutral agent artifact into a running,
governed HTTP service. An agent is authored as a YAML file, validated offline
with no credentials and no network, compiled into an immutable plan at start-up,
and served under the authentication the rest of your application already uses.
The pillar is deliberately narrow about what belongs where:
Concern |
Lives in |
Why |
|---|---|---|
What the agent is — its name, instructions, answer shape, granted capabilities |
the artifact ( |
Portable. The same file moves between environments unchanged. |
Where things are — model provider, endpoints, credentials, server URLs |
the |
Deployment facts. They differ per environment and must never travel in the artifact. |
Which engine actually runs it |
an entry point ( |
Swappable. The compiler never imports an engine. |
That split is the whole design. An artifact names a model role (reasoning),
never a model id; it names an MCP server (runbooks), never a URL; it names a
skill library (./skills), never an absolute path. Nothing in the artifact can
locate a resource or carry a secret, because those fields do not exist in the
format.
Stability¶
Two different promises, and conflating them is the mistake this section exists to prevent.
The artifact format is stable; the Python API is not
The programmatic contracts are experimental (FR-056). The classes,
protocols and functions exported from loom.ai may change within a major
line. Pin your loom version if you import them.
The artifact format is not experimental (FR-056a). An agent definition
declaring spec_version: 1 keeps validating and compiling for the whole
major line. The version-1 corpus is append-only: an existing entry may never
be edited to make a change pass, only added to.
Author against the artifact. Pin the Python API.
This is why the JSON Schema is shipped inside the distribution and the artifact reference is the longest page in this section: the artifact is the surface we ask you to build on.
Install¶
The base wheel imports no engine and no vendor SDK — import loom.ai works on a
bare install. Capabilities arrive as extras:
pip install "loom-kernel[rest,ai-pydantic]" # the engine
pip install "loom-kernel[ai-bedrock]" # + AWS Bedrock models
pip install "loom-kernel[ai-openai]" # + OpenAI and every compatible endpoint
pip install "loom-kernel[ai-anthropic]" # + Anthropic models
pip install "loom-kernel[ai-a2a]" # + the A2A interoperability surface
pip install "loom-kernel[ai-harness]" # + the `skills` capability
pip install "loom-kernel[logfire]" # + the Logfire OTel distribution
A missing extra is reported at start-up, by name, with the extra to install —
never as an ImportError on the first request.
The shape of a deployment¶
# config/api.yaml
ai:
engine: pydantic-ai
specs: ["ai/agents/*/agent.yaml"]
models:
default:
provider: openai
model: gpt-4o-mini
credentials_ref: OPENAI_API_KEY
reasoning:
provider: bedrock
model: anthropic.claude-sonnet-4-20250514-v1:0
region: eu-west-1
mcp_servers:
runbooks:
url: https://runbooks.internal.example.com/mcp
headers_ref: ${secrets:/loom/runbooks/api-key}
timeout_ms: 20000
endpoints:
incident-triage:
enabled: true
auth: jwt
Every agent matched by specs is compiled. Only the agents named in
endpoints, with enabled and a named auth, are mounted on the HTTP
and A2A doors. An agent absent from endpoints is still reachable — from
application code, through an
Agent() marker
declared on a use case’s own execute signature, bound to that use case’s
own verified caller. That is a separate door with its own default: a
marker-filled handle always refuses an anonymous caller, with no
allow_anonymous to relax it, unlike the HTTP door an operator explicitly
opts an agent into. Exposure over the network is always an explicit opt-in,
never a default; the code door is a distinct decision, not derived from it.
Nesting: how deep an agent may call another¶
ai:
max_agent_depth: 1 # default
max_agent_depth bounds the longest chain of nested agent runs one task may
open, counting the top-level run itself. A use case reaches a named
agent through an
Agent() marker,
and that handle’s run counts as one more entry in the same chain the run
that reached the use case already opened.
Defaults to 1: the top-level run alone already consumes the whole budget,
so an on_output hook — or any other use case an agent’s run invokes — that
itself declares an Agent() marker finds no depth left, and the whole class
of agent-calls-agent cycles is unreachable without deliberately raising this
value. Raising it buys deliberate nesting (a hook that escalates by asking a
second agent, say) at the cost of a chain that can grow that much deeper
before AGENT_CALL_TOO_DEEP catches a runaway one — see
what the marker refuses.
Conversation memory belongs to the application¶
An artifact may declare a conversation use case beside on_output: for
every run that carries a conversation_id, the runtime executes it before the
model starts and it returns the prior turns as opaque, engine-native bytes —
or None on the first turn. The engine replays them and hands the turn’s new
messages to the on_output use case as messages, so the application
persists them under its own conversation_id, in its own store, under its own
tenancy rules. Loom stores nothing, caches nothing and never reads a message;
no history travels on the wire in either direction. See
conversation.
An application that is only agents¶
An application does not need a model, a use case or a REST interface to run an
agent. Its manifest module declares AGENTS and nothing else, discovery runs in
manifest mode, and persistence is switched off:
# myapp/manifest.py
AGENTS = ["agents/*.yaml"] # globs resolve against app.code_path
# config/api.yaml
app:
name: incident-triage
discovery:
mode: manifest
manifest:
module: myapp.manifest
persistence:
backend: none
ai:
engine: pydantic-ai
models:
default:
provider: bedrock
model: <model id>
region: eu-west-1
output_mode: native # optional, see Model providers
endpoints:
incident-triage:
enabled: true
auth: jwt
pip install "loom-kernel[rest,ai-pydantic,ai-bedrock]" # swap the provider extra for yours
The sqlalchemy extra is not needed: create_app imports the SQLAlchemy backend
only inside the sqlalchemy wiring, so an agents-only process never loads it.
(loom.celery still imports it at module level; this recipe is about the REST
application, not every loom module.)
What persistence.backend: none means:
No unit of work and no repositories. A granted
usecasecapability still runs through the kernel executor, just without a transaction around it.Discovered models are accepted but not compiled, and they get no repository. Declaring them under
noneis harmless and pointless.A
database:section is ignored, silently.Deferred job dispatch does not fire.
JobService.dispatchqueues the call to run after the unit of work commits, and with no unit of work nothing flushes the queue, so a job dispatched from a use case is dropped without an error. An agents-only application that needs background jobs keeps a real backend.
Only manifest discovery can describe this application: the interfaces and
modules engines require at least one module path, and their errors — like the
RuntimeError raised when discovery finds no use case, no interface and no
agent — say so by naming app.discovery.mode: manifest and AGENTS.
persistence.backend still defaults to sqlalchemy; loom never infers none
from the absence of models or of a database: section. Leaving the default in
place with no models starts the application with a WARNING that names
persistence.backend: none as the way out, and still requires a reachable
database.
Where to go next¶
Artifact reference — the complete
spec_version: 1format, field by field, and the folder layout an agent lives in. It includeson_output, the use case the runtime executes once per completed run with the validated output, andconversation, the use case that loads the prior turns of aconversation_idbefore the model starts.Model providers — the provider matrix, and why an OpenAI-compatible endpoint is one provider covering many vendors.
A2A surface — publishing an agent to other agents, and exactly what the card does and does not say.
MCP deployment — where an MCP server goes in your topology, and the rule that decides
mcpversususecase.Observability — OTel and Logfire, including an honest account of what the traces do not yet give you.