loom.ai.abc

Neutral runtime contracts of the AI pillar.

Everything the compiler, the runtime and the HTTP layer share with an engine lives here, and nothing here imports an engine: the bootstrap resolves the provider through loom.ai.registry and hands the compiler plain values.

A run may continue a conversation the application loaded (FR-034): the prior history crosses this boundary as opaque, engine-native bytes inside Conversation, and the run’s new messages come back the same way on AgentResult.messages and FinalEvent.messages. Loom defines no message model and stores no history.

These contracts are experimental and may change within a major line; the artifact format they serve is not. See loom.ai for the distinction.

Module Attributes

CONVERSATION_ID_MAX_LENGTH

Longest conversation_id a run accepts; the value itself is opaque.

AnswerT

Output type of an AgentHandle, carried by the annotation on the use-case parameter it fills — never by the marker that fills it.

ExpectedT

Per-run output type passed to AgentHandle.run() through expect, independent of the handle's own AnswerT.

ToolResultT

Decoded type of a single McpHandle.call().

AgentEvent

Closed five-member tagged union of streaming events (FR-030).

HealthState

The three health states.

ToolsetFactory

Target of a kind: python capability, validated at compile time.

NativeToolSupport

Answers which provider tools a model binding admits, by loom tool name.

OutputCheck

a pure predicate over an answer.

Classes

AgentAnswer(*, output, usage[, interaction_id])

Outcome of one run reached through an AgentHandle.

AgentEngine(*args, **kwargs)

One compiled agent, ready to run.

AgentEngineProvider(*args, **kwargs)

Entry-point target in group loom.ai.engines.

AgentHandle(*args, **kwargs)

A named agent reached from another use case, bound to this run's caller.

AgentResult(*, output, usage[, ...])

Outcome of a non-streaming agent run.

AgentUsage(*, input_tokens, output_tokens, ...)

Resource accounting of one agent run.

ConcurrentMcpSession()

Declares an McpSession implementation already safe for concurrent calls.

Conversation(*, conversation_id[, history])

The conversation a run continues.

DepsFactory(*args, **kwargs)

Builds per-invocation dependencies for capability calls.

ErrorEvent(*, code, message[, ...])

The run failed mid-stream (FR-032).

FinalEvent(*, output, usage[, ...])

The run completed; the only variant carrying usage.

HealthStatus(*, status[, detail])

Health of one agent engine, shared by every engine (FR-048).

McpHandle(*args, **kwargs)

A caller-bound, filtered view of one MCP server's tools.

McpSession(*args, **kwargs)

Minimal MCP session the runtime needs from any client library.

McpToolCallResult(*, ok[, structured])

The server's own answer to one call_tool, protocol-level and undecoded.

McpToolInfo(*, name, has_output_schema)

One tool a session's server advertises.

SqlGrantHandle(*args, **kwargs)

One artefact's own bounded view of one of its sql grants.

StateShape(*, schema, decoder)

The one shape deps_type/deps_schema compile to (FR-003).

TextDeltaEvent(*, text)

Incremental model text.

ToolCallEvent(*, tool, call_id, arguments)

The model invoked a tool.

ToolResultEvent(*, call_id, ok, summary)

A tool invocation completed.

ToolsetContext(*args, **kwargs)

What a kind: python factory may reach while building its toolset.

loom.ai.abc.CONVERSATION_ID_MAX_LENGTH: Final[int] = 128

Longest conversation_id a run accepts; the value itself is opaque.

class loom.ai.abc.AnswerT

Output type of an AgentHandle, carried by the annotation on the use-case parameter it fills — never by the marker that fills it.

alias of TypeVar(‘AnswerT’)

class loom.ai.abc.ExpectedT

Per-run output type passed to AgentHandle.run() through expect, independent of the handle’s own AnswerT.

alias of TypeVar(‘ExpectedT’)

class loom.ai.abc.ToolResultT

Decoded type of a single McpHandle.call().

alias of TypeVar(‘ToolResultT’)

