Skip to content

Host API

Z-M-Huang edited this page May 2, 2026 · 5 revisions

Host API

The services core exposes to every extension. Stable, versioned, not replaceable.

Every extension receives a Host API context at init and retains it for its lifetime. An extension never talks to another extension directly — it talks to core through the Host API, and core routes through contracts.


Services

The Host API is a set of services. Each service is small, scoped, and has a precise interaction model.

classDiagram
    class HostAPI {
        +env : EnvReader
        +config : ConfigReader
        +events : EventPublisher
        +audit : AuditLogger
        +interaction : InteractionRequester
        +cancellation : CancellationFactory
        +session : SessionAccessor
        +workspace : WorkspaceInfo
        +prompt : PromptReader
        +resource : ResourceReader
        +context : ContextContributor
        +commands : CommandSurface
        +metrics : RuntimeReader
    }
    class EnvReader {
        +get(name) : value | undefined
        +require(name) : value
    }
    class ConfigReader {
        +readOwn() : object
        +readGlobal(key) : value
    }
    class EventPublisher {
        +publish(event)
    }
    class AuditLogger {
        +record(event)
    }
    class InteractionRequester {
        +request(kind, payload) : response
    }
    class CancellationFactory {
        +token(scope)
    }
    class SessionAccessor {
        +read()
        +append(record)
        +stateSlot(extId)
    }
    class WorkspaceInfo {
        +projectRoot
        +globalRoot
        +correlationId
        +sessionId
    }
    class PromptReader {
        +list(filter) : PromptRef[]
        +read(id) : Prompt
    }
    class ResourceReader {
        +list(filter) : ResourceRef[]
        +read(id) : Resource
    }
    class ContextContributor {
        +contribute(fragment)
    }
    class CommandSurface {
        +list() : CommandRef[]
        +complete(input, cursor) : Completion[]
        +dispatch(name, args) : CommandResult
    }
    class RuntimeReader {
        +snapshot() : RuntimeSnapshot
        +subscribe(handler) : Unsubscribe
        +subscribeToTokens(handler) : Unsubscribe
    }
    HostAPI --> EnvReader
    HostAPI --> ConfigReader
    HostAPI --> EventPublisher
    HostAPI --> AuditLogger
    HostAPI --> InteractionRequester
    HostAPI --> CancellationFactory
    HostAPI --> SessionAccessor
    HostAPI --> WorkspaceInfo
    HostAPI --> PromptReader
    HostAPI --> ResourceReader
    HostAPI --> ContextContributor
    HostAPI --> CommandSurface
    HostAPI --> RuntimeReader
Loading

env — EnvReader

Access to environment values routed through the core env provider.

Call Semantics
get(name) Returns the value or undefined.
require(name) Returns the value or throws a load-time error if missing.

There is no bulk-read API. Listing all env names is not exposed. See LLM Context Isolation and Risk F6 in the plan.

Values resolved through EnvReader may be used inside the extension. They may not enter the LLM request under any condition — the LLM Context Isolation hard ban is enforced at Context Assembly time, and the Context Provider capability path described in earlier drafts was removed by Q-6. The one sanctioned path for environment-shaped material reaching the LLM is the user typing the value directly into the interactor.


config — ConfigReader

Access to the extension's own configuration and a restricted subset of global settings.

Call Semantics
readOwn() The validated config block for this extension.
readGlobal(key) Read-only access to whitelisted global keys (security mode, project root, …).

An extension cannot read another extension's configuration. Extensions cannot enumerate the keyspace.


events — EventPublisher

Publishes events on the event bus. Projection only — not authoritative.

An extension publishing a custom event must declare the event's schema in its contract. Subscribers discover event shapes through the Prompt Registry / Resource Registry when appropriate, or through the event type registry maintained by core.


audit — AuditLogger

Records a durable audit event and exposes a narrow read surface for callers that need to project authoritative state. Audit is distinct from logs: audit is a structured record of authoritative actions (approvals, forced prompts, compaction fires, model/provider switches, extension-set reloads, trust decisions, SM transitions). See Audit Trail.

Call Semantics
record(event) Append a durable audit record. Caller is the actor (subject to the actor-type rules in Audit Trail).
query({ class?, kind?, correlationId?, parentSessionId?, subagentId?, since? }) Read the session's audit history filtered by the supplied criteria. Returns immutable records.
activeSubagents() Convenience derived view: every SubagentSpawned record in the current session whose terminal record (SubagentCompleted / SubagentHalted / SubagentAborted) has not yet landed. The canonical answer to "which subagents are running right now?" — UIs reconcile their event-projected subagent panels against this view (see Default TUI § Subagents panel).

Audit records:

  • Never contain resolved secrets (see Secrets Hygiene).
  • Always carry the correlation ID.
  • Are immutable after write.

interaction — InteractionRequester

Issues a typed request through the Interaction Protocol.

