These are the primary public interfaces of the 0.4 SDK. Each topic page includes usage and guarantee boundaries; the linked source provides full signatures. Upgrading from 0.3? Migrating to 0.4 lists every renamed, regrouped, and removed name with its replacement.

Agent

from harnessx import Agent, AgentConfig, Limits

agent = Agent(
    config=AgentConfig(limits=Limits(max_iterations=20)),
    tools=[],
)
Method / property Contract
await agent.run(message) Final RunResult
agent.run_stream(message) RunStream: async context manager and RunEvent iterator
agent.stream_text(message, on_reset=...) Async iterator of answer text; raises RunFailed if the run does not complete
await stream.result() Final result for that stream
stream.text(on_reset=...) Text-only view over any RunStream
agent.closed / agent.busy True once aclose() has started / while a run is in progress
agent.tools ToolRegistry; agent.tools.policy and agent.tools.sandbox expose the effective tool policy and the bound sandbox
agent.mcp_tools Names of the MCP tools bridged from Agent(mcp=...)
agent.memory ConversationMemory
agent.permissions PermissionManager
agent.guardrails.usage_summary Accumulated usage
await agent.save_session(storage_dir=...) Save a snapshot; returns session ID
await Agent.load_session(session_id, ...) Construct an agent from a validated snapshot
await agent.aclose() Release owned resources

One Agent permits one active run; starting a run on a busy or closed agent raises RuntimeStateError. Use a distinct agent per concurrent conversation. Constructor injection includes providers, tool registries, memory, permissions, hooks, middleware, sandbox, MCP, skills, knowledge bundles, extensions, and subagent definitions. Agent(client=...) was removed in 0.4; wrap the SDK client in a provider: Agent(provider=AnthropicProvider(client=client)).

SubAgent

from harnessx import Agent, AgentConfig, Limits, SubAgent

agent = Agent(subagents=[SubAgent(
    name="reviewer", description="Review supplied code",
    config=AgentConfig(system_prompt="Review code carefully", limits=Limits(max_iterations=5)),
)])

Required: name, description, config. Optional: tools, skills (paths), permission, and timeout_seconds (DEFAULT_TIMEOUT_SECONDS, 300 seconds, by default). Agent.subagents is a tuple of bound specialist definitions. Each task gets a fresh conversation; delegation returns to the orchestrator. The generated delegate_ tool is serial and uses manual replay policy. Child progress is not independently durable. See multi-agent orchestration.

AgentConfig

Five top-level fields plus four sub-policies. Only model, provider, and max_tokens may be passed positionally; everything else is keyword-only.

Field Default Meaning
model claude-sonnet-4-6 Provider model ID
provider anthropic A built-in name or one added with register_provider()
max_tokens None Reply token budget; unset, the provider chooses for the model (20,000 on Claude 4 models, 8,192 elsewhere)
system_prompt You are a helpful assistant. Base instructions
temperature None Omitted unless configured
limits Limits() Iteration, context, result-size, and cost budgets
retry RetryPolicy() Transient-failure handling for model calls
prompt_cache PromptCachePolicy() Prompt-prefix caching policy; None disables it
tools ToolPolicy() Registry-wide tool options applied to the registry the agent adopts
Sub-policy Fields and defaults
Limits max_iterations=50 (0 means unlimited), max_context_tokens=150_000, max_result_chars=12_000, max_cost_dollars=None, input_cost_per_m=None, output_cost_per_m=None
RetryPolicy attempts=2 (1 disables retry), backoff_seconds=0.5, call_timeout_seconds=None (sized to the reply budget, never below 300 s)
ToolPolicy default_timeout_seconds=None, dedupe_calls=False
PromptCachePolicy ttl_seconds=None, cache_history=True, key_salt=""

The sub-policies are frozen dataclasses; derive one with dataclasses.replace. Values are validated at construction and raise ConfigurationError. The flat 0.3 names (max_iterations, max_context_tokens, max_result_chars, max_cost_dollars, input_cost_per_m, output_cost_per_m, llm_max_attempts, llm_retry_backoff_seconds, model_timeout_seconds) still work as keyword arguments and attributes but warn on every use; they are removed in 0.5. AgentConfig.from_dict() reads the 0.3 flat and the 0.4 nested shape without warnings. Model availability is checked by the provider service.

ToolRegistry

Method Use
ToolRegistry(default_timeout_seconds=..., dedupe_calls=..., sandbox=...) Constructor values take precedence over AgentConfig.tools
register(...) Decorator registration
register_tool(handler, ...) Register a Python callable
register_with_schema(name, description, input_schema, handler, *, ...) Supply an explicit input schema; metadata is keyword-only
get_tool(name) / has_tool(name) Inspect registration
unregister(name) Remove a tool
policy The effective ToolPolicy after adopting the agent's config
await execute(call, permissions=...) Validate, authorize, and execute

