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_
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.