Plugin context vs runtime context

Plugin code sees two contexts — PluginContext (boot-time, no user) and RuntimeContext (per-request, authenticated user). Knowing which one you're in is the first thing to learn.

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.

Source: packages/oracle-runtime/src/runtime-context/ and PluginContext / RuntimeContext.