Policies include permission, replay_policy (a ReplayPolicy member or "manual", "safe", "idempotent"), concurrent, and timeout_seconds (DEFAULT_TIMEOUT_SECONDS unless a ToolPolicy changes the default). Duplicate names require replace=True. An omitted permission inherits the manager default, normally ALLOW. A denied call returns the error result "Permission denied". See Tools for precedence.

AgentRuntime

For SQLite or PostgreSQL, construct AgentRuntime(agent, backend=backend) with a configured Agent. Recreate a fresh compatible agent before resuming in another process. AgentRef and AgentRegistry remain supported for named definitions; the current Temporal path requires AgentRef and worker-side bindings. See Advanced topics → Durable agent runs for the complete API walkthrough and PostgreSQL setup for the runnable example.

Method Use
await start() Create a new durable session
await submit(message, request_id=...) Return RunHandle
await run(message, request_id=...) Run one durable turn and wait for its RunResult
run_stream(message, request_id=...) Run one durable turn as a RunStream of typed events
stream_text(message, request_id=..., on_reset=...) Text-only view; raises RunError if the run does not complete
await resume(session_id) Resume unfinished work or return None
await approve(target, allow=True, resume=False) Record a decision for a PendingTool or execution key; resume=True returns the finished RunResult
await decline(target, resume=False) Refuse a paused tool; the model sees "Permission denied"
await resolve_tool(target, result=..., retry=..., abort=...) Reconcile an uncertain tool by PendingTool or execution key
await pause() / cancel() Control execution
await stop() / aclose() Pause local work, close the agent, and close a backend the runtime created
await status() session_id, state, and run, the latest RunResult or None
await export_incident(run_id, ...) Export a recorded incident

execute(), execute_stream(), and get_status() remain as deprecated aliases until 0.5. RunHandle.backend is deprecated; runtimes own their backend.

RunHandle and RunResult

submit() and a successful resume() return a RunHandle. resume() returns None for a session with no run or a latest run that is already completed/cancelled. A missing session raises an error.

Handle API Use
session_id / run_id Identify the conversation and execution
await result() Wait for a RunResult, including approval waits, pauses, and failures
events(after=cursor) Read stored events and follow new events
stream(after=cursor) Open an async-context-managed event stream

RunResult contains output, status, error, pending operations, IDs, usage, stop_reason, and usage_incomplete. Inspect status before treating output as a successful final answer. Controls such as approve(), decline(), pause(), cancel(), and resume() belong to AgentRuntime.

Predicate / method Meaning
result.ok status is COMPLETED
result.failed status is FAILED; result.error has type and message
result.needs_input status is AWAITING_INPUT; result.pending lists PendingTool objects
result.raise_for_status() Returns the result, or raises RunFailed, RunAwaitingInput, RunTruncated, or RunCancelled, each carrying .result

The advanced guide explains result handling, event cursors, reconnection, and the difference between a handle and an answer. A disconnected subscriber does not itself cancel durable execution; the runtime and worker must remain alive.

PendingTool

Attribute Meaning
execution_key Identifies the paused tool for approve, decline, and resolve_tool
call The ToolCall: call.id, call.name, call.input
status "approval" or "uncertain"
attempt Attempt counter
policy The tool's replay policy string
concurrent Whether the tool was declared concurrent
timeout_seconds The tool's timeout

Indexing (pending["execution_key"]) still works and is deprecated. The persisted shape (to_dict() / from_dict()) is unchanged from 0.3.

Errors

Every exception the SDK raises on purpose derives from harnessx.HarnessError. Classes that predate 0.4 keep their previous bases, so except ValueError and except RuntimeError clauses keep working.

Error Also a Raised for
ConfigurationError ValueError invalid AgentConfig, Limits, RuntimeConfig, SandboxConfig, mismatched sessions
RuntimeStateError RuntimeError closed or busy agent, runtime not started, run not awaiting a resolution
ResolutionError ValueError an approval or recovery decision that does not fit the tool's state
UnknownExecutionKey KeyError approve / resolve_tool with a key the run does not have
RunError, RunFailed, RunAwaitingInput, RunTruncated, RunCancelled raise_for_status() and stream_text(); carry .result
StorageError and subclasses, IncidentError, RecordingError, MaxIterationsError, CostLimitError, ToolNotFoundError, ToolApprovalRequired their 0.3 bases unchanged sites

Typed results and events