class loom.ai.abc.StateShape(*, schema, decoder)[source]

Bases: LoomFrozenStruct

The one shape deps_type/deps_schema compile to (FR-003).

deps_schema, deps_type: <symbol> and deps_type: dict are three authored spellings of one optional JSON Schema; the compiler resolves all three to this value, never to one of three interchangeable objects. Same idiom as its sibling CompiledOutput, including a decoder field msgspec cannot itself encode, under the invariant loom.ai.compiler._plan documents.

Parameters:
schema

JSON Schema the artifact’s state must satisfy, or None under the deps_type: dict waiver, where no schema exists and template markers are not validated (FR-006).

Type:

collections.abc.Mapping[str, Any] | None

decoder

Built msgspec JSON decoder producing the normalised state mapping, or None alongside schema is None.

Type:

msgspec.json.Decoder[Any] | None

class loom.ai.abc.AgentUsage(*, input_tokens, output_tokens, requests, duration_ms, cache_read_tokens=0, cache_write_tokens=0, tool_calls=0, cost=None, details=<factory>)[source]

Bases: LoomFrozenStruct

Resource accounting of one agent run.

Nothing the engine reported is dropped. The counters any engine would plausibly report are named fields; every other field it returned — the audio counters, a provider’s own extras, a field a future engine release adds — rides verbatim in details, so a new counter reaches the caller without a change here. The engine’s own usage type never crosses this boundary: a second engine fills this struct.

Parameters:
input_tokens

Tokens sent to the model across the run, cached ones included.

Type:

int

output_tokens

Tokens produced by the model across the run.

Type:

int

requests

Model requests issued during the run.

Type:

int

duration_ms

Wall-clock duration of the run in milliseconds.

Type:

int

cache_read_tokens

Input tokens served from the provider’s prompt cache, already counted in input_tokens. A cached token costs a fraction of a fresh one, so comparing models on input_tokens alone can invert the ranking.

Type:

int

cache_write_tokens

Input tokens written to the prompt cache, already counted in input_tokens.

Type:

int

tool_calls

Tool invocations the model completed during the run.

Type:

int

cost

Run cost in the engine’s currency, or None when the engine could not price the model. Absent rather than zero: a zero would silently win a cost comparison.

Type:

decimal.Decimal | None

details

Every field the engine reported that has no named field here, under the engine’s own names. Not disjoint from the named counters: a provider that reports its own cached_tokens alongside the normalised cache_read_tokens has both, so summing details double-counts.

Type:

collections.abc.Mapping[str, int | float]

property total_tokens: int

Return the input plus output tokens of the run.

property cache_hit_ratio: float

Return the fraction of input tokens served from the prompt cache.

Zero when the run reported no input tokens.

class loom.ai.abc.Conversation(*, conversation_id, history=None)[source]

Bases: LoomFrozenStruct

The conversation a run continues.

Parameters:
  • conversation_id (str)

  • history (bytes | None)

conversation_id

The application’s identifier of the conversation; opaque to loom.

Type:

str

history

Prior turns in the engine’s own serialised form, opaque to loom; None on the first turn.

Type:

bytes | None

class loom.ai.abc.AgentResult(*, output, usage, interaction_id=None, hook_result=None, messages=None)[source]

Bases: LoomFrozenStruct

Outcome of a non-streaming agent run.

Parameters:
output

Answer already decoded and validated against the declared output shape.

Type:

object

usage

Resource accounting of the run.

Type:

loom.ai.abc.AgentUsage

interaction_id

Identifier the runtime minted for this run.

Type:

str | None

hook_result

Return value of the on_output use case, when the plan declares one.

Type:

object | None

messages

New messages of this run in the engine’s own serialised form; None when they are longer than policies.max_history_bytes.

Type:

bytes | None

class loom.ai.abc.AgentAnswer(*, output, usage, interaction_id=None)[source]

Bases: LoomFrozenStruct, Generic[AnswerT]

Outcome of one run reached through an AgentHandle.

