Agent Tool Invocation
Spectral runs one LangGraph-driven production agent — the World Agent. Its tool registry
lives at world-agent.mdx.
This page describes the tool-invocation contract: envelope, error taxonomy, retry behavior, observability, approval mechanism, and framework-layer composition pattern. Decision lineage in ADR-060.
Runtime placement
Section titled “Runtime placement”The agent runtime lives in apps/workers. apps/api is thin: authentication, AgentTask dispatch via outbox, and SSE streaming proxy. Workers consumes AgentTask events, loads checkpointer state, runs the LangGraph orchestrator, executes tool calls, writes memory, and streams output via Supabase Realtime channel keyed by conversation_id; apps/api proxies the Realtime channel as SSE to the client.
Approval interrupts use LangGraph’s interrupt() to suspend the run; the checkpointer persists state; an operator response (HTTP into apps/api) resumes via Command(resume=...).
Tool envelope
Section titled “Tool envelope”Tools are plain async callables produced by closed-over-DI factories. Cross-cutting metadata is captured at call time via a lightweight ToolCallMetadata value object emitted by the observed_tool decorator. There is no ToolCallEnvelope wrapper around every tool body.
spectral.core.tools.metadata.ToolCallMetadata:
tool_name: stragent_name: strlatency_ms: intok: boolerror_class: str | Nonetrace_id: strstarted_at: datetimeended_at: datetime
The observed_tool decorator emits one ToolCallMetadata per call to structlog and OTel (per observability-stack) and integrates with LLM cost tracking when the tool body invokes an LLM.
Error taxonomy
Section titled “Error taxonomy”Four classes in spectral.core.tools.errors:
ToolUserError— invalid input from user/operator (bad args, missing context); user-visibleToolPolicyError— policy/scope/approval denied; user-visible with framingToolTransientError— infrastructure transient (DB blip, brief LLM provider rate-limit)ToolTerminalError— non-recoverable (invariant violation)
The taxonomy’s role is to shape what the LLM sees via the tool message. Tool errors flow back to the LLM as tool messages with the error class plus a human-readable description. The LLM decides next action: retry as-is, retry with modified args, surface to operator, abandon. LangGraph’s orchestrator-level recursion limit (default 25; configurable per agent) caps runaway loops as the circuit breaker.
There is no agent-layer retry budget. Tool implementations may include single-retry-on-transient-IO as an implementation detail (e.g., a DB connection blip); that is not contract.
Approval mechanism
Section titled “Approval mechanism”Mutate-with-call-time-approval tools use LangGraph’s interrupt() to suspend the run with a standardized payload. spectral.core.tools.approval.ToolApprovalRequest:
tool_name: stragent_name: strargs_summary: str— sanitized; PII-stripped; safe to display to the operatoreffect_description: str— human-readable description of what will changecorrelation_id: UUID
Operator response paths:
- Approve:
Command(resume=ApprovalGranted(...))resumes the run; the tool body executes. - Deny: the tool aborts with
ToolPolicyError(reason=APPROVAL_DENIED). - Request revision: the agent revises the proposed action and re-emits the approval request.
All paths audit-logged through the observability substrate.
Framework-layer composition
Section titled “Framework-layer composition”When a tool needs data or behavior in another context, the call flow goes through DI at the framework-layer composition seam — never via SQL grants. See Contract Surfaces for the canonical pattern.
Per ADR-065 D5, bridge tools live in apps/* framework deliverables, never in caller-context code; a bridge imports the callee context’s OHS Protocol from <context>.contracts.protocols.* (framework-to-context, allowed under the validator’s app-context-surface rule) and is composed into the caller agent’s tool list via DI at workers startup. The caller agent sees an opaque tool callable — never the Protocol or its implementation. Auto-generated docs for Protocols live under Protocols.
The pattern is forward-compatible for bridge tools when the flow shape is genuinely call (caller needs a result back). For eventual-consistency reads of facts already recorded elsewhere, notification flow + consumer-side local projection (per ADR-065 D4 + ADR-064 D3) is the canonical choice; no Reader Protocol is minted. See Contract Surfaces for the simplest-fit ladder.
Workshop discipline at the tool → memory boundary
Section titled “Workshop discipline at the tool → memory boundary”Tool outputs containing canonical content (rule body, audit-chain entry, customer PII) are not round-tripped into agent memory verbatim. Per the agent-memory-primitives workshop framing, agent memory holds thought-in-progress and workflow meta-state — not canonical content.
The repository gateway enforces typology-driven classification at the memory-write path; the trigram trigger backstops doctrine drift. There is no separate sanitization decorator on tools — discipline lives in the memory-write path, not the tool surface.
See also
Section titled “See also”- Agent architecture — runtime placement details; streaming pattern
- Contract Surfaces — the broader pattern
- Agent memory primitives — workshop framing
- Event substrate — DLQ underlying
- LLM platform — cost tracking integration
- Observability stack — span propagation; structlog
- ADR-060 — decision lineage