Skip to content

SDK reference (noeta.sdk)

noeta.sdk is the single public import surface of the SDK. Everything below is re-exported from it — users never import noeta.client or runtime internals directly. Source of truth: the __all__ list in packages/noeta-sdk/noeta/sdk/__init__.py:108-174.

python
from noeta.sdk import query, Client, Options, tool

Client verbs

query(options, goal, *, provider=None, workspace_dir=None, model=None, images=()) → QueryResult

One-shot query: drives a single turn to a genuine terminal and returns the full envelope stream with pre-folded projections (packages/noeta-sdk/noeta/client/client.py:984). Creates a temporary Client(multi_turn=False) and shuts it down before returning. Use Client directly for multi-turn work.

QueryResultclient/client.py:881

A list[EventEnvelope] subclass (iteration/indexing behave like a list) plus:

MemberReturnsNotes
.task_idstrthe driven task
.messages()list[ViewItem]pre-folded human view; every ContentRef already dereferenced
.answer()Anythe terminal answer; raises QueryFailedError on a failed or non-terminal task

The projections are materialized against the temporary Client's ContentStore before teardown — do not re-project raw envelopes with a fresh store.

Clientclient/client.py:122

python
Client(options, *, provider=None, workspace_dir=None, model=None,
       multi_turn=True, host_config=None, allowed_models=None)

(client/client.py:147) A provider must come from the provider kwarg or Options.provider, and a workspace from workspace_dir or Options.cwd — otherwise ValueError. Storage defaults to in-memory; pass a HostConfig to inject a durable triple.

MethodSignature (keyword-only after task_id)Source
start(*, goal, agent=None, model_selector=None, images=(), permission_mode=None, enabled_mcp=(), workspace_dir=None, effort=None) → outcomeclient.py:392
send_goal(task_id, *, goal, model_selector=None, images=(), permission_mode=None, enabled_mcp=(), effort=None) → outcomeclient.py:440
approve(task_id, *, call_id, reason=None, resolver="client")client.py:475
deny(task_id, *, call_id, reason=None, resolver="client")client.py:488
answer(task_id, *, question_id, answers, answered_by="client")client.py:501
deliver_event(task_id, *, event_kind, payload=None) — wake a wait_external suspend; matching is exact on event_kind, an optional payload is recorded as an origin="system" message on the resumed turnclient.py:517
cancel(task_id, *, reason="cancelled", cascade=False)client.py:657
close(task_id, *, closed_by="user", reason=None)client.py:669
reopen(task_id, *, reopened_by="user", reason=None)client.py:681
events(task_id)list[EventEnvelope]client.py:705
messages(task_id)list[ViewItem]client.py:709
events_after(task_id, after_seq=None)list[EventEnvelope] — the stream strictly past a cursorclient.py:719
task_streams() → per-task (task_id, last_seq) summariesclient.py:729
delete_task(task_id){"ok", "reason"?, "task_id", "deleted": [...]}; refuses with reason="running" / "not_found"client.py:738
subscribe(callback) → unsubscribe callable; post-commit envelopes, all tasksclient.py:846
shutdown() — idempotent observer teardownclient.py:856

Properties: registry (the compiled AgentRegistry, client.py:661) and main_agent_name (client.py:666). workspace_dir at start is welded into the durable TaskHostBound once; later turns fold-resolve it. permission_mode / enabled_mcp / effort are per-turn, non-durable host knobs.

The recipe: Options

Optionsclient/options.py:197

Frozen dataclass compiled into AgentSpecs. Fields split into identity (enter the recording) and wiring (mount-point only, ignored by compile_options):