Carries this run’s answer and this run’s own accounting, and nothing else: usage is scoped to the single call that produced this answer and is never merged with the usage of another call the same handle made, or of the parent run that reached this agent in the first place. A use case that runs the same handle three times gets three independent AgentAnswer values with three independent usage fields; summing them, if a caller wants a total, is the caller’s own arithmetic.

Parameters:
output

Decoded answer of this run — the artefact’s declared output shape by default, or the type passed as expect when the run overrode it for this call only.

Type:

loom.ai.abc.AnswerT

usage

Resource accounting of this run only.

Type:

loom.ai.abc.AgentUsage

interaction_id

Identifier the runtime minted for this run.

Type:

str | None

class loom.ai.abc.McpHandle(*args, **kwargs)[source]

Bases: Protocol

A caller-bound, filtered view of one MCP server’s tools.

Two paths hand one out, and they differ in where the filter comes from. AgentHandle.mcp() returns the artefact’s own grant: the very same composed view, over the same shared session and the same include/exclude predicate, that the model’s own toolset runs over — nothing there is re-declared, so it cannot diverge. An Mcp() marker returns a view whose filter is the signature’s own include, which may be wider than any agent’s. That is a second, independently declared filter, and the guarantee it carries is not that no wider view exists: it is that the widening is written down in the signature and verified against the server’s real tool list at start-up.

Both are views over one shared session, never a second connection, and both run under the same guard — authentication, timeout, span, expiry. Only the deadline’s source differs: the agent path uses its plan’s numbers, and the marker path, which belongs to no plan, uses the server’s own timeout_ms.

tools()[source]

Return the tool names visible through this grant’s own filter.

Returns:

Tool names already narrowed by this view’s own filter — the artefact’s declared include/exclude when the handle came from an agent, the signature’s own include when it came from an Mcp() marker.

Return type:

tuple[str, …]

async call(tool, arguments, *, expect)[source]

Call tool and decode its structured result into expect.

Two methods exist here instead of one method with an optional expect — this one and call_untyped() — because an optional argument would govern two different behaviours from one parameter, which this framework’s rules on multi-behaviour flags forbid, and it would turn the unshaped path into an omission instead of a decision. Typed is what this method commits to; the unshaped path is the separately named, deliberate exception.

Loom does not compare expect against the tool’s published output schema — that comparison is JSON Schema subsumption, whose verdict would only be approximate. Decoding the structured result into expect is the check, and it is exact: a mismatch names the field and the type that did not fit.

Parameters:
  • tool (str) – Tool name, as returned by tools().

  • arguments (Mapping[str, Any]) – Arguments passed to the tool call.

  • expect (type[ToolResultT]) – Type the tool’s structured result is decoded into. The tool must publish an output schema; one that does not is refused before any network call.

Returns:

The decoded result.

Raises:

AgentRunError – With a code naming why the call did not produce a decoded expect — the tool is outside this grant’s filter, the tool publishes no output schema, the tool reported a failure, the server returned no structured content despite publishing a schema, the structured content did not decode into expect, or — on the Mcp() marker path, under ai.remote_clients: optional — the server this grant names never connected at start-up (TOOL_UNAVAILABLE).

Return type:

ToolResultT

async call_untyped(tool, arguments)[source]

Call tool and return the server’s own result, undecoded.

The deliberate exception to call()’s typed default — for a tool that never publishes an output schema, or for a caller who genuinely wants the server’s own shape. Mapping[str, Any] is admissible here specifically because this signature has nowhere to put a type parameter; it is not a general-purpose escape hatch.

Parameters:
  • tool (str) – Tool name, as returned by tools().

  • arguments (Mapping[str, Any]) – Arguments passed to the tool call.

Returns:

The server’s own structured result, unvalidated and undecoded.

Raises:

AgentRunError – With a code naming why the call failed — the tool is outside this grant’s filter, the tool reported a failure, the server returned structured content that is not a mapping (a list, a scalar) — a contradiction of the protocol this method returns, distinct from returning no structured content at all, which comes back as {} — or, on the Mcp() marker path under ai.remote_clients: optional, the server this grant names never connected at start-up (TOOL_UNAVAILABLE).

