Skip to content

API reference

Top-level symbols exported by reactifact (see reactifact/__init__.py). The format for each group: name — one-line role. Details live in the doc-strings of the modules — see the auto-generated reference for those rendered directly from source (signatures, types, full docstring text) on the docs site, rather than copied here by hand.

Stability

reactifact is pre-1.0 (0.20.1) but not rc — the surface below is the stable contract, not a moving target. It is machine-checked: tests/test_public_api.py pins every __all__ listed here, so a new export has to be added deliberately (and documented on this page), never by accident.

  • Public API = every name in reactifact.__all__ (and each submodule's own __all__ — reactifact.recipes, reactifact.providers, reactifact.viz, reactifact.eval, reactifact.tracing, reactifact.quick, reactifact.redaction, reactifact.audit, …), which is exactly the set of symbols documented on this page and pinned by the freeze test. If it's importable from reactifact but not in __all__, it's an internal detail with no compatibility guarantee — e.g. reactifact.relations.RelationGraph and reactifact.commit_log.CommitLog exist because Context was split into smaller modules for readability, but neither is exported: Context is the supported surface, they are not.
  • SemVer, pre-1.0 style: a minor bump (0.20.1 → 0.21.0) may add symbols or, rarely, change behavior in a way CHANGELOG.md marks Breaking — pre-1.0 minors are where reactifact is still allowed to correct a design mistake. A patch bump (0.14.0 → 0.14.1) never removes or renames a public symbol and never changes documented behavior, only fixes bugs against it.
  • Deprecation: a symbol on its way out stays importable and keeps working for at least one minor release; it is marked deprecated in the docstring and CHANGELOG.md (with the replacement) before any removal, and removal only happens in a minor release under a ### Breaking heading.
  • Every breaking change is called out in CHANGELOG.md under a ### Breaking heading, even in a pre-1.0 release — see release.md. If you only read one section before upgrading, read that one.
  • 1.0 means this page stops moving: the release checklist is the bar for leaving pre-1.0; after that, SemVer 1.x applies (no breaking change in a minor).
  • Anything under reactifact.cli.* beyond the documented python -m reactifact … subcommands, and anything in a module's tests-only helpers, is implementation detail regardless of whether it happens to be importable.

Quick on-ramp (reactifact.quick)

Thin sugar over the primitives below for the four common first tasks — every object exposes the real .agent/.agents and the run's .context, so it graduates to Consume/Step/Effects with nothing to rewrite. See Quickstart §0.

Symbol Role
agent(system, schema) one structured LLM call → one typed artifact
rag(sources) retrieval → materialize → answer, with supported_by provenance
tools_agent(system, tools, human=False) LLM + tools (human=True → HITL asks)
chat_agent(agents) a configured ChatAssistant (in-memory store by default)
Question / Doc / Answer the generic artifact models the facade uses

Context & state

Symbol Role
Context versioned working state; resources; request (the active turn's per-request mapping); queries; latest(Model); announce; diff/rollback; compact(keep_commits=, keep_versions=) to bound history (compacted_at); apply_commit(commit) replays a commit into the live state (the append-only session load path)
View result of a typed join query (context.view(...))
RuntimeResources providers + sources + app resources; register(Type, instance) / get(Type) / require(Type) / has(Type) for typed collaborators (ResourceKey[T] when two of one type); string get/set stay as the additional escape hatch; redactor= scrubs trace text; trace_truncate= caps artifact/response JSON kept in a trace (None = no clip) — deprecated fallback (emits DeprecationWarning), prefer TraceLimits(max_data_chars=…); await resources.aclose() closes the llm/embedder HTTP clients (duck-typed) — call it yourself at real shutdown; record_tool_args=True puts tool args on ToolRun (scrubbed by redactor=)
ResourceKey[T] a typed handle for registering two resources of one type (primary/replica, per-tenant)
ResourceScope / RuntimeResources.scope(factory) async with builder that creates resources on the current loop and closes them on exit — the loop-safe way to own providers (see the class docstring)
current_request() the active turn's request mapping (same as StepCall.request)
Commit, Read, Write version bookkeeping and recorded provenance ops

Artifacts & changes

Symbol Role
Artifact immutable (id, version, data, content_hash) handle; history lives in Context (artifact_version/version_history/find_versions)
register_type(Model, *, type_id=, aliases=, migrate=) (reactifact.types) stable persisted type identity: a rename/move of a model no longer breaks loading old sessions/commits/events. type_id_of/resolve/migrate_payload; default id is the qualified name
Patch the runtime's compiled change-set (transport); steps write effects (effects/call.effects), Patch is assembled by the runtime
reactifact.operations (Create/Update/Delete/Link/Unlink/Relation) the compiled operations a patch carries (§12)
Create, Update, Delete, Link, Unlink, Relation op records from which patches are built

Agents & steps

Symbol Role
Agent thin container: name, steps, concurrency_limit; step consumes derive triggers
create_agent constructor-style Agent builder — no subclassing needed for plain containers
Wakes(False) / Scope / Consume / consume declarative (or decorator) reaction declaration; Consume.by_field for scoped events; Consume.by_base / include_subclasses=True wakes on a marker base's subclasses; wakes=False reads as input without waking the agent; debounce=True collapses several same-generation events into one run
reactifact.consume.CorrelatedConsume fires (and feeds inputs) only for a correlation key where every require type is present and every forbid type is absent — the mechanism behind JoinConsume/AbsentConsume
reactifact.consume.JoinConsume(*parts, key=…) CorrelatedConsume factory: fires once every listed type exists for the same key
reactifact.consume.AbsentConsume(type, absent_type=…, key=…) CorrelatedConsume factory: fires for type only where no matching absent_type exists yet for the same key
Step / @step the work unit: class run(...) gets typed inputs + effects: Effects; function @step gets call: StepCall and uses call.effects. consumes declares inputs/triggers, produces the allowed create types; return a model for create-sugar. Annotated[T, When(...)]/Consume.by_* select conditions; put extra creates in the same tuple (produces=(Primary, Extra)) — also_creates=/reacts_to= are deprecated aliases
Trigger secondary (non-artifact) enter condition for a step; context_condition(artifact, context) for conditions that need other artifacts (joins/correlation); debounce hint consumed by Runtime
StructuredGenerateAgent declarative LLM→schema→artifact agent (schema, build_prompt, fallback)
LLMAgent blocking LLM+tools loop (system, tools, max_steps, deferred_tool_groups)
HITLLMAgent LLM+tools loop that can pause for human answers (max_asks, resume reporting)
ToolUse, ToolUseHITL the tool-loop step; HITL variant waits for approval on execution
DeferredToolGroup (reactifact.tools.use) a tool group whose schemas stay out of the prompt until ToolUse's built-in load_tools tool requests it (LLMAgent/ToolUse only, not the HITL variant)
Tool, FunctionTool, tool, ToolOutput, ToolError tool abstraction and registration; ToolOutput.failed(ToolError(code=, message=, retryable=)) gives a typed failure while ToolOutput.error stays a plain string
ToolAnswer, Observation tool results and model observations (loop protocol)
PendingQuestion HITL primitive: a paused ask waiting for a human answer, resumed via effects.resume(...)
Effects.act(kind, *, key, payload=…) outbox primitive: records a PendingAction intent (stable id action:{key}) instead of doing I/O in the step; None if it already exists
PendingAction the recorded outbound side effect (kind, idempotency_key, payload, status, attempts); performed once by Runtime(dispatcher=...), then dispatched / failed (retry_action re-arms)

Runtime

Symbol Role
Runtime wakes agents on events; run / arun / astream (each takes request=Mapping); budget & concurrency; isolate_errors=True + on_agent_error(agent, event, exc) to keep one agent's exception from aborting the whole run (default: propagates, §69); dispatcher=async (context, action) drains the PendingAction outbox after each commit (once per stable id, keeping the turn alive until it is empty); on_dispatch_error(action, exc) fires when the dispatcher raises (the action is marked failed and the exception propagates); flush_pending_actions() drains without a generation (e.g. after merge()); Budget.max_seconds is a hard turn deadline (cancels the generation in flight)
Runtime.request_stop() / await ashutdown(timeout=…) / in_flight graceful shutdown: stop at the next generation boundary (RunOutcome.STOPPED; the current generation still commits); ashutdown waits, force-cancelling after timeout
reactifact.runtime.active_runs() / cancel_run(run_id) read-only snapshots of the process's in-flight turns (RunInfo) and targeted cancel — readiness/ops, in-process only
StepCall.request the turn's request mapping inside a step (Runtime.arun(request=…) / ChatAssistant.stream(request=…)); empty when none was set
Budget, RunOutcome, RunStats, BudgetTracker run limits (max_runs/max_iterations/max_seconds/max_tool_calls/max_tokens/max_cost) and the final outcome/usage stats (RunOutcome includes budget_time_exceeded and stopped). Token/cost limits count LLM usage without a tracer; cost needs an injected Pricer (reactifact.pricing)
Event, EventType the wire format of "something changed" — ARTIFACT_CREATED/UPDATED/DELETED/STALE
EventHub, ProgressEvent progress/announce channel consumed by web UIs
Scheduler filter → rank → LLM tie-break agent-selection policy, callable from the runtime each iteration (see design notes)
uncertainty_policy(...) builds the built-in hybrid Scheduler (filter → rank → LLM tie-break → top-k)

Logging (reactifact.logging)

Symbol Role
configure_logging(level="INFO", *, json=False, stream=None) turn on correlated structured logs under reactifact.* — silent until called; json=True → one JSON object per line
get_logger(name) a logger that injects the turn's correlation fields (run_id/session_id/generation/agent/request_id) into every record
bind(**fields) add app correlation fields for a block (tenant, user, …)

Chat layer (reactifact.chat + reactifact.web)

Symbol Role
ChatAssistant sessions + turn loop + history in one handle (stream/invoke/history); hooks: agents, user_message, reply, session_state; dispatcher=/on_dispatch_error= pass through to Runtime (outbox side effects from a chat turn)
ChatMemory optional bounded memory for ChatAssistant, each field independent: keep (prune the raw thread), summarize+summary_type (growing digest), compact_commits (Context.compact — bounds history without touching artifacts); message_types=[UserQuery, Answer] (or a marker base) bounds a heterogeneous thread
ChatEvent one transport-neutral frame; kind is a closed Literal["session","status","message"], so the schema is introspectable
run_message(runtime, text, *, user_message, reply) the turn building block: create input → stream statuses → terminal reply
default_session_state(ctx, user_message) generic history reader (any artifact with .text)
create_chat_router(assistant, *, event_names=…, forward_kinds=…, payload_shaper=…, done_event=…, request_factory=…) FastAPI APIRouter for the SSE contract (/api/chat/stream, /api/runs/{id}, /api/health) — needs the web extra. The wire vocabulary is configurable (rename kinds, filter, reshape payloads, emit a terminal frame); request_factory(request) builds the per-turn request= mapping from the FastAPI Request (identity/ACL-aware turns); the event schema is published in the route's OpenAPI responses
reactifact.web.sse(event, data) one SSE frame

Visualization (reactifact.viz + python -m reactifact)

Symbol Role
blueprint(agents) static map of consumes/produces as Mermaid flowchart
context_to_mermaid(context) live provenance graph of a context (artifacts + relations)
trace_to_mermaid(trace) one run as a Mermaid sequenceDiagram
python -m reactifact graph\|context\|trace CLI printing the diagrams to stdout
trace_provenance_to_mermaid(trace) a run's evidence graph (written artifacts + patch.link edges)

Replay (reactifact.replay, §55)

Symbol Role
ReplayLLM(recording, mode="record"\|"replay", inner=…) records every LLM call to JSONL, or replays them exactly; ReplayMiss on divergence
ReplayMiss a replaying call did not match the recording
verify_run(build, *, recording=None, repeat=2) runs build(resources) -> Context repeat times under a recorded model and strict ids (counter_ids), returns a ReproReport; ok is False when the context_hash differs (real nondeterminism)
counter_ids() a deterministic id_factory (Model:0000, Model:0001, …) for RuntimeResources(id_factory=…) — makes artifacts created without an explicit id reproducible
replay_context(store, session_id, version=None) reconstructs a saved session's state at a commit
replay_summary(context) compact state summary for the replay CLI — includes dispatched_actions / pending_actions / failed_actions (replay never re-sends them)

Branching (reactifact.context + reactifact.branching, §39-§40)

Symbol Role
Context.branch(name="") forks an isolated copy; records the base snapshot for three-way merge
Context.merge(other, message=…) atomic three-way merge; MergeConflict on diverged artifacts
MergeConflict raised when both sides changed an artifact differently since the fork
BranchStore(KVBackend) persists branches as branch:<session>:<name> over a KV backend
python -m reactifact branch … CLI: list / save / merge

Evaluation (reactifact.eval, §56)

Symbol Role
run_suite(cases, metrics) / run_case(case, metrics) execute cases and score the final contexts
EvalCase / EvalResult / EvalReport / Metric case/score/report structures (overall(), aggregate(), passed(), render(), to_dict())
core_metrics the four non-generative metrics (answer/provenance/evidence/claim)
answer_coverage() · calculation_correctness(values=…) · source_coverage() · confidence_calibration() ground-truth factories (skip when expected is missing)
Dataset / Example versioned examples (from_file/from_list, .json/.jsonl, content-hash version)
evaluate(dataset, target, evaluators, *, extract=…, summary=…, max_concurrency=…, on_result=…) / aevaluate run a target over every example and score it; target returns a Context, ScenarioResult, outputs mapping or RunResult; on_result(result) streams each scored EvalResult (completion order when concurrent)
ResultCallback Callable[[EvalResult], None] type alias for the on_result progress hook
from_metric(name, metric_fn) wrap a scoring metric into an Evaluator
trajectory_match(mode, *, steps=…, reference_key=…) match a run's path (strict/unordered/subset/superset) over agents/events/reads/writes
llm_judge(llm, *, instructions, …) · judge_correctness · judge_relevance · judge_faithfulness LLM-as-judge evaluators (continuous/choices/include_reference)
summary_pass_rate(threshold) / summary_mean(key) · assert_eval(report, …) suite-level aggregators and the CI gate
OnlineEvaluator(store, config, metrics=, on_report=) · online_evaluate(store, config, metrics=) · OnlineEvalConfig score live runs sampled from a TraceStore; run_once() / run_forever(interval)
trace_source() · context_source(run_fn) · output_present(key) · no_errors() what to score from a run: the trace itself, a rehydrated Context, its outputs, its spans
create_online_eval_router(evaluator) FastAPI router: POST /api/evals/run, GET /api/evals/report, GET /api/evals/summary
python -m reactifact eval <traces.db> CLI: online-evaluate runs (tags the results, --evaluators trace\|judge\|all)

Structured output

Symbol Role
structured_llm(context, schema, *, system, user, attempts=…, validate=…, repair=…, prompt_hash=…, on_error=…) one structured call; None on honest failure; validate(model)->bool adds a domain-rule check retried like a parse failure, repair(invalid_or_None, last_reply)->str supplies the retry instruction; on_error(reason, exc) ("no_provider"|"provider_error"|"parse_error"|"validation_error") to distinguish why, without changing the None contract
json_schema_llm(context, json_schema, *, user, validate=…, repair=…, …) structured output against your own JSON Schema (dict or JSON string), returns a plain dict; same validate/repair retry hooks as structured_llm
StructuredLLM(schema, *, system=…, attempts=…, on_error=…) reusable instance; .call(context, user)
llm_reply(context, *, system, user, attempts=…, on_error=…) plain-text completion → str or None (single-text schema under the hood)
parse_structured lenient JSON→model parser used internally

Prompts (reactifact.prompts, §68)

Symbol Role
PromptTemplate(template, *, defaults=…) strict {var} rendering: declared variables, KeyError on missing vars, model-attribute fields ({question.text}); identifier-shaped placeholders only — a literal JSON {"name": …} / {} in the prompt is left verbatim, no escaping; .hash is a stable sha256 of the template
MessagesPrompt([(role, template), …]) renders a chat sequence to list[Message]; .hash covers all rows

Sources (reactifact.sources)

Symbol Role
Source ABC: asearch(query, limit) → list[SourceRef]
SourceRef the shared atomic search result (ranked, scoped, stable id)
FileSystemSource keyword/embedding search over local files
CSVSource deterministic catalog/table search
EmbeddingSource vector search over a prepared corpus
WebSource discovery + lazy remote document resolution

Providers (reactifact.providers)

Every provider lazily creates its httpx.AsyncClient and rebinds it to the current event loop, so one provider instance stays usable across repeated asyncio.run(...) and per-test loops (no RuntimeError: Event loop is closed); aclose() closes the client for the running loop. The same applies to WebSource and the OTLP/Langfuse sinks.

Symbol Role
LLMProvider, EmbeddingProvider the two contracts the core talks to
ImageProvider, SpeechProvider, TranscriberProvider, VideoProvider media contracts
OpenAICompatProvider, OpenAICompatEmbedder + vendor factories (openai_llm, anthropic_llm, deepseek_llm, groq_llm, mistral_llm, openrouter_llm, gemini_llm, ollama_llm, azure_llm, …) 20+ chat/embedder backends, all retry_attempts=3 by default (429/5xx/transport errors, exponential backoff — never on 4xx)
openrouter_embedder, openrouter_speech, groq_transcriber, together_embedder, fireworks_embedder, qwen_embedder, nvidia_embedder embeddings/TTS/STT for vendors whose non-chat endpoints are confirmed OpenAI-compatible (see providers)
Message, Role one chat message; role is a closed Literal + Message.system/user/assistant/tool factories
LLMRequest one completion: messages + temperature/max_tokens — None = provider default (call overrides provider, provider None = field omitted)
LLMResponse, LLMResponseChunk one completion result / one streamed chunk returned by a provider
*_from_env(**overrides) .env-driven wiring that returns None when unconfigured
CachingLLM, KVCache (reactifact.cache) exact cache for complete(): a repeated request is answered without the provider (cache= to persist), a hit is not charged to the budget; not semantic, stream() uncached
RouterLLM, retryable_only (reactifact.routing) provider failover: try providers in order, fall back on failure (should_fallback=/on_fallback=); only complete()
from_env(**overrides) one-call selection: OPENROUTER_API_KEY first, else OPENAI_BASE_URL, else None — the two-branch default every example's local build_llm() hand-rolls
FakeLLM, FakeEmbedder deterministic stand-ins for tests/demos

Recipes (reactifact.recipes)

Symbol Role
find(inputs, Model) / find_all(inputs, Model) typed lookup in a step's inputs without next(... isinstance ...)
fan_out_sources(context, query, owner_id, …) idempotent fan-out search → refs + patch
materialize_doc(context, ref_artifact, doc_factory, relation=…) lazy ref → document with provenance
StatusMachine / StatusState deterministic artifact lifecycle: next_status(state) over key-scoped inputs (all/any/latest), terminal, on_transition
WindowSummarizer(message_type, artifact_type, summarize=…, build=…) periodic conversation-window summarization, idempotent by message count; message_type may be a sequence of types
WindowPruner(message_type, keep=…) deletes messages older than the window; standalone-useful; message_type may be a sequence of types
Conversation / ConversationMessage / create_turn / transcript multi-turn conversation generic over a message model (yours, or the default ConversationMessage): append turns, render the thread to provider Messages (turn_count / conversation_state helpers)
Transcript / MessageSpec read-only view for apps with separate types per side (MessageSpec(Question, "user"), MessageSpec(FinalResponse, "assistant", render=…)): merges typed turns by role into one Message list (messages / state)
llm_summarizer(system=…) builds a WindowSummarizer(summarize=…) callback from a system prompt via llm_reply
run_tool_loop(context, *, system, user, tools, max_rounds=…, parallel=True, mandatory=…, max_observation_chars=…, redact_tool_outputs=False, retry_tool_errors=False, tool_retry_attempts=2, tool_retry_delay=0.5, parse_calls=…, fallback=…, on_event=…) opt-in native tool-calling loop over reactifact.tools.native: bounded rounds, parallel tool execution, mandatory-tool nudging, forced final answer; honours the turn's Budget (max_tool_calls, max_seconds, token/cost) between rounds; caps tool observations (max_observation_chars), optionally redacts them via resources.redactor, retries ToolError(retryable=True) when retry_tool_errors=True, accepts a parse_calls codec (e.g. textual DSML), a fallback for markup answers, and an on_event streaming hook; returns ToolLoopResult (text, transcript, ToolObservations, stop_reason/error)
looks_like_tool_markup(text) / parse_textual_tool_calls(text) / parse_tool_calls_with_textual(response) opt-in helpers for providers that emit DSML/Hermes/XML tool-call markup instead of native tool_calls; pass the last one as run_tool_loop(parse_calls=…)

Text & rollback helpers (reactifact.recipes)

Symbol Role
keyword_score(text, query, *, stopwords=EN_STOPWORDS, use_stems=False) deterministic query-coverage scoring (English / Russian)
stem_words(text) / stem(word) Russian-English token stemming without embedders
EN_STOPWORDS the default English stop-word set
changed_fields(old, new, *, ignore=()) which fields actually changed (new None = not a change)
earliest_stage(changed, *, field_stages, order) the first stage a change affects (change → rebuild)
downstream_fields(target, *, field_stages, order) fields to reset when rebuilding from a stage

Sessions, checkpoints, tracing

Symbol Role
Session, SessionStore durable per-chat working memory across requests; SessionStore(backend, compact_every=N) bounds the append-only log (roughly every N saves it rewrites the snapshot)
KVBackend, FileKVBackend, SQLiteKVBackend, PostgreSQLKVBackend key/value checkpoints backing sessions (pg extra for Postgres) — async-native: file I/O runs off-thread, SQLite/Postgres each hold one persistent connection (WAL + busy_timeout on SQLite) serialized by an asyncio.Lock; SQLite/Postgres also implement AppendBackend (snapshot + append-only commit log, so a save writes only new commits)
CheckpointBackend, FileBackend, SQLiteBackend full-context checkpoints
Tracer, CompositeTracer, AgentSpan, AgentContract, RunTrace, LLMCall, StepRun (ProduceRun alias), ToolRun, TraceStore tracing primitives (async sinks: export/query/get/sessions; AgentSpan.started_at powers the dashboard timeline; AgentSpan.event_artifact_type/event_artifact_id identify the triggering artifact, AgentSpan.contract carries the declared consumes/produces/tools/priority, AgentSpan.produces lists the step runs that ran, and AgentSpan.tools lists ToolRuns; get(span_limit=…, span_offset=…) pages a large run and reports span_total/has_more_spans/trace_truncated; both stores expose delete_run/delete_session)
TraceLimits write-time trace caps for Tracer(..., limits=TraceLimits(max_spans=5000, max_refs_per_span=50, max_llm_calls_per_span=20, max_data_chars=None)); max_data_chars=None falls back to the legacy RuntimeResources.trace_truncate; dropped data marks the trace trace_truncated
TraceColumn a configurable, artifact-derived table column for create_trace_router(store, columns=[…]): label, field (dotted path, list→first), agent, type (class or name), direction read/write/any, index, scope run/session, default
Tag, TagAssignment, TraceAnnotator, TraceStoreProtocol reviewer annotations: a tag vocabulary (Tag) and per-run tags + notes (TagAssignment, RunTrace.annotations); stores expose list_tags/tag_runs/untag_runs/rename_tag/set_tag_color/delete_tag
LangfuseTracer, OTLPTracer, PostgresStore external trace sinks — OTLPTracer is vendor-neutral (GenAI semconv, any OTLP/HTTP collector), LangfuseTracer targets Langfuse specifically, Postgres supports async read+write and the full annotation API; the dashboard (create_trace_router) accepts any TraceStoreProtocol (SQLite or Postgres)
create_trace_router(store, *, max_detail_spans=500, max_mermaid_spans=200, max_concurrent_reads=2) (reactifact.tracing.web) FastAPI dashboard router: list/detail/sessions pages, tag facets + filtering, bulk tagging, tag-vocabulary management, URL-synced + saved views, sortable table, Tree/Timeline run view, in-trace search, raw JSON, JSON export; detail/diagram are bounded and lazy, DELETE /api/traces/{id} / DELETE /api/sessions/{id} remove runs/sessions; pages are prefix-aware (work under include_router(prefix=…) / ASGI root_path)
Metrics, MetricsTracer, create_metrics_router (reactifact.metrics) dependency-free Prometheus metrics: counters/histograms (runs_total, agent_runs_total, llm_calls_total, llm_tokens_total, llm_cost_total, artifacts_written_total, relations_total, latencies…) filled from the tracer hook; render() emits the text exposition, create_metrics_router serves GET /metrics. RuntimeResources(metrics=…) exposes the same sink to produces (defaults to a no-op)

Trust & safety (reactifact.guardrails / reactifact.authz / reactifact.quota, §57)

Symbol Role
GuardrailPolicy · PIIGuardrail · InjectionGuardrail · DenyListGuardrail · SizeGuardrail · PatternGuardrail · GuardrailViolation artifact-boundary checks on agent-produced data; on_violation="block"|"flag", PII redaction via any Redactor
Principal · PermissionPolicy · AuthorizationError capability-based authz on run / create / update / delete / execute
Quota · QuotaTracker · QuotaLLM · QuotaExceeded cross-turn per-principal token/cost/call limits (RunOutcome.QUOTA_EXCEEDED)
RuntimeResources(principal=, authorizer=, guardrails=, quota=) · resources.authorize/require_authorized/quota_key opt-in wiring; off by default

Testing (reactifact.testing, §56)

Symbol Role
ScenarioLab, Scenario scenario harness: seed artifacts, run agents, assert (artifacts/tools/path/relations/llm/errors/events), mode= live/record/replay. Fault and stub injection: fail/stub_tool (tools) and fail_resource/stub_resource (any resource by string name or typed Type/ResourceKey), each with times/when/delay; stub_resource supports returns/side_effect. run_sync/turn_sync mirror the async API for plain tests
ScenarioResult, Scenario assertion groups result.artifacts(Type) (.linked(relation, Type) for provenance), result.relations (has/none/count/outgoing/incoming), result.tools, result.path, result.llm, result.errors, result.events. result.explain() dumps the whole run
ScenarioReport, ScenarioResult.report, Scenario.report token/latency/tool totals for a turn (or aggregated over a multi-turn scenario): llm_calls, prompt_tokens/completion_tokens/total_tokens, tool_calls, spans, agents, errors, outcomes, with to_dict() and render(). result.llm also exposes prompt_tokens/completion_tokens/tokens
current_result() the most recent ScenarioResult this task produced — so tooling (the CLI) can show the run state when a later assertion fails
capture(context, *, trace=…), assert_golden(context, golden, *, trace=…), assert_golden_file(context, path, *, trace=…, update=…) freeze/compare a GoldenRun; the file variant seeds the snapshot when it is missing (or update/$REACTIFACT_GOLDEN_UPDATE), and ScenarioResult.assert_golden(path)/Scenario.assert_golden(path) wrap it
replay_resources(recording, *, base=…) a RuntimeResources whose llm replays a recording — an offline regression on a real run
reactifact.testing.pytest_plugin opt-in pytest plugin (pytest_plugins = ["reactifact.testing.pytest_plugin"]): runs async def scenarios without pytest-asyncio, maps ScenarioSkip to a skip, adds a scenario_lab factory fixture

MCP (reactifact.mcp, mcp extra)

Symbol Role
mcp_stdio_tools(command, args), mcp_http_tools(url) connect to an MCP server, yield its tools as list[Tool]
mcp_tools(session), MCPTool wrap tools off an existing mcp.ClientSession
oauth_client_credentials(server_url, client_id=, client_secret=, issuer=), InMemoryTokenStorage auth= value for mcp_http_tools — OAuth's client_credentials grant (machine-to-machine, no browser/human consent)
create_mcp_server(tools, context=...) expose Tools (and, with context=, a Context's artifacts) as an mcp.server.mcpserver.MCPServer