Skip to main content

Two contexts, two lifetimes

ContextLifetimeHas user?Where you receive it
PluginContextBoot (cached)NogetTools, getSubAgents, getMiddlewares, getNestModules
RuntimeContextFresh per requestYes (authenticated)Tool handlers, sub-agent handlers, middleware hooks, getRequestTools, getRequestSubAgents
The distinction matters because the wrong context can’t see what your code needs. A boot-time hook can’t ask “who is the user” — there is none yet. A per-request hook can’t be used to register a Nest module — it’s already too late.

What each context carries

PluginContext — boot-time, no user

  • config — the merged, Zod-validated env (base + every plugin’s configSchema).
  • identity — your oracle’s identity (name, org, description, entityDid, prompt).
  • availablePlugins — set of names of all loaded plugins; useful for soft-dependency branching.
  • logger — a plugin-scoped logger.

RuntimeContext — per request, authenticated user

Everything in PluginContext, plus:
FieldPurpose
userDID, Matrix user ID, UCAN delegation, timezone
sessionThread ID (= session.id), client (portal / matrix / slack), request ID, room ID
historymessages, recent(n), userContext, typed state view
secretsgetIndex(), getValues(keys) for per-user secrets
blobStoreShort-TTL keyed store for values the model must never echo verbatim (UCAN invocation CARs, JWTs): put() returns an opaque blob_<hex> id, get() resolves it server-side, isValidBlobId() checks the format. Blobs are scoped to the issuing user’s DID
matrixScoped methods: postToRoom, getRoomState, getEventById
ucanrequireCapability, hasCapability, mintInvocation, resolveServiceDid, hasSigningKey(), createInvocationFromDelegation()
llmget(role, params) for the configured provider — role is 'main' / 'subagent' / 'utility'
emitTyped event emitter (toolCall, actionCall, renderComponent, reasoning, …)
sharedTyped reads from other plugins’ getSharedState
loadedPluginsSet of plugin names loaded for this thread
toolCallIdThe current tool call’s id — needed when a tool returns a LangGraph Command
abortSignalRequest-scoped abort
Full field list and types: RuntimeContext reference.

Registration vs execution

A tool registered via boot-time getTools(ctx) still receives a fresh RuntimeContext when its handler fires. “Boot-time” applies to when the tool is registered, not to when it runs. Both contexts coexist over the lifetime of any tool.

Choosing the right hook

If a hook exists in both forms, pick by what your code reads:
If your code needs…Use
Only config + identityBoot-time hook (getTools(ctx))
Live state (loadedPlugins, userContext, browser tools)Per-request hook (getRequestTools(rtCtx))
The authenticated user’s DID / timezone at registration timePer-request hook
Stable tool listBoot-time hook
When both hooks fire on the same plugin, their outputs are merged — no need to choose one or the other.

Write a plugin

See both contexts in real code.

RuntimeContext reference

The full field list.
Source: packages/oracle-runtime/src/runtime-context/ and PluginContext / RuntimeContext.