Return type:

Mapping[str, Any]

class loom.ai.abc.SqlGrantHandle(*args, **kwargs)[source]

Bases: Protocol

One artefact’s own bounded view of one of its sql grants.

Queries the same read-only connection under the same row and byte bounds, and the same plan timeout, that the artefact’s own sql capability enforces for the model — the granted view, not a second one a use case could widen.

async query(statement, *, parameters=None)[source]

Run a read-only statement bounded by this grant’s own limits.

Parameters:
  • statement (str) – SQL statement to run against the granted connection.

  • parameters (Mapping[str, Any] | None) – Server-side bound parameters, when the statement uses them.

Returns:

Result rows, each as a column-name-to-value mapping, truncated to this grant’s row and byte bounds.

Raises:

AgentRunError – With a code naming why the query did not run — the connection is outside this artefact’s grants, or the grant’s own bounds rejected the result.

Return type:

Sequence[Mapping[str, Any]]

class loom.ai.abc.AgentHandle(*args, **kwargs)[source]

Bases: Protocol[AnswerT]

A named agent reached from another use case, bound to this run’s caller.

Filled by the executor when a use case declares one in its execute signature through loom.core.use_case.markers.Agent(), and never constructed directly. The type argument this Protocol carries — AgentHandle[SeverityAssessment] on the parameter’s annotation — is what the compiler checks against the named agent’s own declared output at start-up; Agent() itself returns an untyped value, for the same reason every other marker in this vocabulary does.

Exactly three arguments cross this boundary on a per-run basis: the prompt, the shape of the answer, and which conversation it continues. Nothing else does. What the agent may reach, what it may cost and which model serves it are decided once — by the artefact and by deployment configuration — and stay there; a per-run argument that changed any of them would be a second place the same policy could drift.

async run(prompt: str, *, conversation_id: str | None = None, state: object | None = None) AgentAnswer[AnswerT][source]
async run(prompt: str, *, expect: type[ExpectedT], conversation_id: str | None = None, state: object | None = None) AgentAnswer[ExpectedT]

Run the agent once and decode its answer.

Without expect, the answer is decoded into the artefact’s own declared output shape — this handle’s type argument. With expect, that declared shape is a default rather than a ceiling: this run only is decoded into expect instead. The override applies to this call and nothing else; it is never merged with the artefact’s declared shape and never carries over to the handle’s next call.

The artefact’s own output check — the retry loop that asks the model to correct a violation of its declared schema — does not run when expect overrides the shape. That is forced, not chosen: the check is compiled against the declared schema, so handing it another shape would either fail inside the engine’s own retry loop or invent a verdict loom has no basis for. Validating an overridden shape is the calling code’s job instead, and it is better placed there: holding a typed answer, calling code can act on a bad verdict — for example asking again in a loop it controls — rather than only reporting one.

Permissions never travel through this call. What the agent may reach comes from the artefact’s own grants and from the identity already bound to this handle; expect changes what comes back, never what the agent is allowed to do.

Parameters:
  • prompt (str) – Prompt for this run.

  • expect (type[ExpectedT] | None) – When given, decode this run’s answer into this type instead of the artefact’s declared output. Applies to this run only.

  • conversation_id (str | None) – Identifier of the conversation this run continues; None runs single-shot.

  • state (object | None) – This run’s state, or None. Reaches the artefact’s dependency bundle normalised against its declared shape; given against an artefact declaring no deps_type or deps_schema, the call is refused rather than dropped silently (FR-010).

Returns:

The decoded answer, this run’s own usage and its interaction id.

Raises:

AgentRunError – With AGENT_RUN_SHAPE_WITH_HOOK when expect is given and the artefact’s output hook command declares the output field — refused before the model is called, since the hook could not be handed an answer shaped by expect; with STATE_UNDECLARED when state is given and the artefact declares no state shape.

Return type:

