Skip to content

Plugin registry API ​

The object your plugin's register(registry) receives — the whole Python extension surface in one place. Generated from graph/plugins/registry.py.

python
def register(registry):
    """Called at plugin load and again on every config reload, before the graph is built."""
    registry.register_tool(my_tool)

Two properties of this surface are worth knowing before you use any of it:

Every seam is best-effort. A register_* call with bad arguments logs a warning and returns — it never raises into the loader, because one malformed plugin must not stop an agent from booting. If a seam seems to have done nothing, read the log.

Guard for older hosts. A plugin that must run against a host predating a seam should hasattr(registry, "register_x") before calling it. That is also why FakeRegistry mirrors this class method-for-method, with a test that fails CI when it stops matching.

Attributes ​

Read these in register() and close over them for your tools, routes, and surfaces.

registry.plugin_id ​

The plugin's manifest id — the slug every namespaced thing derives from. Pass it to the SDK calls that take an explicit owner (sdk.schedule_recurring, sdk.record_metric): that attribution is how the host sweeps a plugin's jobs when it's disabled, and how one plugin is kept out of another's namespace.

registry.plugin_dir ​

The plugin's own directory on disk. The right base for reading files the plugin ships — templates, a view's HTML, seed data — since the process CWD is not it.

registry.config ​

The plugin's resolved config section (ADR 0019) — manifest defaults ⊕ YAML ⊕ secrets. Read it in register() and close over it for your tools/routes/surface, e.g. registry.config.get("api_key"). This is a register-time SNAPSHOT; for a mounted router/surface that must reflect config edits without a restart, use live_config() instead.

registry.config_section ​

The top-level config key this plugin's section lives under (config_section or the id) — the lookup key for live_config().

registry.host ​

Host services (agent invoke + event bus) a surface/route can use — the server populates these before startup; guard for None (e.g. in tests).

Methods ​