FieldType / defaultKind
system_promptstr | SystemPromptPreset — requiredidentity
namestr = "main"identity
skillstuple[str, ...] = ()identity
budgetBudgetSpec | NoneNone ⇒ default with max_subtask_depth=3identity
capabilitiesCapabilities | NoneNone ⇒ derived from childrenidentity
agentsMapping[str, AgentDefinition] = {} — flat, non-recursiveidentity
allowed_toolstuple | NoneNoneall 11 built-ins; entries are name strings or DecoratedToolsidentity
disallowed_toolstuple[str, ...] = () — subtracted from the allow-listidentity
permission_mode"default" | "acceptEdits" | "bypassPermissions"identity
max_turnsint | None — sugar for budget.max_iterations; setting both raises ValueErroridentity
policycallable (llm) → Policy with a .refNone ⇒ built-in ReActidentity
mcp_serverstuple[SdkMcpServer, ...] = () — their tools enter identityidentity
modelstr | None — routing hintexcluded from identity
metadataMapping[str, str] = {} — observational labelsexcluded from identity
providerLLMProvider | Nonewiring
cwdstr | Path | Nonewiring
can_use_tool(tool_name, arguments) → bool — auto-resolve gated calls; recorded with resolver="can_use_tool"wiring
output_schemaMapping | None — JSON Schema for the final answerwiring
thinking"adaptive" | "disabled" | Nonewiring
effort"low" | "medium" | "high" | "xhigh" | "max" | Nonewiring
guardstuple[Guard, ...] = ()wiring
observerstuple[Observer, ...] = ()wiring
content_channelstuple[ContentKindSpec, ...] = () — the only composer seamwiring

Invalid thinking / effort values raise ValueError at construction; invalid permission_mode raises at compile time (options.py:541).

AgentDefinitionclient/options.py:121

Flat child-agent recipe: description (required, non-empty), prompt (required), tools (None ⇒ all built-ins), model, capabilities, metadata. Cannot nest — children are leaves.

SystemPromptPresetclient/options.py:101

preset: str = "main", append: str | None = None — resolves a registered preset prompt, optionally appending a suffix.

compile_options(options) → (AgentSpec, tuple[AgentSpec, ...])client/options.py:514

Pure compile of the recipe into (main_spec, descendant_specs). Referentially transparent: equal Options produce equal AgentSpecs.

register_preset_prompt(name, prompt) → Noneclient/options.py:84

Registers a named preset for SystemPromptPreset (last-writer-wins).

Authoring

@toolpackages/noeta-runtime/noeta/tools/decorator.py:99

python
@tool(name="word_count", version="1", risk_level="low",
      input_schema={...}, description="...")
def word_count(arguments: dict, ctx: ToolContext) -> ToolResult: ...

Wraps fn(arguments, ctx) → ToolResult as a DecoratedTool (decorator.py:43). version is required — omitting it raises TypeError (version feeds the identity fingerprint). risk_level defaults to "low". input_schema is LLM-facing metadata (not validated at runtime); description is the model's single source of tool semantics. Also callable directly: tool(fn, name=..., ...).

create_sdk_mcp_server(name, version="1.0.0", tools=()) → SdkMcpServersdk/authoring.py:60

Bundles @tool functions into an in-process ("sdk" transport) MCP server for Options.mcp_servers. Empty name raises ValueError; a non- DecoratedTool entry raises TypeError. SdkMcpServer (sdk/authoring.py:35) is frozen: name, version, tools.

Message projection & wire

as_messages(envelopes, content_store) → list[ViewItem]client/messages.py:150

Pure projection of an envelope stream into the human-readable view. The content_store must be the one paired with the stream. ViewItem (messages.py:136) is the union of:

TypeFieldsSource
AssistantMessagetextmessages.py:80
UserMessagetextmessages.py:87
ToolUsecall_id, tool_name, argumentsmessages.py:94
ToolResultViewcall_id, tool_name, success, output: str | Nonemessages.py:108
Resultanswer, status — on "failed", answer holds the failure reasonmessages.py:123

envelope_to_dict(env) → dictclient/wire.py:25

Canonical JSON-ready dict form of an EventEnvelope (the wire shape the SSE stream and the web frontend consume).

Content blocks