AgentAnswer[AnswerT] | AgentAnswer[ExpectedT]

async run_text(prompt, *, conversation_id=None, state=None)[source]

Run the agent for open prose, pinning this run’s answer to str.

A named spelling of run(prompt, expect=str) rather than a third overload of it — the name states the form this run asks for, open prose, not the author’s intent, because the form is the only part loom knows. Being a shape pin, this mode runs no output check (run()’s own note on expect applies here too: the check is compiled against the artefact’s declared schema and cannot validate another one) and is refused before the model is called under exactly the condition run’s expect is: when the artefact’s output hook command declares the output field, which was compiled against the declared shape and cannot be handed str instead.

Parameters:
  • prompt (str) – Prompt for this run.

  • conversation_id (str | None) – Identifier of the conversation this run continues; None runs single-shot.

  • state (object | None) – This run’s state, forwarded to run() unchanged; see its own state for what it does and how it can fail.

Returns:

The model’s own prose, this run’s usage and its interaction id.

Raises:

AgentRunError – With AGENT_RUN_SHAPE_WITH_HOOK when the artefact’s output hook command declares the output field — refused before the model is called, for the same reason run() raises it with expect given; with STATE_UNDECLARED for the same reason run() raises it.

Return type:

AgentAnswer[str]

mcp(server)[source]

Return the artefact’s own filtered view of one mcp grant.

Parameters:

server (str) – Server name as the artefact’s own mcp capability declares it. Not verified at start-up — only a compiled agent name and its output type are — so a typo here is caught on first call, not before.

Returns:

The grant’s own view, filtered exactly as the model’s is.

Raises:

AgentRunError – With MCP_GRANT_UNKNOWN when the artefact declares no mcp grant on that server name.

Return type:

McpHandle

sql(connection)[source]

Return the artefact’s own bounded view of one sql grant.

Parameters:

connection (str) – Connection name as the artefact’s own sql capability declares it. Not verified at start-up, for the same reason mcp()’s server is not.

Returns:

A view bounded by that grant’s own row and byte limits, under the plan’s timeout.

Raises:

AgentRunError – With SQL_GRANT_UNKNOWN when the artefact declares no sql grant on that connection name.

Return type:

SqlGrantHandle

grants()[source]

Return every grant name reachable through mcp() and sql().

Exists so application code — most usefully a test — can pin a grant name in one assertion instead of reading the artefact’s YAML.

Returns:

Every mcp server name and sql connection name the artefact declares: every server first, then every connection, each group in declaration order.

Return type:

tuple[str, …]

class loom.ai.abc.TextDeltaEvent(*, text)[source]

Bases: LoomFrozenStruct

Incremental model text.

Parameters:

text (str)

text

Text fragment, passed through unmodified.

Type:

str

class loom.ai.abc.ToolCallEvent(*, tool, call_id, arguments)[source]

Bases: LoomFrozenStruct

The model invoked a tool.

Parameters:
tool

Tool name as the engine exposes it.

Type:

str

call_id

Correlation id matching the eventual tool_result.

Type:

str

arguments

Arguments the model supplied.

Type:

collections.abc.Mapping[str, Any]

class loom.ai.abc.ToolResultEvent(*, call_id, ok, summary)[source]

Bases: LoomFrozenStruct

A tool invocation completed.

Parameters:
call_id

Correlation id of the originating tool_call.

Type:

str

ok

Whether the tool succeeded.

Type:

bool

summary

Short human-readable outcome; never the full payload.

Type:

str

class loom.ai.abc.ErrorEvent(*, code, message, interaction_id=None, usage=None)[source]

Bases: LoomFrozenStruct

The run failed mid-stream (FR-032).

Parameters:
code

Stable run-time failure code; the retry policy reads its class.

Type:

loom.ai.errors.AgentRunErrorCode

message

Human-readable description.

Type:

str

interaction_id

Identifier of the admitted run this failure belongs to; None before admission.

Type:

str | None

usage

