Skip to Content
DocsConceptsWhat is an Agent

What is an Agent

An Agent is a configured loop driver. It carries:

  • Instructions. The system prompt.
  • An architecture. The loop strategy (ReAct by default).
  • A Dependencies bundle. Every protocol the architecture might need: model, memory, runtime, tool host, budget, permissions, hooks, telemetry, audit log.
from loomflow import Agent agent = Agent( "You are a helpful assistant.", # instructions model="claude-opus-4-7", # the Model protocol memory="sqlite:./bot.db", # the Memory protocol tools=[search, fetch], # the ToolHost protocol budget=StandardBudget(BudgetConfig(...)), permissions=StandardPermissions(...), audit_log=FileAuditLog("./audit.jsonl"), telemetry=OTelTelemetry(...), runtime=SqliteRuntime("./journal.db"), architecture="react", # default )

Each kwarg is a different protocol implementation. Most have no-op defaults so a bare Agent("...", model="...") is a complete object.

What it’s not

  • Not a base class. You don’t subclass Agent. To customize the loop, pass a different architecture=. To customize a backend, pass a different protocol implementation.
  • Not stateful at the class level. All per-run state lives in AgentSession (constructed fresh per agent.run()). The Agent itself is a configuration container.
  • Not single-tenant. One Agent instance serves N users via user_id= on agent.run().

How agent.run() works

result = await agent.run( "What's the weather in Tokyo?", user_id="alice", # multi-tenant scope session_id="conv_42", # conversation continuity output_schema=MyPydanticModel, # optional structured output )

The Agent:

  1. Opens a runtime session (sets a contextvar).
  2. Opens a root telemetry span (loom.run).
  3. Writes a run_started audit entry.
  4. Seeds the message stack from memory (recent episodes, recent facts, working blocks).
  5. Hands control to the architecture’s run() async generator.
  6. Forwards events to the consumer (or awaits through them for agent.run()).
  7. Persists the new episode to memory.
  8. Triggers auto-extract for fact consolidation (if enabled).
  9. Writes a run_completed audit entry.
  10. Returns a RunResult.

For the full step-by-step including all four observability boundaries (events, telemetry, audit, runtime journal), see Architecture (internals).

What agent.stream() returns

stream() is the same loop, exposed as an async generator of Events:

async for event in agent.stream("..."): if event.kind == "model_chunk": print(event.payload["chunk"]["text"], end="") elif event.kind == "tool_call": log.info("calling tool %s", event.payload["call"]["tool"])

Same loop, same RunResult (built from the same AgentSession at the end). Pick run() when you only need the final answer; pick stream() when you want backpressure-aware token output.

Re-using one Agent across users

You construct ONE agent per logical service. Pass user_id= per call:

agent = Agent( "...", model="claude-opus-4-7", memory="postgres://...", audit_log=FileAuditLog("./audit.jsonl"), ) # In a request handler: async def handle(prompt, user_id, session_id): return await agent.run(prompt, user_id=user_id, session_id=session_id)

Memory partitions by user_id, audit attributes by user_id, budgets cap per user_id. The Agent itself is shared.

Agent is a value object plus a run method. It carries no mutable state across calls except for hook registrations and add_tool / remove_tool plugin operations. Two agent.run() calls in flight concurrently against the same Agent are safe. Each gets its own AgentSession.

Last updated on