Only certain contexts are allowed to request interactions:

  • Tools asking for user input (Ask).
  • Core asking for approval (tool calls, destructive commands).
  • Providers asking for auth (Auth.DeviceCode, Auth.Password).
  • Commands asking to confirm a destructive action.
  • SMs asking to Confirm, Select, or Ask through the orchestrator session's active interactors at stage boundaries.

grantStageTool is core-only. It is auto-issued when an LLM proposal inside a driving SM stage falls outside allowedTools; no extension category — SM included — may invoke it through interaction.request(...). See Interaction Protocol § grantStageTool.

Hooks and Context Providers may not drive the Interaction Protocol — see Hooks contract § a hook may not and Context Providers contract. An extension that calls interaction.request(...) when it should not fails at the contract level — the validation pipeline disallows interaction requests from categories that do not own the kind.


cancellation — CancellationFactory

Creates cancellation tokens scoped to session, turn, or tool. Extensions pass tokens into long-running work; tokens fire when the enclosing scope cancels. See Execution Model § Cancellation and Concurrency and Cancellation.

Extensions must observe tokens. A tool that ignores cancellation blocks the turn; a guard that ignores cancellation delays session close. This is a v1 reality (no sandbox) — misbehaving extensions cannot be preempted.


session — SessionAccessor

Read/append to the session. The session's shape is core-owned (see Session Manifest); extensions append records they own.

Call Semantics
read() Read the current session view (respects scope).
append(record) Append a record attributed to the caller.
stateSlot(extId) Read/write the caller's own state slot.
openChild({ prompt, requestedEnvelope, label?, depth }) Open a subagent session on behalf of the caller. Restricted: only the bundled delegate tool may invoke this in v1. The validation pipeline rejects calls from any other extension. The call validates the depth cap and envelope-subset rules, then auto-issues the approveSubagentEnvelope interaction; on approval, the child session is constructed with inherited posture and seeded envelope. The call resolves with the child's terminal state (Completed / Halted / Aborted / Rejected).

An extension cannot read another extension's state slot. Cross-extension communication rides events, commands, or contracts.

SessionAccessor serializes mutations — two commands racing to append do so in a defined order. See Command Model and Event and Command Ordering.

openChild is restricted because subagent spawning is a trust-amplification surface — opening sessions outside of delegate would let an extension bypass the user-approved envelope flow. The wiki's allowance at Stage Executions § What extensions may still do on their own for "additional sessions" remains valid for fully ungoverned ad-hoc use, but those sessions are not subagent sessions and do not get the audit-chain attribution, IP routing, cancellation cascade, or headless behavior the contract specifies.


workspace — WorkspaceInfo

Read-only surface for paths and IDs.

Field Meaning
projectRoot Always <cwd>/.stud/ — no walk-up. See Project Root.
globalRoot ~/.stud/.
correlationId Current turn's correlation ID, if inside a turn.
sessionId Current session ID.

prompt — PromptReader

Read-only access to the Prompt Registry.

Call Semantics
list(filter) Enumerate prompt references the caller is allowed to see (scope + namespace filtered).
read(id) Fetch a prompt by namespaced ID (bundled:, mcp:<server>:, cp:<ext>:).

Commands and Context Providers are the primary consumers. Extensions cannot mutate the Prompt Registry through this service — contribution happens via contract-declared sources at load time.


resource — ResourceReader

Read-only access to the Resource Registry.

Call Semantics
list(filter) Enumerate resource references the caller is allowed to see.
read(id) Fetch a resource by namespaced ID.

Context Providers are the primary consumers. Resources are reference-only — an extension cannot mutate another extension's resource.


context — ContextContributor

Available to Context Providers only. The contributor hands a fragment to the core context-assembly subsystem at the declared target stage.

Call Semantics
contribute(fragment) Submit a context fragment (prompt text, reference pointer, or system message) for this turn.

Fragments are subject to per-provider budget, capability-flag enforcement, and provenance labeling. See Context Assembly.


commands — CommandSurface

Available to UI and Command extensions. Other extension categories receive a stub that rejects dispatch attempts.

Call Semantics
list() Return a UI-safe command catalog snapshot: names, aliases, descriptions, argument hints, categories, source extension, and turn-safety metadata.
complete(input, cursor) Return side-effect-free completion suggestions for a slash-command draft.
dispatch(name, args) Invoke the command dispatcher. This is the only authoritative command execution path.

list() and complete() are projection surfaces for slash palettes and help views. They do not run handlers and do not expose secrets. dispatch() emits the normal command events and audit records described by Command Model.


metrics — RuntimeReader

A read-only projection of runtime state. Available to every extension. Designed for plugin extensibility: a third-party UI region, alternate header, or sidebar should be able to render real information (the list of MCP servers with their connection status, every loaded tool with its source layer and allowedNow resolution, the current state-machine stack) without reaching into core internals.

metrics is projection only. It carries no authority. Extensions cannot mutate state through it; subscribing or snapshotting does not change session behavior.