MethodPurpose
emit()Broadcast an event on the bus (ADR 0039) — fire-and-forget
live_config()The plugin's CURRENT resolved config, re-read from the host on each call
navigate()Ask the operator console to open one of THIS plugin's views — plugin-driven UI navigation (ADR 0044)
on()Subscribe an in-process handler to bus topics (ADR 0039)
register_a2a_skill()Contribute an A2A card skill — advertised on the agent card and, when it declares output_schema + result_mime, enforced by the executor's structured finalizer…
register_chat_command()Own a user-only chat control command — /<name> … short-circuits the turn with the handler's reply, like the core /goal (and the old, now plugin-owned, /issue)
register_component()Contribute a component-v1 KIND (ADR 0051) the chat stream may carry — e.g. a chip that points into this plugin's console view (the artifact…
register_embedder()Contribute an in-process embedder (ADR 0031 follow-up) — factory(config) -> (text: str) -> list[float]
register_goal_hook()React when a goal reaches a terminal state (ADR 0028 D4)
register_goal_verifier()Contribute an in-process goal/watch verifier (ADR 0028) — an async (spec, ctx) -> VerifyResult referenced by a {"type":"plugin", "check":"<name>"} goal or watch
register_knowledge_store()Contribute a knowledge backend (ADR 0031) — factory(config) -> KnowledgeBackend (see knowledge.backend.KnowledgeBackend for the surface: pgvector, Qdrant…
register_late_tool_factory()Contribute a tool factory that runs AFTER the full toolset is assembled
register_lifecycle_hook()React to a system lifecycle event (ADR 0074) — on_app_loaded (boot finished: graph compiled + scheduler/surfaces/fleet up), on_agent_active (the agent went…
register_mcp_server()Contribute a managed MCP server the agent connects to (ADR 0019)
register_middleware()Add a plugin-contributed LangGraph AgentMiddleware (ADR 0032)
register_router()Mount a FastAPI APIRouter on the server (ADR 0018)
register_service()Offer fn to OTHER plugins as the service <plugin_id>.<name> (ADR 0116)
register_setup_step()Register a SETUP STEP: the server-side half of a plugin_setup setup-gap action
register_skill_dir()Add a directory of SKILL.md skills bundled with the plugin
register_subagent()Add a SubagentConfig to SUBAGENT_REGISTRY (ADR 0018)
register_surface()Register a lifecycle-managed background surface (ADR 0018)
register_thread_id_resolver()Override how the checkpointer thread_id is derived for each turn (#571): fn(request_metadata: dict, session_id: str) -> str
register_tool()Expose a LangChain tool to the agent
register_tools()Convenience: register an iterable of tools
register_watch_hook()React when a watch trips (ADR 0067) — on_met (verifier passed), on_expired (deadline passed), on_stalled (evidence unchanged for stall_after checks…
register_work_provider()Contribute this plugin's own open work to the agent's <working_state> block (ADR 0079, the Observe step)
register_workflow_dir()Add a directory of *.yaml workflow recipes bundled with the plugin (ADR 0027)
report_setup_gap()Tell the operator this plugin can't do its job until something is fixed (a missing binary, no coder delegate, an unauthenticated CLI) — or clear that notice with message=None
save_media()Persist a generated binary artifact (image/audio/video/…) into the CORE media store and get back a MediaRef {id, url, path, mime} (#1929)

registry.emit ​

python
registry.emit(topic: str, data: dict | None = None) -> None

Broadcast an event on the bus (ADR 0039) — fire-and-forget.

Topics are namespaced to this plugin: emit("created") publishes "<plugin_id>.created". A topic not already under this plugin's namespace is auto-prefixed — a plugin may only publish under its own namespace (the no-cross-dependency clause). Consumers subscribe by topic; nobody imports this plugin to hear it.

registry.live_config ​

python
registry.live_config() -> dict

The plugin's CURRENT resolved config, re-read from the host on each call.

self.config is a register-time snapshot, so a hot-reload after a config save can't refresh it for an already-mounted router or running surface (FastAPI can't re-mount a router). But the reload DOES rebuild STATE.graph_config — so a handler that reads this on each request reflects config edits with no restart. Falls back to the snapshot when the host state isn't available (unit tests, or a section that resolved empty).

registry.navigate ​

python
registry.navigate(view: str = '') -> None

Ask the operator console to open one of THIS plugin's views — plugin-driven UI navigation (ADR 0044). Fire-and-forget.

Publishes a reserved host intent ui.navigate with {plugin, view}; the console focuses plugin:<this plugin>:<view> when that surface exists (a blank view opens the plugin's first view). Unlike emit, this is a host-level navigation request, not a namespaced plugin event — and it is scoped: the payload carries this plugin's id, so a plugin can only open its own views, never hijack the console to another surface. The console honors it generically (one handler, no per-plugin code), so any plugin gets agent-driven navigation for free.

registry.on ​

python
registry.on(topic: str, handler) -> None

Subscribe an in-process handler to bus topics (ADR 0039). topic may use * (one segment) / # (tail) wildcards and match ANY plugin's namespace — subscribing is read-only and safe. handler(payload) gets {event, data, seq}, may be sync or async; exceptions are isolated by the bus.

registry.register_a2a_skill ​

python
registry.register_a2a_skill(spec: dict) -> None

Contribute an A2A card skill — advertised on the agent card and, when it declares output_schema + result_mime, enforced by the executor's structured finalizer (#570). Distinct from register_skill_dir (disk SKILL.md procedural memory): this is what the card advertises to callers. spec is a dict with at least id/name/description (+ optional tags/examples/ output_schema/result_mime). All three are required — the card build hard-indexes them, so a spec missing any must fail HERE, attributed to its plugin, not as a KeyError at boot inside the card build (#2754).

registry.register_chat_command ​

python
registry.register_chat_command(name: str, handler) -> None

Own a user-only chat control command — /<name> … short-circuits the turn with the handler's reply, like the core /goal (and the old, now plugin-owned, /issue).

handler is async (rest: str, session_id: str) -> str | dict | None: rest is everything after the token; return the reply string to send (the turn is NOT run through the agent), or None to pass the message through as a normal turn. This is user-only by design — it is NOT an agent tool, so a plugin can expose a write action (file an issue, open a PR) that the model can't invoke autonomously. Read your own config in register() and close over it, e.g. repo = self.config.get("default_repo").

To collect input, return a form request (#1701 Slice 2): {"form": <request_user_input-shaped payload>, "on_submit": <async (answers: dict, session_id: str) -> str | dict | None>}. The runtime opens the form in the composer (the same HitlForm the agent's HITL uses) and, on submit, calls on_submit with the field values — which may itself return a reply string or another form (a multi-step wizard).

The token is slugified + lowercased (/Issue == /issue). The reserved core tokens goal / lifecycle are refused; a collision with a token another enabled plugin already registered keeps the first and warns (resolved in the loader).

registry.register_component ​

python
registry.register_component(name: str, validator) -> None

Contribute a component-v1 KIND (ADR 0051) the chat stream may carry — e.g. a chip that points into this plugin's console view (the artifact plugin's artifact-ref, #3617).

Your TOOL emits it: return graph.components.encode_component(name, props) after the model-facing text, and the server lifts it into a component frame. The host forwards a payload only when validator(props) returns None; return a short reason string to drop it (a raise also drops it). Keep props a POINTER — ids, numbers, short labels — never content: they persist in chat history. The console renders the kind through a registerChatComponent renderer (apps/web/src/ext); without one it shows a labeled "[unsupported component]" note.

name is lowercase kebab ([a-z][a-z0-9-]*, ≤ 64 chars) and can't be a core kind (table/keyvalue/timeline/code-ref); an invalid name or non-callable validator is refused with a warning. The kind is live only while the plugin is loaded — disabling it stops extraction on the next reload. show_component never builds a plugin kind. Guard with getattr(registry, "register_component", None) on hosts older than this seam.

registry.register_embedder ​

python
registry.register_embedder(name: str, factory) -> None

Contribute an in-process embedder (ADR 0031 follow-up) — factory(config) -> (text: str) -> list[float]. Selected by a fork with knowledge.embedder: "<name>" for the built-in hybrid store, avoiding the gateway round-trip (e.g. fastembed / sentence-transformers). On a None/error return the agent falls back to the gateway embedder (degrade-safe).

registry.register_goal_hook ​

python
registry.register_goal_hook(*, on_achieved=None, on_failed=None) -> None

React when a goal reaches a terminal state (ADR 0028 D4). on_achieved / on_failed take the terminal GoalState (sync or async) — push a notification, record a finding, or set the next goal. Provide either/both. A raising hook is logged + swallowed. (A metric to watch over time is a watch — see register_watch_hook with on_met/on_expired/on_stalled, ADR 0067.)

registry.register_goal_verifier ​

python
registry.register_goal_verifier(name: str, fn, description: str = '') -> None

Contribute an in-process goal/watch verifier (ADR 0028) — an async (spec, ctx) -> VerifyResult referenced by a {"type":"plugin", "check":"<name>"} goal or watch. Name it <plugin-id>:<verifier> to avoid collisions; args in the spec are declarative data your verifier validates (no shell, no eval). This is the only verifier type safe to set programmatically (D3). ctx is a graph.goals.VerifyContext; ctx.invoker identifies the polling goal/watch (kind/id/session_id/ interval_s, #1641) so a verifier can keep per-invoker state.

description is a one-line human summary shown wherever a verifier is CHOSEN (the console's goal/watch creators list it beside the core types). Optional and additive — an older plugin that omits it just shows its <plugin-id>:<name>.

registry.register_knowledge_store ​

python
registry.register_knowledge_store(name: str, factory) -> None

Contribute a knowledge backend (ADR 0031) — factory(config) -> KnowledgeBackend (see knowledge.backend.KnowledgeBackend for the surface: pgvector, Qdrant, Chroma, a managed vector DB…). Selected by a fork with knowledge.backend: "<name>"; on a None/error return the agent keeps the built-in SQLite store (degrade-safe). Name it simply (e.g. pgvector); a collision keeps the first.

registry.register_late_tool_factory ​

python
registry.register_late_tool_factory(factory) -> None

Contribute a tool factory that runs AFTER the full toolset is assembled.

factory(all_tools, config) -> BaseTool | list[BaseTool] | None receives the fully-resolved, denylist-final tool list (core + subagent + plugin + MCP tools, after the operator tools.disabled sweep — ADR 0103 S4: a proxying factory may treat it as exactly what the model has bound) and the live LangGraphConfig; its result is appended last (and denylist-swept itself), before the deferred search_tools meta-tool (so the late tool stays discoverable). For a meta-tool that must see or wrap every other tool — e.g. programmatic tool-calling that proxies the whole set — which a plain register_tool can't, because plugin tools are registered before the set is complete. Built last, so it can reference any tool but never itself. A raising factory is logged and skipped, never breaking the graph build.

registry.register_lifecycle_hook ​

python
registry.register_lifecycle_hook(*, on_app_loaded=None, on_agent_active=None, on_system_wake=None) -> None

React to a system lifecycle event (ADR 0074) — on_app_loaded (boot finished: graph compiled + scheduler/surfaces/fleet up), on_agent_active (the agent went idle → active — the first turn after a quiet gap, debounced), on_system_wake (reserved — the desktop shell woke; emitted in a follow-up). Each takes the event payload dict (ts + previous_state + event-specific keys), sync or async. Provide ANY of the three. A raising hook is logged + swallowed — a bad hook must never break boot or a turn. (These are broadcast on the bus too, so registry.on(...) is the zero-config alternative; a hook is the direct callback form.)

registry.register_mcp_server ​

python
registry.register_mcp_server(factory) -> None

Contribute a managed MCP server the agent connects to (ADR 0019).

factory is a callable factory(config) -> dict | None returning a mcp.servers[] entry ({name, transport, command, args, env, ...}) or None when the server shouldn't start (off / not yet connected). It's called at every graph build with the live LangGraphConfig, so the server comes and goes with config — this is how a plugin ships an OAuth-gated MCP server without a core edit. A returned entry whose name matches a user-defined mcp.servers entry replaces it.

registry.register_middleware ​

python
registry.register_middleware(factory) -> None

Add a plugin-contributed LangGraph AgentMiddleware (ADR 0032).

factory is (config) -> AgentMiddleware | None — it receives the live LangGraphConfig and returns a middleware instance (or None to opt out). Plugin middleware is appended to the chain just before the internal message-capture middleware (so before/after-model + tool hooks run, and the turn is still captured). For per-request data, read graph.middleware.request_context.current_request_metadata().

This is the last core extension point that previously forced a fork to edit graph/agent.py / executor.py.

registry.register_router ​

python
registry.register_router(router, prefix: str | None = None) -> None

Mount a FastAPI APIRouter on the server (ADR 0018).

Defaults to the namespaced prefix /plugins/<id> so a plugin can't silently shadow a core route. Pass prefix="" (or your own) to mount elsewhere — an escape hatch, logged. The two canonical prefixes are the public view /plugins/<id> and the bearer-gated data router /api/plugins/<id> (the documented two-router pattern — docs/guides/ plugin-views.md, ADR 0026); a prefix outside both logs a WARNING (#870, #1732). The default-deny auth middleware guards all non-public paths regardless of prefix.

registry.register_service ​

python
registry.register_service(name: str, fn, description: str = '') -> None

Offer fn to OTHER plugins as the service <plugin_id>.<name> (ADR 0116).

The cross-plugin CALL seam: where the event bus (emit) is fire-and-forget, a service returns a result. A consumer resolves it at call time with graph.sdk.service("<plugin_id>.<name>") and gets your callable — or None when your plugin is disabled, so it must degrade (a service is an optional capability). It never imports your plugin, so you can refactor freely behind the name. The artifact plugin's artifact.show is the reference: the data plugin creates charts through it.

The name is namespaced to this plugin: "show" registers "<plugin_id>.show", and a name already under this plugin's namespace is kept as-is (a plugin may only provide under its own). The bare part is lowercase [a-z][a-z0-9_]*. Treat the callable's signature and return shape as a PUBLIC API — document them in its docstring, keep them backward compatible, and return refusals as data rather than raising for an expected "no". It runs in the CALLER's thread (often a tool body; sync or async is your call, say which). description is a one-line summary for status surfaces. Live while the plugin is loaded — a reload that disables it stops the name resolving. Guard with getattr(registry, "register_service", None) on hosts older than this seam.

registry.register_setup_step ​

python
registry.register_setup_step(step: str, fn) -> None

Register a SETUP STEP: the server-side half of a plugin_setup setup-gap action.

For a fix that is a COMMAND rather than a setting — download a CLI, install a browser — which a banner would otherwise tell the operator to go and run in a terminal. Report the gap with action={"kind": "plugin_setup", "step": step, "label": "Download the CLI"} and the console renders a button on its banner; clicking it POSTs /api/plugin-setup/<this plugin>/<step> — a core route outside the /api/plugins/<id>/ subtree, so no manifest public_paths / federation_paths can lower its operator-credential gate — and the host runs fn() off the event loop. Only the callable held for exactly this (plugin, step) pair ever runs — the action is data naming it, nothing more.

step is one lowercase identifier ([a-z0-9][a-z0-9_-]*, at most 64 chars; a plugin may hold 8). fn takes no arguments and returns a short message string, or a dict {"ok": bool, "message": str, "pending": bool}. Long work belongs on a thread: start it, re-report the gap with its progress (and no action, so it can't be clicked twice), return pending: True, and clear or re-report the gap when it finishes — the console keeps refreshing runtime status while a step it started is pending. An exception becomes ok: False with its message, never a 500. Steps are dropped along with the plugin's gaps when it is disabled or uninstalled. register_setup_step arrived with the plugin_setup kind; a plugin that must also run on older hosts guards it with getattr(registry, "register_setup_step", None) — an older host drops the unknown action kind, so the banner degrades to its message.

registry.register_skill_dir ​

python
registry.register_skill_dir(path: str | Path) -> None

Add a directory of SKILL.md skills bundled with the plugin.

Relative paths resolve against the plugin's own directory.

registry.register_subagent ​

python
registry.register_subagent(config) -> None

Add a SubagentConfig to SUBAGENT_REGISTRY (ADR 0018).

Picked up by every graph build, so the lead agent can delegate to it via task / task_batch — no edit to graph/subagents/config.py.

registry.register_surface ​

python
registry.register_surface(start, stop=None, name: str | None = None, reload=None) -> None

Register a lifecycle-managed background surface (ADR 0018).

start (sync or async, no args) runs in the server's startup hook — so it has the running loop, like the Discord gateway — and may return a task/ handle. stop (optional) runs in shutdown. reload (optional, called with the new LangGraphConfig on a config reload) lets a running surface reconfigure in place when its config changes. Best-effort: a failing surface logs, never breaks boot.

register() re-runs on every config reload. What happens to a surface that is still wanted afterwards depends on the two registrations (#3593):

  • both declare reload → it keeps running and reload(cfg) is called;
  • otherwise, the same stop (a module-level function, or a long-lived object's method) → it is the same surface and is left running untouched;
  • otherwise, a different stop (fresh closures over fresh objects) → it is stopped, its task given a grace period (then cancelled), and started again from the new registration — so the running surface and the plugin's routes/tools never hold two different instances. If the old one will not end, it is kept and the replacement is not started.

registry.register_thread_id_resolver ​

python
registry.register_thread_id_resolver(fn) -> None

Override how the checkpointer thread_id is derived for each turn (#571): fn(request_metadata: dict, session_id: str) -> str. Lets a fork scope memory off request metadata (e.g. per-project working memory) without editing server/chat.py. One resolver wins — last registration across enabled plugins (a warning fires if more than one is contributed). Unset ⇒ the template default (a2a:<session_id>).

registry.register_tool ​

python
registry.register_tool(tool) -> None

Expose a LangChain tool to the agent.

registry.register_tools ​

python
registry.register_tools(tools) -> None

Convenience: register an iterable of tools.

registry.register_watch_hook ​

python
registry.register_watch_hook(*, on_met=None, on_expired=None, on_stalled=None, on_changed=None) -> None

React when a watch trips (ADR 0067) — on_met (verifier passed), on_expired (deadline passed), on_stalled (evidence unchanged for stall_after checks, the watch stays active), on_changed (a trigger: "change" watch observed a new value — NOT the same as met: the value moved, the condition may still be false). Each takes the Watch (sync or async). Provide ANY of them. A raising hook is logged + swallowed.

registry.register_work_provider ​

python
registry.register_work_provider(name: str, fn, label: str = '') -> None

Contribute this plugin's own open work to the agent's <working_state> block (ADR 0079, the Observe step).

fn is () -> list[dict]; each item is {"id", "title", "state", "hint"} with every field optional (a plain string is accepted and rendered verbatim). The host renders them under a heading — label if you set one, else OPEN WORK (<name>) — in the SAME block and the same line shape as OPEN TASKS, so the agent reads one vocabulary for its commitments rather than two.

This is projection, not replication: your store stays the system of record and the host reads a bounded snapshot per turn, so there is no copy to keep in sync. Use it when your plugin owns a queue the agent is responsible for — a board, a review lane — not for reference data the agent merely consults (that is knowledge/RAG).

Your provider must be cheap and non-blocking: it runs inline on EVERY turn. Return an in-memory snapshot — never shell out, hit the network, or take a lock. A provider slower than graph.work_providers.SLOW_PROVIDER_S is logged once, and one that raises is skipped without disturbing the turn.

registry.register_workflow_dir ​

python
registry.register_workflow_dir(path: str | Path) -> None

Add a directory of *.yaml workflow recipes bundled with the plugin (ADR 0027). Relative paths resolve against the plugin's own directory. A conventional <plugin>/workflows/ dir is auto-discovered without this call; use it for a non-standard location.

registry.report_setup_gap ​

python
registry.report_setup_gap(key: str, message: str | None, *, label: str | None = None, action=None) -> None

Tell the operator this plugin can't do its job until something is fixed (a missing binary, no coder delegate, an unauthenticated CLI) — or clear that notice with message=None. Surfaces in GET /api/runtime/status as a structured record in setup_gaps[] (plugin, key, label, message, actions), which the console renders as a dismissible banner, plus the legacy "<label>: <message>" line in warnings[] for older consumers. It self-clears live, so a plugin that re-checks each tick heals the banner without a restart. key namespaces one gap per concern ("br", "auth"); label is the display name for the banner (defaults to the plugin id). Plugins that also run on older hosts guard with getattr(registry, "report_setup_gap", None).

action (optional, keyword-only, SINGULAR — there is no actions=; omit it and storage/warnings are byte-for-byte what they always were) attaches a bounded, DECLARATIVE remediation hint that the console renders as a button on the banner: a single action dict or a list of them, e.g. action={"kind": "plugin_config", "label": "Configure My Plugin", "fields": ["repo"]} (a plugin_config action with no label reads "Configure <label>"). An action is closed, server-validated DATA, never behavior — a fixed kind from setup_gaps.ACTION_KINDS ("plugin_config" opens THIS plugin's config section — the target is forced to this plugin, it can't aim at another; "global_settings" opens global settings at an optional section target; "plugin_setup" names a step THIS plugin registered with register_setup_step and renders as a button that runs it; "install_deps" renders as an "Install dependencies" button that installs THIS plugin's declared requires_pip through the host's install-deps route) plus optional bounded label/fields plain text; any other key is dropped. The host sanitizes on the way in: an unknown kind, a callback, an arbitrary URL, HTML, or an oversized payload is dropped, not stored, and a malformed action never raises into plugin loading. It is the console's job to turn a kind into a known control — never a plugin string into a URL or callback. action arrived in v0.162.0; a plugin that must also run on older hosts feature-detects the keyword ("action" in inspect.signature(fn).parameters) and makes the plain call otherwise.

A report from a registry whose plugin has since been disabled, uninstalled, or failed to reload is dropped — so a thread of the old load that finishes late (a download, an install) can't bring back a banner whose buttons no longer lead anywhere.

registry.save_media ​

python
registry.save_media(data, mime: str, meta: dict | None = None)

Persist a generated binary artifact (image/audio/video/…) into the CORE media store and get back a MediaRef {id, url, path, mime} (#1929).

The convention: embed ref.url in the tool's returned markdown — f"![generated image]({ref.url})" — and the console chat renders it inline, with no plugin-owned route and no UI change. data is the raw bytes or a source file path to copy in; mime drives the file extension and the served content type; meta is optional provenance (prompt, model, …) kept in a hidden sidecar (this plugin's id is recorded automatically). Files land under the instance data dir (instance_paths().store("media")) and survive restart.

Access: the returned url carries a per-file HMAC signature, so it renders even under a bearer-gated deployment (an <img> tag can't send a header) while the store stays default-deny; media.public: true (core config) opts the whole store public, and media.retention_days prunes old files. A media.saved event is broadcast on the bus.

Host services — registry.host ​

Services the server owns: calling the agent, the event bus, live config. The server populates them before any surface starts, so they are None at import time and in host-free tests — always guard. Generated from graph/plugins/host.py.

ServiceTypePurpose
host.invokeCallable[..., Awaitable[strasync (prompt, session_id, *, tool_fence=None) -> str — invoke the agent as a chat surface (one conversation per session_id, the LangGraph thread key). tool_fence (#2972, optional keyword) is a per-turn tool allowlist for a turn that originated with an untrusted party — e.g. a surface relaying another operator's agent; the host blocks tool calls outside it. Surfaces that need it should feature-detect (inspect.signature) and refuse to run unfenced on an older host.
host.invoke_delegateCallable[..., Awaitable[strasync (name, prompt, conversation_key=None, *, permissions="readonly") -> str — invoke a configured delegate without reaching into its plugin package. The default host-enforced ceiling makes an ordinary plugin invocation read-only.
host.publishCallable[[str, dict], Any(event: str, data: dict) -> None — publish to the server→client event bus.
host.subscribeCallable[[], Any() -> subscription — subscribe to the event bus (e.g. return-address delivery).
host.onCallable[[str, Callable], Any(topic, handler) -> unsubscribe — register an in-process handler for bus topics (ADR 0039). Lets a server-side plugin react to events (topic-filtered, sync/async) without ever importing the plugin that emitted them.
host.configCallable[[], Any() -> LangGraphConfig — the live server config. A route handler reads this for the current resolved values (incl. plugin_config) rather than closing over a load-time snapshot, so it sees Settings changes without a restart.
host.apply_settingsCallable[[dict], Any(patch: dict) -> (ok, messages) — persist a nested config patch to YAML (secrets routed automatically) and reload the graph once. Heavy (a full reload) — a route should call it via asyncio.to_thread. Lets a plugin route apply config + reload (e.g. an OAuth Connect flow flipping enabled).

Part of the protoLabs autonomous development studio.