RunResult exposes output, status, run_id, error, and pending. RunEvent carries a type and a validated payload; RunEvent(data=...) is required. ToolCall uses id, name, and input; ToolResult uses tool_call_id, content, and is_error.

PermissionLevel, StopReason, RunStatus, RunEventType, and ReplayPolicy are str enums (PermissionLevel.ASK == "ask") and serialise with json.dumps directly. ReplayPolicy lives in harnessx.types and is accepted everywhere replay_policy is, alongside the plain strings. StopReason.TOOL_USE replaces the removed TOOL_CALLS; the removed Role enum gives way to the "user" / "assistant" strings. DEFAULT_TIMEOUT_SECONDS = 300.0 is the shared default for tools, sub-agents, and model calls.

ProviderResponse's text, thinking, and tool_calls are canonical. Its content property is a compatibility view. Persistent values must be JSON-compatible and finite.

Extension and integration interfaces

Interface Guide
LLMProvider / register_provider Model providers
DecisionProvider / JevDecisionProvider Decisions with Jev
MCPManager MCP servers
MCPServerConfig.stdio(...) / .http(...) MCP servers
SkillManager Skills
KnowledgeManager / OKFBundle / OKFConcept / GitSource Knowledge (OKF)
Extension / ExtensionContext Extensions
Middleware / HookManager / HOOK_PAYLOADS Hooks & middleware
Sandbox Code execution
IncidentRecorder / ExportPolicy Flight recorder
SQLiteBackend / PostgresBackend / TemporalBackend Backends
evaluate_agent / evaluate_agent_async Tracing & evaluations

Exports added in 0.4

Limits, RetryPolicy, ToolPolicy, DEFAULT_TIMEOUT_SECONDS, HarnessError, ConfigurationError, RuntimeStateError, ResolutionError, UnknownExecutionKey, RunError, RunFailed, RunAwaitingInput, RunTruncated, RunCancelled, ToolApprovalRequired, ToolExecutionContext, Registration, register_provider, evaluate_agent_async, KnowledgeManager, OKFBundle, OKFConcept, and GitSource are top-level harnessx exports. harnessx.durable groups the runtime, backends, recorder, and their errors under one import.

Removed in 0.4: Agent(client=...), harnessx.streaming and StreamingAgent, Role, StopReason.TOOL_CALLS, RuntimeConfig.checkpoint_interval and max_checkpoints, LLMProvider.format_tools, and make_provider("azure-openai"). See Migrating to 0.4 for each replacement.

Decision SDK

Import these types from harnessx.decisions; they are not top-level harnessx exports.

Contract Public shape
ChoiceQuestion instructions, options: at least two labels with descriptions
ScoreQuestion instructions, levels: two to ten ordered descriptions
BooleanCriteria Optional true and false descriptions
BooleanQuestion instructions, optional criteria
DecisionRequest check_id, version, strict JSON state, typed questions
ChoiceAnswer choice, label probabilities, nullable confidence
ScoreAnswer Fractional score, ordered probabilities, levels, nullable confidence
BooleanAnswer probability; no implicit boolean conversion
DecisionBatch status, typed answers, read-only choices / scores / booleans, identity, model metadata, latency, attempts, usage, error
DecisionUsage Nullable input_tokens, output_tokens, cost_dollars; incomplete flag
DecisionError Stable code, sanitized message, optional status_code, retryable flag

DecisionProvider.evaluate(request, *, timeout_seconds=None, max_attempts=None) is async and returns DecisionBatch. The provider supports aclose() and async context management. DecisionRequest and DecisionBatch have to_dict() / from_dict() codecs using schema_version=1.

JevDecisionProvider defaults to model jev-1.13.0, a two-second total deadline, one transient retry, and 16,384 bytes of state. Optional constructor settings include api_key, base_url, and a borrowed AsyncTypeSafeClient. Per-call limits can shorten the deadline and cap attempts.

Every requested answer must validate for a completed batch. Unavailable batches expose no partial answers. The Jev adapter requires confidence for Choice and Score; the vendor-neutral answer types allow another provider to return None. See the decision guide for validation, retries, usage, and runnable examples.

Resource ownership

Use async context managers whenever available. Agents close resources they construct. Injected providers, memory, MCP managers, and sandboxes remain application-owned: Agent.aclose() does not disconnect an MCPManager, so use async with MCPManager(). Runtimes close their executing agent and a backend they created; injected backends remain caller-owned, and every backend is an async context manager.

JevDecisionProvider closes the TypeSafe clients it creates and leaves injected clients open. Its aclose() cancels outstanding evaluations and is idempotent. Concurrent evaluations are supported; calls after close raise.