ImageBlock (noeta/protocols/messages.py:121) — an image input block for start / send_goal / query(images=…). ContentRef (noeta/protocols/values.py:27) — hash + size + media_type reference into the ContentStore.

Host-level wiring

HostConfigclient/host_config.py:38

Frozen dataclass passed as Client(..., host_config=…); never part of agent identity. Fields: the durable storage triple event_log / content_store / dispatcher (all-or-nonestorage_triple() at host_config.py:85 raises ValueError on a partial set; all None ⇒ in-memory), app_gateway (AppPreviewGatewayNone ⇒ no open_app tool), mcp_server_resolver ((alias) → McpAnyServerSpec | None), mcp_http_post (injectable HTTP transport, HttpPostFn), delta_sink ((StepContext, call_id, StreamDelta) → None — receives ephemeral token deltas while a streaming-capable provider call is in flight; None ⇒ no streaming, providers are called exactly as before; deltas are never persisted), workflow_allowed: bool = False, and write_mode: str = "dry_run" ("apply" performs real writes).

Related re-exports from noeta.tools.app / noeta.tools.mcp: AppPreviewGateway, AppMount, McpServerSpec (stdio), McpHttpServerSpec, McpAnyServerSpec (their union), McpError, McpConfigError, HttpPostFn.

Errors (typed / coded)

Boundary code matches errors structurally — isinstance(exc, CodedError) + exc.code — never by message text. CodedError is the base (noeta/protocols/errors.py:18).

ErrorcodeSource
QueryFailedError — carries task_id, status, reason, retryablequery_failedclient/client.py:848
ModelSelectorErrormodel_selector_rejectednoeta/execution/driver.py:123
ProviderSelectorErrorprovider_selector_rejecteddriver.py:144
NotResumableErrornot_resumabledriver.py:171
TaskAlreadyTerminalErrortask_already_terminaldriver.py:204
UnsupportedSubtaskSuspendunsupported_subtask_suspendnoeta/execution/subtask_drain.py:110

Capability projections

Three functions (packages/noeta-sdk/noeta/client/capabilities.py):

  • permission_modes() → tuple[str, ...] — the legal permission_mode values (capabilities.py:21).
  • effort_modes() → tuple[str, ...] — the legal effort values (capabilities.py:26).
  • model_capabilities(models) → dict[str, dict[str, bool]] — per-model capability flags, e.g. the vision gate (capabilities.py:31).

Extension interfaces

Implement one of these and mount it through the matching Options field:

InterfaceMount viaSource
Tool (protocol: metadata + invoke(arguments, ctx) → ToolResult)allowed_toolsnoeta/protocols/tool.py:132
ToolContext / ToolResult (success, output, artifacts, output_ref)tool call inputs/outputstool.py:108 / tool.py:19
LLMProviderprovidernoeta/protocols/messages.py:286
StreamingProvider / StreamDelta (optional capability: complete_streaming(request, on_delta, request_headers=None) still returns the complete LLMResponse; deltas are ephemeral side effects)implement alongside LLMProvider on provider; consumed via HostConfig.delta_sinkmessages.py
Policypolicynoeta/protocols/policy.py:21
Guard / GuardContext / ProposedAction / VerdictResultguardsnoeta/protocols/hooks.py:159 / 111 / (payload types) / 45
Observer (= Subscriber, a Callable[[EventEnvelope], None])observersnoeta/protocols/event_log.py:47
ContentKindSpeccontent_channelsnoeta/context/content_channel.py:63
Decision (union of Policy decision types)returned by a custom Policynoeta/protocols/decisions.py:427
StepContext / Viewpassed to a custom Policynoeta/protocols/step_context.py:17 / noeta/protocols/view.py:70

Official presets

presets — the module re-export (noeta.presets, packages/noeta-runtime/noeta/presets/__init__.py). Key entries: main_options() (presets/__init__.py:159) returns the official main-agent Options; official_specs() (presets/__init__.py:185) returns the compiled four-agent set (main / general-purpose / explore / plan).

See also

Released under the MIT License.