-
Notifications
You must be signed in to change notification settings - Fork 0
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.
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
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.
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.
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.
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.
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, orAskthrough 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.
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.
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.
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. |
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.
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.
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.
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.
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. |
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.
This surface is the most powerful read API in the system. The following invariants apply:
-
No env values, secrets, or credential references.
RuntimeSnapshothas no path to environment values, theenvmap insettings.json, or the resolved value of any secret reference. Invariant 2 of the LLM Context Isolation safety set still holds. -
No
settings.jsoncontent.configper-extension blocks are not exposed throughmetrics. An extension that needs its own config still usesconfig.readOwn(). -
DiagnosticItem.messageis 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;
subscribehandlers receive frozen objects. The intent is structural enforcement, not just a TypeScript hint.
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.
- 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
subscribeToTokensevents. - 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).
- 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, nosession.readEverything, noconfig.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.
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.
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.
- Execution Model
- Message Loop
- Concurrency and Cancellation
- Error Model
- Event and Command Ordering
- Event Bus
- Command Model
- Interaction Protocol
- Hook Taxonomy
- Host API
- Extension Lifecycle
- Env Provider
- Prompt Registry
- Resource Registry
- Session Lifecycle
- Session Manifest
- Persistence and Recovery
- Stage Executions
- Subagent Sessions
- Contract Pattern
- Versioning and Compatibility
- Deprecation Policy
- Capability Negotiation
- Dependency Resolution
- Validation Pipeline
- Cardinality and Activation
- Extension State
- Conformance and Testing
- Providers
- Provider Params
- Tools
- Hooks
- UI
- Loggers
- State Machines
- SM Stage Lifecycle
- Stage Definitions
- Commands
- Session Store
- Context Providers
- Settings Shape
- Trust Model
- Project Trust
- Extension Isolation
- Extension Integrity
- LLM Context Isolation
- Secrets Hygiene
- Security Modes
- Tool Approvals
- MCP Trust
- Sandboxing
- Configuration Scopes
- Project Root
- Extension Discovery
- Extension Installation
- Extension Reloading
- Headless and Interactor
- Determinism and Ordering
- Launch Arguments
- Network Policy
- Platform Integration
Tools
UI
Session Stores
Loggers
Providers
Hooks
Context Providers
Commands
- First Run
- Default Chat
- Tool Call Cycle
- Hook Interception
- Guard Deny Reproposal
- State Machine Workflow
- SM Stage Retry
- Hot Model Switch
- Capability Mismatch Switch
- Session Resume
- Session Resume Drift
- Approval and Auth
- Interaction Timeout
- Headless Run
- Parallel Tool Approvals
- Subagent Delegation
- Scope Layering
- Project First-Run Trust
- Reload Mid-Turn
- Compaction Warning
- MCP Remote Tool Call
- MCP Prompt Consume
- MCP Resource Bind
- MCP Reconnect