What the failed run had already spent, when the engine knew it. A run that made three model round trips and then failed its output schema still cost money, and a model that fails more must not rank better on cost for it. None when nothing was spent or nothing was measurable — a refusal before admission, a run a declared limit killed from outside the engine. Not on the wire: the stream contract puts usage on final only.

Type:

loom.ai.abc.AgentUsage | None

class loom.ai.abc.FinalEvent(*, output, usage, interaction_id=None, hook_result=None, messages=None)[source]

Bases: LoomFrozenStruct

The run completed; the only variant carrying usage.

Parameters:
output

Answer already decoded and validated against the declared output shape.

Type:

object

usage

Resource accounting of the whole run.

Type:

loom.ai.abc.AgentUsage

interaction_id

Identifier the runtime minted for this run.

Type:

str | None

hook_result

Return value of the on_output use case, when the plan declares one.

Type:

object | None

messages

New messages of this run in the engine’s own serialised form; None when they are longer than policies.max_history_bytes.

Type:

bytes | None

loom.ai.abc.AgentEvent = loom.ai.abc.TextDeltaEvent | loom.ai.abc.ToolCallEvent | loom.ai.abc.ToolResultEvent | loom.ai.abc.ErrorEvent | loom.ai.abc.FinalEvent

Closed five-member tagged union of streaming events (FR-030).

Exactly one of final or error terminates every stream (SC-011), and final is the only variant carrying usage.

Adding a variant requires two independent real consumers (FR-035): a single engine wanting a richer event is not grounds to widen a union every SSE client, test fake and contract suite must understand.

class loom.ai.abc.AgentEngine(*args, **kwargs)[source]

Bases: Protocol

One compiled agent, ready to run.

Engines take a single prompt and, optionally, the conversation the run continues: an opaque, engine-native history the application loaded (FR-034). Loom defines no message model and stores no history; an engine that receives conversation=None runs single-shot.

async run(prompt, *, identity, conversation=None, state=None)[source]

Run the agent to completion.

Parameters:
  • prompt (str) – Caller prompt.

  • identity (Identity) – Verified caller; every capability call runs as them.

  • conversation (Conversation | None) – The conversation this run continues; None runs single-shot.

  • state (object | None) – This run’s state, already decoded by whichever boundary received it and normalised against the artefact’s declared shape — never parsed again here (FR-008). None for an artefact declaring no state, or for a stateful one whose caller supplied none, in which case it carries the shape’s own declared defaults instead of an empty value.

Returns:

The validated output, the run’s usage and this run’s messages, bounded by policies.max_history_bytes (None above it).

Return type:

AgentResult

run_stream(prompt, *, identity, conversation=None, state=None)[source]

Run the agent, streaming events.

Returns an async context manager rather than a bare iterator so that closing the stream — and the provider connection behind it — is deterministic on exit instead of being left to the garbage collector.

Parameters:
  • prompt (str) – Caller prompt.

  • identity (Identity) – Verified caller; every capability call runs as them.

  • conversation (Conversation | None) – The conversation this run continues; None runs single-shot.

  • state (object | None) – This run’s state; see run()’s own state for what it carries.

Returns:

An async context manager yielding the event stream.

Return type:

AbstractAsyncContextManager[AsyncIterator[TextDeltaEvent | ToolCallEvent | ToolResultEvent | ErrorEvent | FinalEvent]]

async health()[source]

Report the engine’s current health without per-call network I/O.

Returns:

The engine’s state, derived from outcomes it has already observed rather than from a probe issued on this call.

Return type:

HealthStatus

loom.ai.abc.HealthState

The three health states. Defined beside the struct that carries it so the alias and the field cannot drift apart.

alias of Literal[‘ok’, ‘degraded’, ‘unavailable’]

class loom.ai.abc.HealthStatus(*, status, detail=None)[source]

Bases: LoomFrozenStruct

Health of one agent engine, shared by every engine (FR-048).

Parameters:
  • status (Literal['ok', 'degraded', 'unavailable'])

  • detail (str | None)

status

"ok", "degraded" or "unavailable".

Type:

