Skip to content

The plugin system ​

Everything you can extend in Noeta goes through one mechanism: a named surface that a plugin contributes to, or that you fill directly through an Options field. Noeta's own capabilities — file and web tools, memory, MCP, sandboxes, storage, guards, model adapters — are plugins on the same path as yours.

Your pluginand every built-in:same pathload_plugins()reads manifests,runs no code16 surfaceschecked, collisions failSurfaceRegistryPluginSet.resolve()at Client build:imports the codePer agent, picked byOptions.pluginstools, prompts, policy,reminders…Whole processguard, observerevery agent in the process;no opting outHost picksprovider, sandbox_providermcp_server, skills:joined automatically
only identity surfaces count toward agent identity

What it guarantees ​

  • No privileged path. Built-ins are loaded, validated and merged exactly like a third-party plugin. The kernel never imports noeta.builtins statically; an import linter fails the build if anything does.
  • Nothing runs at load time. Manifests are inert data; code is imported only when the client is built.
  • No silent overrides. Two contributions with the same key fail loudly, naming both sides. There is no last-writer-wins.
  • Typos fail. Activating an unknown plugin name fails compilation instead of quietly turning a capability off.

Identity versus wiring ​

Every surface is one of two kinds, and the difference matters for replay:

  • Identity decides how the agent thinks — system prompt, tools, skills, activated plugins, decision policy. It is recorded and reproduced exactly on fold.
  • Wiring only mounts the agent on a host — the model provider, working directory, approval callback, observers, storage. It is not part of identity, so changing it never breaks a recording. This is why switching model vendors is free.

The sixteen surfaces ​

GroupSurfacesScopePart of agent identity
Identitytool, agent, content_kind, prompt_fragment, policy, control_toolper agentyes
Wiringguard, observerwhole processno
Wiringproviderset by the hostno
Wiringreminder_provider, reminder, tool_result_transform, session_packper agentno
Host resourcesmcp_server, skillswhole process, joined automaticallyno
Host resourcessandbox_providerset by the hostno

guard and observer apply to every agent in the process once their plugin is loaded. That is deliberate: governance belongs to the operator, and an agent author must not be able to skip it by leaving a name out. Details of each surface are in the plugin surfaces reference.

How a plugin is loaded ​

  1. load_plugins(...) reads manifests into a PluginSet without importing any plugin code. Each contribution's ref is just a string.
  2. Every contribution is validated against its surface's SurfaceSpec and checked for collisions. Merge order is deterministic: by (plugin, name), or by priority where the surface has one.
  3. When the client is built, PluginSet.resolve() imports the code each ref names.
  4. Activation picks which agents use which plugins: Options.plugins and AgentDefinition.plugins decide every per-agent surface, identity or wiring; only the identity ones fold into the agent's identity. The default is DEFAULT_PLUGINS = ("fs", "web"). guard and observer apply to the whole process instead; provider and sandbox_provider are picked by the host, and mcp_server and skills contributions join automatically.

The loader knows nothing about specific surfaces; it only consults a SurfaceRegistry. A host can add its own surface by copying standard_registry() and registering a new SurfaceSpec.

A built-in is one directory under packages/noeta-sdk/noeta/builtins/<name>/: an __init__.py holding only the PluginManifest, and an impl/ package the manifest's refs point at. Eighteen ship with noeta-sdk.

What stays locked ​

LockedWhyWhat you can change instead
The Engine main loopit only routes decisionsthe policy surface
Dispatcher, worker and lease protocolthe single-writer guarantee depends on itconcurrency and lease timing in HostConfig
The context composerthe cached prompt prefix must stay reproducibleadd a content_kind or a reminder
Storage backendshost wiring, never agent identityHostConfig, via noeta.sdk.storage

What this means for you ​

  • Anything a built-in does, your plugin can do the same way.
  • Activation changes identity and therefore the cached prompt prefix; choose an agent's plugins once, not per turn.
  • Start with a @tool function in Options.allowed_tools; move to a plugin when you want to ship tools, prompts and guards together as one bundle.

Design records: library SDK architecture · plugin contribution bundles · package layout

Next ​

Released under the Apache License 2.0.