agentkit.adapters¶
Concrete Port implementations: LLM providers (Claude, OpenAI,
DeepSeek, OpenRouter), vector / store / checkpoint back-ends,
observer + replay tooling, and the OTel bridge.
Most adapters are behind an opt-in extra (http, postgres, redis,
observability) so the zero-dep core stays clean.
agentkit.adapters itself re-exports nothing — each adapter lives in
its own subpackage. Reference them directly:
LLMPort adapters — CallableLLM (inject any provider).
Model fallover is no longer an adapter — it's the fallback middleware. These adapters just wrap a
single provider seam. CallableLLM adapts an injected fn/chat_fn (e.g. the OpenRouter
ModelSDKTools). The raw↔LLMResult translation helpers live in _mapping. The public surface
is agentkit.adapters.llm.
For the offline, deterministic FakeLLM/Turn test double, import from agentkit.testing —
real adapters live in adapters/, test doubles in testing/.
CallableLLM
¶
CallableLLM(
fn: Callable[..., Any],
*,
to_result: Callable[[Any], LLMResult] | None = None,
chat_fn: Callable[..., Any] | None = None,
cost_fn: Callable[[Any, int, int], float] | None = None,
)
Adapt fn(*, system, user, model, …) (single call) and optionally
chat_fn(*, messages, model, tools, …) (tool-aware) into an LLMPort. Sync fns run off the loop
via asyncio.to_thread; coroutines are awaited. Provider tool-calls are normalized to ToolCall.
StorePort adapters — ONE durable-KV seam backing checkpoints, idempotency, audit, and cache.
InMemoryStore is the offline reference + contract (single-flight get_or_set, never-negative on a
raised producer, append-only logs). FileStore is the zero-dependency durable adapter (survives a
restart). RedisStore/PostgresStore (behind extras) are the same shape over real infra. One KV seam
serves checkpoints, idempotency, the audit log, and the result cache.
One backend per module (single responsibility); the public surface is agentkit.adapters.store.
FileStore
¶
Durable StorePort backed by JSON files under base_dir — survives a process restart, so a
human-gate suspend or a crashed run resumes from disk. Zero-dependency (stdlib json/pathlib) and
async-first: the blocking file I/O is bridged via asyncio.to_thread so it never stalls the loop.
The reference durable adapter; a Postgres/Redis StorePort is the same shape behind an extra. Values
must be JSON-serializable (which the loop/workflow checkpoints are). Single-flight is in-process (an
asyncio.Lock per key); cross-process single-flight needs the real DB's transaction — documented.
ttl is accepted but not enforced (no expiry sweeper); use a TTL-native backend for that.
InMemoryStore
¶
get_or_set
async
¶
Single-flight: a hit returns the stored value; a miss runs fn once and stores it. A raised
fn propagates and is NOT stored, so a transient error retries clean (failures are never cached).
ttl is applied on the stored result, matching the other backends.
PostgresStore
¶
Durable StorePort over Postgres (extra: arc-agentkit[postgres], via asyncpg). One KV table +
one append-log table, JSON-text values. Call await init() once to create the tables. Single-flight is
in-process; durability/atomicity is Postgres's. TTL is not enforced (no sweeper — use RedisStore for TTL).
Inject a pool for tests; else an asyncpg pool is created from dsn.
RedisStore
¶
Durable StorePort over Redis (extra: arc-agentkit[redis]). KV via GET/SET(EX), append-logs via
RPUSH/LRANGE, values JSON-encoded. Single-flight is in-process (an asyncio.Lock per key); TTL is honored
natively (SETEX). Inject a client (e.g. a fake) for tests; else built from url.
VectorPort adapters.
InMemoryVector
¶
VectorPort — scope-isolated, cosine over TF vectors, metadata-filtered, scored (tests/lean).
CheckpointPort adapters — InMemoryCheckpointStore is the offline reference + contract
every durable backend matches; PostgresCheckpointStore is the production durable backing
(extra: arc-agentkit[postgres]). Public surface is agentkit.adapters.checkpoint.
InMemoryCheckpointStore
¶
A CheckpointPort over an in-process dict. One list of Checkpoints per run_id.
PostgresCheckpointStore
¶
A CheckpointPort over Postgres via asyncpg. Pool is application-owned (matches the
explicit-lifecycle convention PostgresStore follows — see store/postgres.py). Call
await ensure_schema() once at startup; the per-call methods then go through pool.acquire()
like every other asyncpg adapter in this package.
ensure_schema
async
¶
Idempotent DDL — CREATE TABLE IF NOT EXISTS + CREATE INDEX IF NOT EXISTS. Safe to
call multiple times. Apps call this once at startup before any save/load (mirrors
PostgresStore.init()).
Concrete observer adapters for the observation channel.
CollectingObserver/QueueObserver— terminal sinks (list capture; bounded async-consumable queue).PolicyObserver/RollupObserver— emission-cadence wrappers around an inner observer.Hooks— lifecycle subscription (on(stage, handler)), sugar over the stream.
In-process transports. A cross-process transport (e.g. Redis pub/sub → SSE) is a future adapter on the
same ObserverPort. The public surface is agentkit.adapters.observer.
PolicyObserver
¶
The emission-cadence knob: forwards only selected kinds to an inner observer.
result/error/interrupt always pass (never silence a result or a human gate); other kinds pass
only if allow is None (everything) or contains the kind. Compose it around any ObserverPort
(e.g. a QueueObserver); the consumer still streams from the inner observer. Emit-only by design —
an ObserverPort needs only emit.
everything
classmethod
¶
everything(inner: Any) -> PolicyObserver
Forward every observation (the streaming/step cadence).
summaries
classmethod
¶
summaries(inner: Any) -> PolicyObserver
Forward rolled-up summary/progress (+ always-forwarded), drop finer events.
result_only
classmethod
¶
result_only(inner: Any) -> PolicyObserver
Send only on a complete result/error (+ interrupt) — the quiet-pipeline cadence.
RollupObserver
¶
RollupObserver(
inner: Any,
*,
every: int = 8,
summarize: Callable[
[Sequence[Observation]], str | Awaitable[str]
]
| None = None,
kind: ObservationKind = "summary",
)
The 'rolled-up summary' cadence: buffer non-critical observations and emit one
summary every every of them — flushing the buffer first whenever a critical kind
(result/error/interrupt) passes through, and again on close().
summarize(buffer) -> str builds the roll-up text; it may be sync or async (async is the hook for
an LLM judge or a Compactor-backed summariser). Critical observations are always forwarded
immediately (after the flush), so a result is never delayed behind buffered progress.
close
async
¶
Flush any buffered tail (and close the inner observer if it supports it).
Hooks
¶
Lifecycle subscription: a thin ObserverPort that dispatches each observation to handlers
registered with on(stage, handler), where stage is the observation kind (e.g. run_start,
summary, interrupt, error, result) or "*" for every observation.
Lifecycle is not a new mechanism — it's the observation stream, named. Handlers are sync or
async and never break the run: an exception in a handler is swallowed.
Optionally forwards to an inner observer, so Hooks chains in front of a QueueObserver.
CollectingObserver
¶
Records every observation into .items (no backpressure). For tests and small in-proc consumers.
QueueObserver
¶
Non-blocking, bounded async observer with the never-drop-results rule.
emit never blocks and never raises into the run. Non-critical observations (progress/summary) are
bounded to maxsize; when over, the oldest non-critical is dropped (coalesced). result/error
are never dropped and never count against the bound. stream() async-iterates in insertion order
until close().
OpenTelemetry bridge. Distinct from adapters.observer (in-process
observer-pattern impls). Requires the observability extra.
otel_exporter_otlp_http
¶
One-call setup of the OTel SDK with an OTLP-HTTP exporter.
Builds a TracerProvider with a BatchSpanProcessor wrapping
an OTLP-HTTP exporter pointed at endpoint (or the
OTEL_EXPORTER_OTLP_ENDPOINT env var). Call once at process
startup BEFORE otel_tracer() is consumed — sets the global
tracer provider.
The convenience version: operators who want a "give me OTel
with sensible defaults" path call this. Operators with their
own setup (custom processor, multiple exporters, etc.) skip
this and configure opentelemetry.trace.set_tracer_provider
themselves.
otel_meter
¶
Construct a MetricsPort backed by the OpenTelemetry SDK.
Production: pass nothing — pulls the global meter (configured
via env vars OTEL_EXPORTER_OTLP_ENDPOINT /
OTEL_EXPORTER_OTLP_METRICS_ENDPOINT). Tests: pass an explicit
meter_provider so each test gets isolated state.
Raises ImportError when opentelemetry-sdk isn't installed
(i.e., the [observability] extra wasn't installed); the
error message names the extra so the operator knows what to do.
otel_metrics_exporter_otlp_http
¶
One-call OTel metrics SDK setup with an OTLP-HTTP exporter.
Sister to otel_exporter_otlp_http for traces. Call once at
startup BEFORE otel_meter() is consumed — sets the global
meter provider with a PeriodicExportingMetricReader pushing
to the OTLP-HTTP endpoint every interval_ms (default 60s, the
OTel default).
The convenience version: operators who want a "give me OTel metrics
with sensible defaults" path call this. Operators with their own
setup (custom readers, multiple exporters, etc.) skip this and
configure opentelemetry.metrics.set_meter_provider themselves.
otel_sampler
¶
Convenience: build a SamplerPort from agentkit's pure-Python
TraceIdRatioSampler. ratio=1.0 = always-on; ratio=0.1 = 10%.
The sampler is a kernel-level seam (no OTel dependency) so this
factory just wraps the in-tree impl — it lives next to the OTel
metrics/tracer factories so operators have ONE module to import
from when wiring observability. For richer policies (parent-based,
rule-based), construct your own SamplerPort impl and inject it
directly into Services(sampler=...).
otel_tracer
¶
Construct a TracePort backed by the OpenTelemetry SDK.
Production: pass nothing — pulls the global tracer (configured via env vars OTEL_SERVICE_NAME / OTEL_EXPORTER_OTLP_ENDPOINT / OTEL_EXPORTER_OTLP_HEADERS / ... per the standard OTel SDK convention).
Tests: pass an explicit tracer_provider (e.g. a
TracerProvider wired to InMemorySpanExporter) so each
test gets its own isolated provider. OTel forbids overriding
the global set_tracer_provider once it's been set, so
sharing a global across tests gives stale-provider failures.
Raises ImportError when opentelemetry-sdk isn't installed
(i.e., the [observability] extra wasn't installed); the
error message names the extra so the operator knows what to do.
ReplayStore adapters.
FileReplayStore
¶
ReplayStore that writes each record to <root>/<span_id>.json.
Constructed with an explicit root directory (created on first
write if missing). Use FileReplayStore.from_env() to read
the RIO_REPLAY_DIR env var.
Best-effort: a failed write logs a warning and returns None
(the contract of ReplayStore.put says writes must not raise
into the run). A failed read returns None (same as a
genuine miss).
from_env
classmethod
¶
from_env() -> FileReplayStore | None
Read RIO_REPLAY_DIR and construct; return None when
the env var isn't set so callers can fall back to
NoopReplayStore.
default
classmethod
¶
default() -> FileReplayStore
Standard location: $XDG_DATA_HOME/rio/replays or
~/.rio/replays.