Literal[‘ok’, ‘degraded’, ‘unavailable’]

detail

Optional human-readable explanation.

Type:

str | None

class loom.ai.abc.McpToolInfo(*, name, has_output_schema)[source]

Bases: LoomFrozenStruct

One tool a session’s server advertises.

Parameters:
  • name (str)

  • has_output_schema (bool)

name

Tool name as the server exposes it.

Type:

str

has_output_schema

Whether the server published a schema for this tool’s structured result. A McpHandle refuses a typed call on a tool for which this is False, before any network call — see McpHandle.call().

Type:

bool

class loom.ai.abc.McpToolCallResult(*, ok, structured=None)[source]

Bases: LoomFrozenStruct

The server’s own answer to one call_tool, protocol-level and undecoded.

Parameters:
ok

False when the server flagged the call as failed.

Type:

bool

structured

The tool’s structured result, or None when the server returned none — including every call the server flagged failed, whose content is never treated as an answer.

Type:

object | None

class loom.ai.abc.McpSession(*args, **kwargs)[source]

Bases: Protocol

Minimal MCP session the runtime needs from any client library.

Migration (breaking, from v1.16.1): list_tools used to return tool names (tuple[str, ...]) and call_tool used to return the server’s structured content directly (object). Both shapes shipped, so a third-party session implementing this Protocol has to update both methods. list_tools now returns McpToolInfo so a caller can see which tools publish an output schema, and call_tool now returns McpToolCallResult so a caller can see the server’s own failure flag instead of having it silently folded into a successful-looking return. Nothing else about the Protocol moved.

async list_tools()[source]

Return the tools the server exposes.

Returns:

Every tool the server advertises, before any declared filter is applied, each carrying whether it publishes an output schema.

Return type:

tuple[McpToolInfo, …]

async call_tool(name, arguments)[source]

Invoke one tool and return its protocol-level result.

Parameters:
  • name (str) – Tool name as the server exposes it.

  • arguments (Mapping[str, Any]) – Arguments to pass to the tool.

Returns:

The server’s own error flag and structured content, neither interpreted nor decoded.

Return type:

McpToolCallResult

class loom.ai.abc.ConcurrentMcpSession[source]

Bases: object

Declares an McpSession implementation already safe for concurrent calls.

A JSON-RPC session is one framed stream: two callers writing into it at the same time can interleave their frames, and a caller cancelled mid-call can leave the stream desynchronised for whoever is waiting beside it. That is why a session gets serialised behind one lock by default. A session that already guards its own frames — one that multiplexes concurrent calls by matching each response back to its own request id, rather than writing straight through a single unmatched stream — declares that guarantee by also subclassing this, in addition to implementing McpSession, and the runtime leaves it unwrapped.

This class carries no members: subclassing it is the declaration. A session that does not subclass it is treated exactly as every McpSession was before this class existed — wrapped and serialised — because not declaring the guarantee is the safe default, never a failure.

A subclass keeping this promise must (a) tolerate overlapping calls by matching each response back to its own request id — the multiplexing that makes the guarantee true in the first place — and (b) must not shield a call from its own caller’s cancellation the way the runtime’s locked wrapper does: with no shared frame to desynchronise, there is nothing left to drain, and shielding would only stop the plan’s own tool_timeout_ms from bounding the call.

class loom.ai.abc.ToolsetContext(*args, **kwargs)[source]

Bases: Protocol

What a kind: python factory may reach while building its toolset.

A build-time object: the engine hands it to the factory once, at start-up, and nothing keeps it alive afterwards. A factory resolves the remotes it needs in its body and keeps the session on the toolset it returns; it must not call remote() lazily from a tool at run time.

remote() is bounded to the mcp grants of the same agent: it returns the worker’s shared session for a server the agent’s own artifact declared, and nothing else. Calls made through that session go to the shared connection directly, so they bypass the include/exclude filter of the mcp grant.

property agent: str

Name of the plan being built.

property container: LoomContainer

Application container the factory may resolve services from.

remote(server)[source]

