Skip to content

Python API ​

The names importable from noeta (noeta/__init__.py), with their signatures, grouped by what they are for. They load on first use, so import noeta stays cheap. Everything else is imported from its module (noeta.core.*, noeta.toolkit.*, noeta.plugins.*, noeta.backends.*, noeta.host.*, noeta.agents); noeta.cli is the noeta command, not part of the SDK. A walk-through is on Embed it in Python; the plugin side is in Extension interface.

Making the parts ​

noeta.store(url) -> Store ​

The store for url, chosen by its scheme through the noeta.stores entry points: sqlite:///path (~ expanded, parent directories made), postgres://… or postgresql://… (the postgres extra). An unknown scheme raises ValueError.

noeta.provider(family, **kw) -> Provider ​

The provider adapter for family (the noeta.providers entry points: anthropic, openai_compat, openai_responses, or a third party's). Keywords go to its factory; the built-ins take api_key=, base_url= and extra_headers=. openai_compat needs base_url (the API root that /chat/completions is appended to, e.g. …/v1); openai_responses needs both base_url (the whole …/v1/responses URL) and api_key. Without api_key, anthropic reads ANTHROPIC_API_KEY and openai_compat reads OPENAI_API_KEY; anthropic also takes prompt_cache_ttl="1h".

noeta.default_agents(model, *, plugins=None, add=None, extensions=(), extra_files=(), made=None, **main_kw) -> list[Agent] ​

main and the sub-agents Explore, Plan and general-purpose, built from the agent files in noeta/agents/files/, plus the agents of extra_files (a file named like a built-in replaces it).

ArgumentMeaning
modelthe model every agent runs unless its file names another; a file with model: inherit (or no model) runs on it, and as a sub-agent on its caller's model
pluginsper plugin name: its arguments (a mapping; enabled: False turns it off), a ready Extension, or False to leave it out of every agent. memory, mcp and repetition_guard stay off until this names them
addper agent name ("*" for all): more plugins after the file's own, as (name, arguments) pairs
extensionsExtension instances added to every agent
extra_filesmore AgentFiles (from read_agent_dir / parse_agent_file)
madea dict that keeps the plugins made, so a second build reuses the unchanged ones
**main_kwfields of main to replace, e.g. one_shot=True, system_prompt=…, name=…

noeta.parse_agent_file(text, *, source="", default_name="") -> AgentFile ​

Reads one agent file: YAML frontmatter in Claude Code's fields (plus plugins, one_shot and identity) and the system prompt as the body. Raises AgentFileError on a malformed file.

noeta.read_agent_dir(path) -> list[AgentFile] ​

Every *.md agent file in path (not recursive). A file that cannot be read is logged and skipped.

noeta.Agent ​

A dataclass; what a Runtime runs.

FieldDefaultMeaning
name—the agent's name, as tasks record it
model—the model id
system_prompt""the system prompt (the prompt name system/<agent>)
identity""a one-line identity sent as its own cached system block ahead of the prompt
description""its line in another agent's sub-agent roster
extensions()its plugins
tools()tools of its own, besides the plugins'
effortNonelow … max
max_tokensNonethe output cap; None takes the catalog's
one_shotFalseend the task when its first turn ends instead of waiting for the next message
output_schemaNonethe JSON Schema answers follow when the task sets none
step_budget1000model calls allowed in one turn; the next asks for the final answer with tools off
extra{}the agent file's other fields, as written
allowed_toolsNoneonly these tool names are offered (None: all)
denied_tools()tool names never offered

all_extensions() is the list a task runs: extensions, with tools wrapped as a ToolSet ahead of them; it raises ValueError when two plugins share a name or one has none.

Running tasks ​

noeta.Runtime ​

A worker: claims tasks from the store and runs them, many at once. A dataclass:

FieldDefaultMeaning
store—the store
agents—dict[name, Agent]
provider_for—model id -> Provider
env_forthe local machine at the task's cwdTaskState -> ExecEnv: where a task's tools run
task_providerNone(state, model) -> Provider, to follow a route the task's config records
prepare_childNonecompletes a child task's TaskCreated before it is written
workerhost:pid:randomthis worker's name in claims
queues("default",)the queues it claims from
lease_seconds30.0how long a claim lasts without renewal
poll_interval0.5seconds between looks for claimable work, when no change wakes it
concurrency8tasks run at once
spill_rootNonewhere tool outputs too long to return inline are saved; None cuts them
prompt_dirs()host directories of prompt override files
workspace_trustedNonecwd -> bool: may a task's .noeta/prompts override prompts (None: every workspace)
on_deltaNone(task_id, Delta) -> None for live output
output_ceilingthe catalog'smodel -> max_tokens when the agent sets none
snapshot_every100events between fold snapshots; 0 never
agent_aliases{}a former agent name -> its name now, for tasks recorded under the former
rootsNoneserve only these task trees (a set the front end adds to); None serves every task
sleepasyncio.sleephow it waits between model retries (tests pass a fake)

Methods:

MethodDoes
await run_until_idle()claim and run tasks until none is claimable
await run_once() -> boolclaim one task and run it until it waits or ends; False when nothing was claimable
await serve(stop: asyncio.Event)run tasks as they become claimable until stop is set; running tasks are handed back on the way out
await let_go()give up the claims kept over waits (tasks owning a background job); the tasks stay waiting
await preview(task_id) -> ModelRequest | Nonethe request the task's next model call would start from, recording nothing
serve_root(root_id)add a task tree to roots and look for work at once
agent(name) -> Agent | Nonethe agent by name or former name
provider_of(state, model) -> Providerthe provider a task's call goes to: task_provider when set, else provider_for
await run_claim(claim)run one claim the caller already holds until the task waits or ends

noeta.LocalApp(store, agents, provider, *, own_tasks_only=False, task_config=None, channels=(), **runtime_kw) ​

A store, a Runtime serving in the background and a client, in one process: what the terminal UI, noeta acp, noeta -p and noeta worker run on (noeta/host/local.py).

ArgumentMeaning
agentsa list of Agent
providera Provider, or a model -> Provider function
own_tasks_onlyserve only the task trees this app created or adopted
task_configlaid under every new task's config (prompts merged key by key)
channelsChannels to run while started
**runtime_kwpassed to Runtime (spill_root defaults to noeta-<uid>/tool-results in the system temp directory; poll_interval defaults to 0.2 and the client polls at the same interval; a caller's own value wins)
MemberIs
clienta LocalClient (a Client subclass) whose new tasks this app's worker serves; it lays task_config under each new task and puts an agent file's initialPrompt ahead of a new conversation's first message
runtime, storethe parts
await start(worker=True) / await stop(), or async withrun the worker, the channels and the daily sweep of old saved tool outputs in the background; stopping also closes the store
await run_until(stop)start(), run until the asyncio.Event stop is set (or the worker fails, which is raised), then stop(): a process that only runs tasks, as noeta worker does
deltas(sink) -> unsubscribelive output: sink(task_id, Delta) per fragment, also from other machines on Postgres
await adopt(task_id)serve an existing task's tree (resuming it); raises UnknownAgent for an agent this app lacks
replace_agents(agents)run these agents from the next pickup on
await agent_names()the agents' names, sorted
await commands(agent, ctx), await run_command(agent, ctx, text)an agent's slash commands
await context(task_id)what fills the task's next request, by category
await summaries(limit=50, archived=None), await archive(task_id, archived=True)the root tasks for a resume list
notices, main_agentwhat a front end should show; the agent to start with ("main")
channelsthe channels it runs
reload_plugins, list_models, prepare_modelhooks a host sets (the noeta command does): find the plugins again, list every provider's models, settle a model before a switch; None otherwise

noeta.Client(store, *, poll_interval=0.5) ​

The client API on a store, in this process (noeta/core/client.py). It writes to the store; a Runtime somewhere runs the work.

MethodDoes
await create(agent, input=None, *, task_id=None, config=None, queue="default", dedupe_key=None, history_from=None, history_through=None) -> strcreate a task, optionally with its first message; returns its id. A repeated dedupe_key, or an existing task_id, returns the existing task
await send(task_id, input, *, when="next_turn", dedupe_key=None) -> intsend a message ("now": at the next step of the running turn); returns its inbox seq
await answer(task_id, request_id, decision=None, *, always=False, data=None) -> intanswer an approval ("allow" / "deny") or a question (data); idempotent per request id
await signal(task_id, name, data=None, *, dedupe_key=None, wake=True) -> intpost a named signal a plugin reads (permission_mode, config, …)
await cancel(task_id, reason="", *, turn_only=False)end the task, or stop only its current turn
await get(task_id) -> TaskRowthe task's row; raises UnknownTask
await tasks(*, parent_id=None, status=None, limit=100) -> list[TaskRow]rows of the task table
await events(task_id, after=0) -> list[Recorded]the log after seq after
watch(task_id, after=0) -> AsyncIterator[Recorded]the log from after on, live, until the task ends
await wait(task_id, *, timeout=None) -> TaskRowuntil the task is waiting or ended

input is a string or a noeta.core.types.Message (a user turn of Text and Image blocks); an image is shrunk to fit before it is stored. A history_from that is no task raises UnknownTask.

noeta.RemoteClient(url, *, token=None, transport=None) ​

The same methods as Client, over a noeta serve server's HTTP API (noeta/host/remote.py); watch reconnects by itself. It also has await context(task_id) (what fills the task's next request) and await close(), which closes its HTTP client. transport is an httpx transport (tests).

noeta.connect(url, *, token=None, **kw) -> Client | RemoteClient ​

sqlite:///… or postgres://… gives a Client on that store; http://… / https://… a RemoteClient for that server with token. Extra keywords go to the client's constructor.

Records and state ​

NameIs
noeta.Recordedan event as stored: seq, at (a Unix time), event (one of the ten event types in noeta.core.events; see Events)
noeta.TaskStatea task's state folded from its log: config, messages, calls, waits, children, timers, usage, and each plugin's state in ext (read only for plugins)
noeta.ToolUsea tool call the model made: id, name, input

Writing plugins and tools ​

These are the plugin author's names; each is described fully in Extension interface and Write a plugin.

NameSignatureIs
noeta.Extensionsubclass it; set namethe one plugin interface: tools, context, reminders, commands, gates on tool calls, state folded from the log; every method optional
noeta.TaskViewpassed to the hookswhat a plugin sees of a task: its folded state, a way to record entries, its exec env
noeta.ToolChanges(add=(), withhold={}, entries=())what Extension.tool_changes changes in a task's tool list
noeta.ContextPart(text, order=100, before="")system prompt text a plugin adds
noeta.Reminder(text, kind="reminder", data=None, origin="system", role="user", order=100)text for the model that is not the user speaking
noeta.Command, noeta.CommandContext(name, description, template="", argument_hint="", local=False); (cwd, task_id=None)a slash command front ends offer, and where it is listed or run
noeta.Allow, noeta.Ask, noeta.Deny, noeta.RewriteAllow(approve=False, outright=False), Ask(prompt="", detail={}, firm=False), Deny(reason=""), Rewrite(input, name=None)what before_tool returns: deny wins over rewrite, ask and allow
noeta.Toola protocol: name, description, input_schema, read_only, clearable, async run(input, ctx)a tool the model can call; optional flags, resume, recover, rule_inputs
noeta.ToolContextpassed to runthe call's task, exec env, plugin states, record, spawn, post, keep, progress, …
noeta.ToolOutput(content=[], is_error=False, meta={}, display=None, park=None, end_turn=None, block_list=False)a tool's result
noeta.text_output(text, *, is_error=False, **meta) -> ToolOutputa text result
noeta.function_tool@function_tool or @function_tool(name=None, description=None, read_only=False, clearable=False, deferred=False)a typed function as a tool: name, docstring, schema from the annotations
noeta.askask(*questions) -> Parkpark a tool call on questions to the person ({"question", "header"?, "options"?, "multiSelect"?})
noeta.read_answerread_answer(outcome, asked) -> dict | Nonethe checked answers a parked question got, or None when it was withdrawn

Useful modules outside noeta ​

ModuleFor
noeta.core.eventsthe event types (TaskCreated, ModelReplied, ToolStarted, Waiting, TaskEnded, …) and inbox items
noeta.core.typesMessage, Text, Image, ToolUse, Delta, Usage
noeta.core.modelsthe model catalog: find_spec, register_models, ModelSpec, ALIASES
noeta.host.pendingpending_tree, choices, answer_choice: what a task tree waits on a person for
noeta.host.questionsanswer_question: answer a question after checking it
noeta.host.httpcreate_app: the HTTP API as a Starlette app in your own server
noeta.testingScriptedProvider, reply: a scripted model for tests