Skip to content

Noeta coding agent (python -m noeta.agent)

A workspace-scoped coding agent. It reads, edits, runs shell commands, and holds multi-turn sessions over one directory, recording every step in a durable EventLog so the run's state can be re-derived offline by folding that log. Point it at a directory, start the server, and drive it through the bundled web UI or over HTTP.

Starting the server

The only entry point is python -m noeta.agent — zero positional args, all configuration through environment variables or a JSON config file. It boots an HTTP/SSE chat server plus a bundled web SPA at <url>/chat and blocks until SIGINT/SIGTERM. There is no noeta console script and no operator CLI.

bash
NOETA_AGENT_WORKSPACE=./my-project \
NOETA_AGENT_PROVIDER=openai \
NOETA_AGENT_MODEL=gpt-5.5 \
NOETA_AGENT_BASE_URL=https://api.openai.com/v1 \
NOETA_AGENT_API_KEY=sk-… \
NOETA_AGENT_STORAGE=./session.sqlite \
python -m noeta.agent
# → noeta.agent serving at http://127.0.0.1:<port>/ ; chat at <url>/chat

Launcher and env parsing live in apps/noeta-agent/noeta/agent/__main__.py and RunnerConfig.from_env in apps/noeta-agent/noeta/agent/host/runner_cli.py — the authoritative list of NOETA_AGENT_* knobs.

For library use (no server), the SDK exports Options, query, Client, compile_options from noeta.sdk (packages/noeta-sdk/noeta/sdk/__init__.py); the official four-agent recipe is noeta.presets.main_options() / official_specs() (packages/noeta-runtime/noeta/presets/__init__.py).

Environment configuration

VariableDefaultWhat it controls
NOETA_AGENT_WORKSPACE.Directory the agent operates on
NOETA_AGENT_PROVIDERstubLLM adapter: stub, openai, anthropic, openai-responses
NOETA_AGENT_MODELprovider defaultModel name passed to the provider
NOETA_AGENT_API_KEYProvider API key
NOETA_AGENT_BASE_URLprovider defaultOverride base URL (e.g. for OpenAI-compatible endpoints)
NOETA_AGENT_STORAGE:memory:Path to durable SQLite file; :memory: is dev/test only
NOETA_AGENT_WRITE_MODEdry_rundry_run (propose diffs only) or apply (perform real writes)
NOETA_AGENT_SHELL_MODEallowlistallowlist (argv-structural allowlist) or off
NOETA_AGENT_HOST127.0.0.1Bind address
NOETA_AGENT_PORT0 (OS-assigned)Listen port
NOETA_AGENT_CONFIGPath to JSON config file (alternative to individual env vars)

NOETA_AGENT_PROVIDER=stub (the default) is a fully offline, deterministic LLM double — no API key or network needed. Use it to verify install, storage, and wiring on a fresh checkout.

Built-in tools

Tool names are provider-safe snake_case and are the exact strings the model calls. Source of truth: noeta.tools.fs.build_fs_tools() plus the app/ and web/ packs in packages/noeta-sdk/noeta/tools/.

ToolRiskWhat it does
readlowRead a workspace file (UTF-8), optionally sliced by offset / limit
globlowMatch a workspace-relative glob, return matching paths
greplowRegex (re) content search across the workspace
edithighReplace an exact, unique old substring in an existing file
writehighWrite a file (create, or overwrite one previously read)
apply_patchhighApply a batch of edits atomically — all succeed or none
shell_runhighRun a shell command in the workspace (mode-gated)
shell_polllowCheck status / output of a background shell job
shell_killhighStop a background shell job you started
run_skill_scripthighRun an active skill's bundled script via an allowlisted interpreter
open_applowRender a workspace HTML app in the web "App" panel
webfetchlowFetch a public web page, rendered to Markdown
web_searchlowRun a web search, return ranked hits (key-gated: NOETA_WEB_SEARCH_API_KEY)

There is no separate read_file / write_file / replace_text / list_dir / git_status / git_diff tool — those old names were renamed (read / write / edit) or removed; git status / git diff are now allowlist rules inside shell_run.

Remote MCP tools appear dynamically as mcp__<alias>__<tool> (noeta/tools/mcp/tool.py).

Agent presets

noeta.presets ships the official quartet, aligned with Claude Code's roster (packages/noeta-runtime/noeta/presets/__init__.py). The agent is chosen per task in the POST /tasks body ({"goal": …, "agent": …}), not at process launch; custom agents go through the flat Options.agents dict.

AgentRole
mainDefault coding agent: full built-in tool surface, spawns the three subagents, all capabilities
general-purposeSelf-contained coding worker: full read/write/edit/shell set, no delegation
exploreRead-only scout: glob/grep/read + read-only shell, fans out to report facts, never edits
planRead-only architect: reads the code and returns an ordered implementation plan, never writes

The runner filters the tool pack by the agent's allowed_tools before the Engine sees it, and the PermissionGuard uses the same allow-list, so a forbidden tool is provably unreachable.

Skills

A skill pack is <workspace>/.noeta/skills/<name>/SKILL.md (plus a global ~/.noeta/skills tier) — YAML frontmatter (name, description, optional version / priority) + a Markdown body, with any sibling files bundled as on-demand resources.

Activation is two-stage and model-driven:

  1. At startup the index renders a menu (name + one-line description) into the skill control tool's schema.
  2. When the model calls skill: <name>, the body plus an absolute base-directory line are folded into the next turn's semi-stable context, and the model reads bundled resources on demand (no eager inlining).
text
# model picks from the skill menu, then calls the control tool:
skill: pdf-extract
# → next turn carries SKILL.md body + "Base directory: <abs path>"
# → model reads resources via `read`.

Indexer code: packages/noeta-sdk/noeta/context/skills/.

Write & shell safety

Writes are dry-run by default. NOETA_AGENT_WRITE_MODE (dry_run vs apply) decides whether edit / write / apply_patch change bytes or only emit a proposed unified-diff artifact. This is host config, not a request field — a client cannot escalate to apply. apply_patch is the all-or-nothing path (validate every edit, then write; in-process rollback on apply error); sequenced edit / write calls are non-atomic.

shell_run is gated by NOETA_AGENT_SHELL_MODE: allowlist (default — argv-only structural allowlist for git status / git diff / pytest / uv run pytest / npm test / pnpm test; shell metacharacters rejected before tokenisation) or off.

Every path goes through WorkspaceRoot (realpath + containment, symlink-safe; checked before any IO), so absolute / .. / out-of-tree-symlink escapes fail before reading or writing. Approval and write/shell gating are expressed as neutral control mechanisms — see Guard vs Observer.

MCP & hooks

MCP — remote or stdio connectors are registered in ~/.noeta/mcp_servers.json (alias → transport/url/credentials; credentials never travel in request bodies) and enabled per session via the enabled_mcp field. Their tools show up as mcp__<alias>__<tool>.

Hooks — the only extension roles are Guard (veto or mutate at before_tool_call / before_spawn_subtask / before_finish) and Observer (read-only subscriber to committed events). There is no Mutator role — see Guard vs Observer.

Sub-agent fan-out

main can spawn the three subagents (general-purpose, explore, plan) in parallel. Each spawned subtask is an independent event-sourced task with its own EventLog; the result is recorded into the parent's log via a SubtaskCompleted wake, so the whole tree folds back into state. See Wake & resume.

See also

Released under the MIT License.