Return the agent’s shared session for one of its mcp servers.

Parameters:

server (str) – Server name as the agent’s mcp grant declares it.

Returns:

The session the agent’s own mcp toolset runs over.

Raises:

AgentCompilationError – When the agent has no mcp grant on that server.

Return type:

McpSession

loom.ai.abc.ToolsetFactory

Target of a kind: python capability, validated at compile time.

Called exactly once at build as factory(context, **params): the first positional is a ToolsetContext, and the artifact’s params arrive as keyword arguments. A factory declares its own named parameters, with defaults, and returns the engine-facing toolset. The parameter names are checked against the signature at compile time. It is a plain Callable alias rather than a Protocol because a Protocol fixing **params would reject every factory that names them.

alias of Callable[[…], object]

class loom.ai.abc.DepsFactory(*args, **kwargs)[source]

Bases: Protocol

Builds per-invocation dependencies for capability calls.

Singleton services are captured once at build; Identity is supplied per invocation so every capability call runs as the caller (FR-043). The bundle also carries the invocation’s state, alongside the verified caller and the container, never instead of them: a capability call reads identity and container off the bundle the same way regardless of whether state is present.

build(identity, container, state=None)[source]

Build the dependency bundle for one invocation.

Parameters:
  • identity (Identity) – Verified caller of this invocation.

  • container (LoomContainer) – Application container holding the singleton services.

  • state (Mapping[str, Any] | None) – The invocation’s state, a mapping normalised against the artefact’s declared shape, or None for an artefact that declares no state or a call that supplies none.

Returns:

The engine-facing dependency bundle.

Return type:

object

loom.ai.abc.NativeToolSupport

Answers which provider tools a model binding admits, by loom tool name.

Supplied by an engine as an optional native_tool_support attribute and read with getattr, so the compiler learns what a binding admits without importing an engine. May raise AgentCompilationError when the provider SDK is missing.

alias of Callable[[InferenceTarget], frozenset[str]]

loom.ai.abc.OutputCheck

a pure predicate over an answer.

Called once per attempt inside the engine’s own output-retry loop, with the mapping the engine parsed from the model’s answer — never loom’s decoded object, so nothing is decoded twice. Returns None to accept the answer unchanged, or the text the model must read to correct itself, which drives a real retry bounded by the artifact’s policies.retries.

The return contract is the inverse of the usual predicate convention, which is why this alias is published rather than left for an author to spell: it is the one name in this module carrying Mapping[str, Any], because the payload shape is the artifact’s own declared schema, which loom cannot type statically.

A returned mapping is never substituted for the answer: the engine decodes the model’s own bytes independently of what this callable returns, so a check that builds and returns a different mapping has that mapping discarded. Synchronous and dependency-free, because any side effect inside the retry loop would run once per attempt.

Type:

Target of an output_check reference

alias of Callable[[Mapping[str, Any]], str | None]

class loom.ai.abc.AgentEngineProvider(*args, **kwargs)[source]

Bases: Protocol

Entry-point target in group loom.ai.engines.

LOOM_AI_ENGINE_API

Handshake version, checked with getattr on load — never with isinstance.

Type:

ClassVar[int]

native_tool_support

Optional NativeToolSupport, read with getattr; an engine that serves no native grant omits it.

create_engine(plan, *, deps, container)[source]

Build one engine for one compiled plan.

Called exactly once per plan by the runtime, never per request.

Parameters:
  • plan (object) – The compiled AgentPlan. Typed as object here because the plan struct is built in phase 4; the parameter narrows to AgentPlan then (recorded decision).

  • deps (DepsFactory) – Per-invocation dependency factory.

  • container (LoomContainer) – Application container.

Returns:

The engine serving this plan.

Return type:

AgentEngine

supported_capability_kinds()[source]

Capability kinds this engine can serve.

The compiler receives the result as a plain value resolved by the bootstrap — nothing in loom.ai imports an engine to obtain it.

Returns:

The supported kind identifiers.

Return type:

frozenset[str]