Runtime¶
Long-lived resource container. Create once at startup, share across an application. Holds providers, tools, budget, and trace configuration.
The methods listed below are part of the v1.0.0 stable surface
(stability spec §1.2). Other public attributes
on Runtime (e.g. make_llm_node, connect_mcp) work today but are
not stability-promised.
Runtime¶
Runtime
¶
Arcana Agent Runtime -- create once, use many times.
Holds long-lived resources: - Provider connections (gateway registry, with automatic fallback chain) - Tool registry + gateway - Trace backend - Default budget policy - Default engine config
Provider fallback behavior
When multiple providers are registered (e.g.
providers={"deepseek": "sk-xxx", "openai": "sk-yyy"}),
the runtime automatically builds a fallback chain based on
registration order (dict key order). The first provider is
primary; subsequent providers serve as fallbacks. If the
primary provider fails with a retryable error after exhausting
retries, the request is automatically forwarded to the next
provider in the chain.
Use runtime.fallback_order to inspect the resolved order.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
providers
|
dict[str, str | dict[str, str]] | None
|
Mapping of provider name to API key. The first key becomes the default provider; remaining keys form the automatic fallback chain in insertion order. |
None
|
namespace
|
str | None
|
Optional namespace for tenant isolation. When set,
memory and trace are partitioned so that multiple Runtimes
sharing the same backing stores don't see each other's data.
When |
None
|
budget_remaining_usd
property
¶
Remaining USD budget, or None if no budget limit is set.
tokens_remaining
property
¶
Remaining token budget, or None if no token limit is set.
__init__
¶
__init__(*, providers: dict[str, str | dict[str, str]] | None = None, tools: list[Callable] | None = None, guardrails: list[Callable] | None = None, mcp_servers: list[MCPServerConfig] | None = None, budget: Budget | None = None, trace: bool = False, memory: bool = False, memory_budget_tokens: int = 800, config: RuntimeConfig | None = None, namespace: str | None = None, context_strategy: Any = None) -> None
run
async
¶
run(goal: str, *, engine: str = 'conversation', max_turns: int | None = None, budget: Budget | None = None, tools: list[Callable] | None = None, skills: list[str] | None = None, auto_route: bool = True, response_format: type[BaseModel] | None = None, images: list[str] | None = None, input_handler: Callable | None = None, system: str | None = None, context: dict[str, Any] | str | None = None, provider: str | None = None, model: str | None = None, on_parse_error: Callable | None = None) -> RunResult
Run a task to completion.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
goal
|
str
|
What to accomplish |
required |
engine
|
str
|
"conversation" (V2, default) or "adaptive" (V1) |
'conversation'
|
max_turns
|
int | None
|
Override default max turns |
None
|
budget
|
Budget | None
|
Override default budget for this run |
None
|
tools
|
list[Callable] | None
|
Additional tools for this run only |
None
|
skills
|
list[str] | None
|
Optional skill names to force into this run's working set
when those skills exist in |
None
|
auto_route
|
bool
|
Enable intent-routing fast paths. When False, the conversation engine skips classifier-based direct-answer routing and enters the normal turn loop. |
True
|
response_format
|
type[BaseModel] | None
|
Pydantic model class for structured output.
When provided, the LLM response is parsed and validated
against this model. |
None
|
images
|
list[str] | None
|
Optional list of image inputs (URLs, file paths, or data URIs) to include in the initial user message. |
None
|
input_handler
|
Callable | None
|
Optional callback for the ask_user built-in tool. Can be sync or async. When None, the LLM receives a fallback message and proceeds with best judgment. |
None
|
system
|
str | None
|
System prompt for this run. Overrides
|
None
|
context
|
dict[str, Any] | str | None
|
Additional context for the agent. A dict is serialized
as JSON; a string is used as-is. Injected into the goal as
a |
None
|
provider
|
str | None
|
Override the default provider for this run only. |
None
|
model
|
str | None
|
Override the default model for this run only. |
None
|
on_parse_error
|
Callable | None
|
Optional callback invoked when the LLM returns
text that cannot be parsed into the Does NOT fire for provider-level rejections (e.g. the
provider does not support |
None
|
run_batch
async
¶
Run multiple independent tasks concurrently.
Each task dict must have a "goal" key and may include any
keyword arguments accepted by :meth:run (tools, system,
provider, model, response_format, etc.).
Individual failures do not crash the batch -- the corresponding
RunResult will have success=False.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
tasks
|
list[dict[str, Any]]
|
List of task dicts, each with |
required |
concurrency
|
int
|
Maximum number of concurrent runs (default 5). |
5
|
Returns:
| Type | Description |
|---|---|
BatchResult
|
BatchResult with all results preserving input order. |
chat
async
¶
chat(*, system_prompt: str | None = None, max_turns_per_message: int = 10, budget: Budget | None = None, input_handler: Callable | None = None, max_history: int | None = None) -> AsyncGenerator[ChatSession, None]
Create a multi-turn chat session.
Unlike run() (single goal -> result), chat() maintains
conversation history across multiple user messages. Each send()
is one conversation turn where the agent may use tools before
responding.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
max_history
|
int | None
|
Maximum number of non-system messages to retain.
When set, older non-system messages are trimmed after each
|
None
|
Usage::
async with runtime.chat() as c:
r = await c.send("Hello")
r = await c.send("Tell me more about X")
print(c.total_cost_usd)
chain
async
¶
chain(steps: list[ChainStep | list[ChainStep]], *, input: str = '', budget: Budget | None = None) -> ChainResult
Run a pipeline of agent steps with optional parallel branches.
Each step's output is automatically passed as context to the next. Use nested lists for parallel execution::
steps=[
ChainStep(name="filter", ...),
[ChainStep(name="classify", ...), ChainStep(name="analyze", ...)],
ChainStep(name="integrate", ...),
]
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
steps
|
list[ChainStep | list[ChainStep]]
|
Ordered list. Each element is a |
required |
input
|
str
|
Initial input text fed as context to the first step |
''
|
budget
|
Budget | None
|
Shared budget across all steps |
None
|
collaborate
¶
collaborate(*, budget: Budget | None = None, cognitive_primitives: list[str] | None = None, channel_history_limit: int | None = None) -> AgentPool
Create a collaboration pool for multi-agent communication.
Returns an :class:AgentPool that provides shared communication
primitives. The pool itself is an async context manager — enter it
with async with to get automatic cleanup on exit. The user is
responsible for orchestration; the framework does not impose
topology, turn order, or stop conditions.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
budget
|
Budget | None
|
Optional pool-level budget; all agents in the pool
share one |
None
|
cognitive_primitives
|
list[str] | None
|
Pool-level default for
:ref: |
None
|
channel_history_limit
|
int | None
|
Optional bound on the pool |
None
|
Usage::
async with runtime.collaborate(cognitive_primitives=["pin"]) as pool:
planner = pool.add("planner", system="You plan")
# executor opts out even though the pool enables pin
executor = pool.add("executor", system="You execute",
cognitive_primitives=[])
plan = await planner.send("Make a plan for: ...")
result = await executor.send(f"Execute: {plan.content}")
This is a synchronous factory. Do not await the return value.
session
async
¶
session(*, engine: str = 'conversation', max_turns: int | None = None, budget: Budget | None = None, tools: list[Callable] | None = None, system: str | None = None) -> AsyncGenerator[Session, None]
Create a session for manual control.
Usage
async with runtime.session() as s: result = await s.run("Do something") print(s.state) print(s.trace_events)
on
¶
Subscribe to runtime events. Returns self for chaining.
Events
"run_start": (run_id: str, goal: str) "run_end": (run_id: str, result: RunResult) "error": (run_id: str, error: Exception)