Call Semantics
snapshot() Returns a deeply-readonly RuntimeSnapshot capturing the current view of session, provider, tokens, context, tools, MCP, state machine, diagnostics, UIs, hooks, and every loaded extension.
subscribe(handler) Calls handler with a fresh snapshot when underlying state changes. Debounced at the collector (default 60 ms) to avoid render thrash. Returns an unsubscribe function.
subscribeToTokens(handler) Fine-grained subscription for the high-rate tokens slice during streaming. Use when a widget intentionally wants every delta. Bypasses the snapshot debounce.

Snapshot shape

RuntimeSnapshot is a tree of named slices. Each slice surfaces structured detail, not just counts:

Slice Surfaces
session Session id, started-at, last-turn-at, turn count, cwd, project trust, security mode, online indicator.
provider Current provider/model + capability flags (streaming, tool calling, thinking, context window); list of available providers with the same shape.
tokens Cumulative input/output token totals plus the last turn's input/output.
context Used tokens, optional window tokens, optional percent, count of context-provider fragments included in last compose.
tools List of ToolInfo (id, name, description, source layer, sensitivity, allowedNow, approval key, invocation counters), plus activeCount / totalCount.
mcp List of McpServerInfo (id, transport, status, prompt/resource/tool counts, last error), plus connectedCount / configuredCount.
stateMachine Optional. Attached SM id, current stage, stage stack, turn cap, turn count, last NextResult.
diagnostics Counts by level, plus a redacted ring buffer of recent items (default last 50).
ui List of UiInfo (id, roles, target UI for region contributors, region contributions, active flag).
hooks List of HookInfo (id, stage, point, kind).
extensions Every loaded extension across categories (id, kind, contract version, source layer, active flag).

Per-item shapes are normative; field-level additions are minor; field removals or renames are major. See Versioning and Compatibility.

Privacy guarantees

This surface is the most powerful read API in the system. The following invariants apply:

  • No env values, secrets, or credential references. RuntimeSnapshot has no path to environment values, the env map in settings.json, or the resolved value of any secret reference. Invariant 2 of the LLM Context Isolation safety set still holds.
  • No settings.json content. config per-extension blocks are not exposed through metrics. An extension that needs its own config still uses config.readOwn().
  • DiagnosticItem.message is human-safe. Stack traces and error chains stay in the audit path; what surfaces here is the redacted message form already shown to users.
  • Deeply readonly. Implementations return frozen objects; subscribe handlers receive frozen objects. The intent is structural enforcement, not just a TypeScript hint.

Usage from a UI region plugin

A status-line widget contributed by a third-party UI extension reads runtime state through this surface and re-renders on subscription:

const sub = host.metrics.subscribe((snap) => {
  const summary = snap.mcp.servers
    .map((s) => `${s.id}:${s.status}`)
    .join(", ");
  controller.updateItem({ id: "mcp-detail", label: "MCP", value: summary });
});
// Returned unsubscribe should be called from the contribution's deactivate hook.

Region plugins do not receive raw provider credentials, raw env, another extension's state slot, or direct tool-call authority through metrics (or any other host service). See UI and Extension Isolation.

Performance

  • The collector publishes a snapshot once per logical state change, debounced to ~60 ms by default. A burst of token deltas during streaming produces at most one debounced snapshot per debounce window plus the per-delta subscribeToTokens events.
  • Snapshots are immutable references. Successive calls to snapshot() may return the same object until state changes (referential stability), allowing efficient memoization in renderers.
  • The reader does no I/O. All sources push into the collector during their normal flow (provider stream, tool registry, MCP client, SM lifecycle, diagnostics buffer).

What the Host API is not

  • Not a replacement for contracts. A UI extension does not call the Host API to become a Tool. Category identity is fixed at load.
  • Not a bulk data API. There is no env.listAll, no session.readEverything, no config.readAll. Scoped access is a security property.
  • Not a way to reach another extension. There is no host.callExtension(name). Inter-extension behavior rides core-mediated contracts (events, commands, registries).
  • Not replaceable. Host API is core-only per Extensibility Boundary.

Versioning

Host API is versioned alongside core. An extension declares its requiredCoreVersion range in its contract; core refuses to load extensions that need a newer Host API than it ships. Host API deprecations follow the Deprecation Policy.


Extension lifecycle and Host API

The Host API becomes available at init. Some services — session accessor, interaction requester, cancellation tokens for turn scope — are only meaningful once the session is active. The Extension Lifecycle manager guarantees the service is present, even if it is not yet usable (calls return a typed "not-ready" error outside their valid scope). Extensions gate expensive initialization on activate, not init.

Introduction

Reading

Core runtime

Contracts

Category contracts

Context

Security

Runtime behavior

Operations

Providers (bundled)

Integrations

Reference extensions

Tools

UI

Session Stores

Loggers

Providers

Hooks

Context Providers

Commands

Case studies

Flows

Maintainers

Clone this wiki locally