summaryrefslogtreecommitdiffhomepage
path: root/packages/kernel/src/contracts
diff options
context:
space:
mode:
authorAdam Malczewski <[email protected]>2026-06-26 22:03:19 +0900
committerAdam Malczewski <[email protected]>2026-06-26 22:23:39 +0900
commit727c98c9dae516a2070eb950410314380a20c974 (patch)
tree52aa1022c54f11770be7e4e2a324f0a8b8b8deec /packages/kernel/src/contracts
parente59dc11f63b1df51142259bb2c406af8c9c8c2bb (diff)
downloaddispatch-727c98c9dae516a2070eb950410314380a20c974.tar.gz
dispatch-727c98c9dae516a2070eb950410314380a20c974.zip
style: switch from tabs to 2-space indentation
Diffstat (limited to 'packages/kernel/src/contracts')
-rw-r--r--packages/kernel/src/contracts/auth.ts28
-rw-r--r--packages/kernel/src/contracts/conversation.ts38
-rw-r--r--packages/kernel/src/contracts/dispatch.ts4
-rw-r--r--packages/kernel/src/contracts/events.ts32
-rw-r--r--packages/kernel/src/contracts/extension.ts338
-rw-r--r--packages/kernel/src/contracts/hooks.ts24
-rw-r--r--packages/kernel/src/contracts/index.ts192
-rw-r--r--packages/kernel/src/contracts/logging.ts164
-rw-r--r--packages/kernel/src/contracts/provider.ts140
-rw-r--r--packages/kernel/src/contracts/runtime.ts282
-rw-r--r--packages/kernel/src/contracts/tool.ts178
11 files changed, 710 insertions, 710 deletions
diff --git a/packages/kernel/src/contracts/auth.ts b/packages/kernel/src/contracts/auth.ts
index 6058156..85963ba 100644
--- a/packages/kernel/src/contracts/auth.ts
+++ b/packages/kernel/src/contracts/auth.ts
@@ -11,9 +11,9 @@
* This is the common case for OpenAI-compatible and most provider extensions.
*/
export interface ApiKeyCredentials {
- readonly type: "api-key";
- readonly apiKey: string;
- readonly baseURL?: string;
+ readonly type: "api-key";
+ readonly apiKey: string;
+ readonly baseURL?: string;
}
/**
@@ -22,9 +22,9 @@ export interface ApiKeyCredentials {
* receives a currently-valid token.
*/
export interface BearerTokenCredentials {
- readonly type: "bearer-token";
- readonly token: string;
- readonly baseURL?: string;
+ readonly type: "bearer-token";
+ readonly token: string;
+ readonly baseURL?: string;
}
/** Union of credential shapes the kernel recognizes. */
@@ -36,13 +36,13 @@ export type Credentials = ApiKeyCredentials | BearerTokenCredentials;
* directly (the concrete vault is a core extension).
*/
export interface AuthContract {
- /** Unique identifier for this auth provider (e.g. "apikey", "claude-oauth"). */
- readonly id: string;
+ /** Unique identifier for this auth provider (e.g. "apikey", "claude-oauth"). */
+ readonly id: string;
- /**
- * Resolve currently-valid credentials. May involve reading from the
- * secret vault (injected via Host API) or performing a token refresh.
- * Returns `null` if credentials are unavailable (e.g. not yet configured).
- */
- readonly resolve: () => Promise<Credentials | null>;
+ /**
+ * Resolve currently-valid credentials. May involve reading from the
+ * secret vault (injected via Host API) or performing a token refresh.
+ * Returns `null` if credentials are unavailable (e.g. not yet configured).
+ */
+ readonly resolve: () => Promise<Credentials | null>;
}
diff --git a/packages/kernel/src/contracts/conversation.ts b/packages/kernel/src/contracts/conversation.ts
index b459532..f074c52 100644
--- a/packages/kernel/src/contracts/conversation.ts
+++ b/packages/kernel/src/contracts/conversation.ts
@@ -6,23 +6,23 @@
*/
export type {
- ChatMessage,
- Chunk,
- CompactionResult,
- ConversationMeta,
- ConversationStatus,
- ErrorChunk,
- Role,
- StepId,
- StepMetrics,
- StoredChunk,
- SystemChunk,
- TextChunk,
- ThinkingChunk,
- ToolCallChunk,
- ToolResultChunk,
- TurnId,
- TurnMetrics,
- Workspace,
- WorkspaceEntry,
+ ChatMessage,
+ Chunk,
+ CompactionResult,
+ ConversationMeta,
+ ConversationStatus,
+ ErrorChunk,
+ Role,
+ StepId,
+ StepMetrics,
+ StoredChunk,
+ SystemChunk,
+ TextChunk,
+ ThinkingChunk,
+ ToolCallChunk,
+ ToolResultChunk,
+ TurnId,
+ TurnMetrics,
+ Workspace,
+ WorkspaceEntry,
} from "@dispatch/wire";
diff --git a/packages/kernel/src/contracts/dispatch.ts b/packages/kernel/src/contracts/dispatch.ts
index c2914cf..318f14a 100644
--- a/packages/kernel/src/contracts/dispatch.ts
+++ b/packages/kernel/src/contracts/dispatch.ts
@@ -26,6 +26,6 @@
* (safe for any tool), yet the first tool starts during generation.
*/
export interface ToolDispatchPolicy {
- readonly maxConcurrent: number;
- readonly eager: boolean;
+ readonly maxConcurrent: number;
+ readonly eager: boolean;
}
diff --git a/packages/kernel/src/contracts/events.ts b/packages/kernel/src/contracts/events.ts
index dca34c2..dfd7456 100644
--- a/packages/kernel/src/contracts/events.ts
+++ b/packages/kernel/src/contracts/events.ts
@@ -6,20 +6,20 @@
*/
export type {
- AgentEvent,
- StatusEvent,
- TurnDoneEvent,
- TurnErrorEvent,
- TurnInputEvent,
- TurnProviderRetryEvent,
- TurnReasoningDeltaEvent,
- TurnSealedEvent,
- TurnStartEvent,
- TurnSteeringEvent,
- TurnStepCompleteEvent,
- TurnTextDeltaEvent,
- TurnToolCallEvent,
- TurnToolOutputEvent,
- TurnToolResultEvent,
- TurnUsageEvent,
+ AgentEvent,
+ StatusEvent,
+ TurnDoneEvent,
+ TurnErrorEvent,
+ TurnInputEvent,
+ TurnProviderRetryEvent,
+ TurnReasoningDeltaEvent,
+ TurnSealedEvent,
+ TurnStartEvent,
+ TurnSteeringEvent,
+ TurnStepCompleteEvent,
+ TurnTextDeltaEvent,
+ TurnToolCallEvent,
+ TurnToolOutputEvent,
+ TurnToolResultEvent,
+ TurnUsageEvent,
} from "@dispatch/wire";
diff --git a/packages/kernel/src/contracts/extension.ts b/packages/kernel/src/contracts/extension.ts
index 4d6cf07..fd1594b 100644
--- a/packages/kernel/src/contracts/extension.ts
+++ b/packages/kernel/src/contracts/extension.ts
@@ -9,11 +9,11 @@
import type { AuthContract } from "./auth.js";
import type {
- EventHandler,
- EventHookDescriptor,
- FilterDescriptor,
- FilterHandler,
- ServiceHandle,
+ EventHandler,
+ EventHookDescriptor,
+ FilterDescriptor,
+ FilterHandler,
+ ServiceHandle,
} from "./hooks.js";
import type { Logger } from "./logging.js";
@@ -32,16 +32,16 @@ export type TrustLevel = "bundled" | "local" | "external";
* discovery, dependency resolution, and the capability gate.
*/
export interface ManifestContributions {
- readonly tools?: readonly string[];
- readonly providers?: readonly string[];
- readonly auth?: readonly string[];
- readonly hooks?: readonly string[];
- readonly routes?: readonly string[];
- readonly commands?: readonly string[];
- readonly services?: readonly string[];
- readonly migrations?: readonly string[];
- readonly scheduledJobs?: readonly string[];
- readonly settings?: readonly string[];
+ readonly tools?: readonly string[];
+ readonly providers?: readonly string[];
+ readonly auth?: readonly string[];
+ readonly hooks?: readonly string[];
+ readonly routes?: readonly string[];
+ readonly commands?: readonly string[];
+ readonly services?: readonly string[];
+ readonly migrations?: readonly string[];
+ readonly scheduledJobs?: readonly string[];
+ readonly settings?: readonly string[];
}
/**
@@ -50,12 +50,12 @@ export interface ManifestContributions {
* declared capability for.
*/
export interface ManifestCapabilities {
- readonly fs?: boolean;
- readonly shell?: boolean;
- readonly network?: boolean;
- readonly secrets?: boolean;
- readonly db?: boolean;
- readonly spawn?: boolean;
+ readonly fs?: boolean;
+ readonly shell?: boolean;
+ readonly network?: boolean;
+ readonly secrets?: boolean;
+ readonly db?: boolean;
+ readonly spawn?: boolean;
}
/**
@@ -64,32 +64,32 @@ export interface ManifestCapabilities {
* compatibility, and enforce the capability gate.
*/
export interface Manifest {
- /** Unique extension identifier (e.g. "tools-fs", "provider-anthropic"). */
- readonly id: string;
+ /** Unique extension identifier (e.g. "tools-fs", "provider-anthropic"). */
+ readonly id: string;
- /** Human-readable display name. */
- readonly name: string;
+ /** Human-readable display name. */
+ readonly name: string;
- /** Extension's own version (semver). */
- readonly version: string;
+ /** Extension's own version (semver). */
+ readonly version: string;
- /** Semver range of kernel API versions this extension is compatible with. */
- readonly apiVersion: string;
+ /** Semver range of kernel API versions this extension is compatible with. */
+ readonly apiVersion: string;
- /** Ids of extensions this one depends on (resolved topologically). */
- readonly dependsOn?: readonly string[];
+ /** Ids of extensions this one depends on (resolved topologically). */
+ readonly dependsOn?: readonly string[];
- /** Activation strategy: "eager" (on boot) or lazy event triggers. */
- readonly activation?: "eager" | string;
+ /** Activation strategy: "eager" (on boot) or lazy event triggers. */
+ readonly activation?: "eager" | string;
- /** What this extension contributes to the system. */
- readonly contributes?: ManifestContributions;
+ /** What this extension contributes to the system. */
+ readonly contributes?: ManifestContributions;
- /** Capabilities this extension requires from the host. */
- readonly capabilities?: ManifestCapabilities;
+ /** Capabilities this extension requires from the host. */
+ readonly capabilities?: ManifestCapabilities;
- /** Trust level — bundled (first-party), local (project), or external. */
- readonly trust: TrustLevel;
+ /** Trust level — bundled (first-party), local (project), or external. */
+ readonly trust: TrustLevel;
}
// --- Storage interface ---
@@ -100,40 +100,40 @@ export interface Manifest {
* only the contract. Supports key-value and simple query operations.
*/
export interface StorageNamespace {
- readonly get: (key: string) => Promise<string | null>;
- readonly set: (key: string, value: string) => Promise<void>;
- readonly delete: (key: string) => Promise<void>;
- readonly has: (key: string) => Promise<boolean>;
- readonly keys: (prefix?: string) => Promise<readonly string[]>;
+ readonly get: (key: string) => Promise<string | null>;
+ readonly set: (key: string, value: string) => Promise<void>;
+ readonly delete: (key: string) => Promise<void>;
+ readonly has: (key: string) => Promise<boolean>;
+ readonly keys: (prefix?: string) => Promise<readonly string[]>;
}
// --- Permission ---
/** The outcome of a permission check. */
export interface PermissionDecision {
- readonly allowed: boolean;
- readonly reason?: string;
+ readonly allowed: boolean;
+ readonly reason?: string;
}
/** A request to check whether an action is permitted. */
export interface PermissionRequest {
- readonly tool: string;
- readonly action: string;
- readonly context?: Readonly<Record<string, unknown>>;
+ readonly tool: string;
+ readonly action: string;
+ readonly context?: Readonly<Record<string, unknown>>;
}
/** Permission gate exposed through the Host API. */
export interface PermissionGate {
- readonly check: (request: PermissionRequest) => Promise<PermissionDecision>;
+ readonly check: (request: PermissionRequest) => Promise<PermissionDecision>;
}
// --- Scheduler ---
/** A scheduled job definition an extension can register with the host. */
export interface ScheduledJob {
- readonly id: string;
- readonly cron: string;
- readonly execute: () => void | Promise<void>;
+ readonly id: string;
+ readonly cron: string;
+ readonly execute: () => void | Promise<void>;
}
// --- Logger is re-exported from logging.ts (structured, correlated) ---
@@ -142,24 +142,24 @@ export interface ScheduledJob {
/** Read-only config access for an extension's own settings namespace. */
export interface ConfigAccess {
- readonly get: <T = unknown>(key: string) => T | undefined;
- readonly getAll: () => Readonly<Record<string, unknown>>;
+ readonly get: <T = unknown>(key: string) => T | undefined;
+ readonly getAll: () => Readonly<Record<string, unknown>>;
}
// --- Secrets ---
/** Capability-gated access to the secret/credential vault. */
export interface SecretsAccess {
- readonly get: (key: string) => Promise<string | null>;
- readonly set: (key: string, value: string) => Promise<void>;
- readonly delete: (key: string) => Promise<void>;
+ readonly get: (key: string) => Promise<string | null>;
+ readonly set: (key: string, value: string) => Promise<void>;
+ readonly delete: (key: string) => Promise<void>;
}
// --- Events emitter ---
/** Outward event emitter available to extensions via the Host API. */
export interface EventsEmitter {
- readonly emit: (event: { readonly type: string; readonly [key: string]: unknown }) => void;
+ readonly emit: (event: { readonly type: string; readonly [key: string]: unknown }) => void;
}
// --- Host API ---
@@ -175,102 +175,102 @@ export interface EventsEmitter {
* module (not the kernel contracts).
*/
export interface HostAPI {
- /** Register a tool with the kernel's tool registry. */
- readonly defineTool: (tool: ToolContract) => void;
-
- /** Register a provider with the kernel's provider registry. */
- readonly defineProvider: (provider: ProviderContract) => void;
-
- /** Register an auth provider with the kernel's auth registry. */
- readonly defineAuth: (auth: AuthContract) => void;
-
- /** Subscribe to an event hook. Handlers are error-isolated per call. */
- readonly on: <TPayload>(
- hook: EventHookDescriptor<TPayload>,
- handler: EventHandler<TPayload>,
- ) => () => void;
-
- /**
- * Emit an event hook: fire-and-forget dispatch to every `on` subscriber,
- * error-isolated per handler (a thrown handler is caught + logged, never
- * breaks the caller). The counterpart of `on`.
- *
- * This lets a core extension that OWNS a lifecycle publish typed events that
- * standard extensions react to — e.g. the session-orchestrator emitting
- * per-turn start/settle events a cache-warming extension subscribes to. The
- * kernel owns the mechanism; the owner declares the typed `EventHookDescriptor`.
- */
- readonly emit: <TPayload>(hook: EventHookDescriptor<TPayload>, payload: TPayload) => void;
-
- /** Add a filter to a filter hook chain. Filters are awaited in-band. */
- readonly addFilter: <TValue>(
- hook: FilterDescriptor<TValue>,
- fn: FilterHandler<TValue>,
- ) => () => void;
-
- /**
- * Run a filter chain: thread `value` through every filter registered for
- * `hook` in priority/registration order and return the final value. The
- * single-value-in/value-out counterpart to `addFilter`. Awaited in-band.
- *
- * Fail-open by default (a thrown filter is logged and the value passes
- * through unchanged); pass `{ failClosed: true }` to make a thrown filter
- * reject. With no registered filters the input value is returned as-is.
- *
- * This is what lets a core extension expose a contribution point (e.g. the
- * session-orchestrator running a per-turn tool/context-assembly chain) that
- * standard extensions plug into via `addFilter` — the kernel owns the
- * mechanism, the owner declares the typed `FilterDescriptor`.
- */
- readonly applyFilters: <TValue>(
- hook: FilterDescriptor<TValue>,
- value: TValue,
- opts?: { readonly failClosed?: boolean },
- ) => Promise<TValue>;
-
- /** Provide an implementation for a typed service handle. */
- readonly provideService: <T>(handle: ServiceHandle<T>, impl: T) => void;
-
- /** Retrieve the implementation for a typed service handle. */
- readonly getService: <T>(handle: ServiceHandle<T>) => T;
-
- /** Get a namespaced storage interface for this extension. */
- readonly storage: (namespace: string) => StorageNamespace;
-
- /** Read-only access to merged config (global → project → extension). */
- readonly config: ConfigAccess;
-
- /** Capability-gated access to the secret/credential vault. */
- readonly secrets: SecretsAccess;
-
- /** Permission gate — check whether an action is allowed. */
- readonly permissions: PermissionGate;
-
- /** Emit outward events (transport pushes these to clients). */
- readonly events: EventsEmitter;
-
- /** Logger — always available, even before other extensions activate. */
- readonly logger: Logger;
-
- /** Read-only view of all registered providers. */
- readonly getProviders: () => ReadonlyMap<string, ProviderContract>;
-
- /** Read-only view of all registered tools. */
- readonly getTools: () => ReadonlyMap<string, ToolContract>;
-
- /** Read-only view of all registered auth providers. */
- readonly getAuthProviders: () => ReadonlyMap<string, AuthContract>;
-
- /** Look up a single auth provider by id. */
- readonly getAuthProvider: (id: string) => AuthContract | undefined;
-
- /** Read-only view of all activated extensions' manifests (what is loaded). */
- readonly getExtensions: () => readonly Manifest[];
-
- /** Register a scheduled job with the host's scheduler. */
- readonly scheduler: {
- readonly register: (job: ScheduledJob) => void;
- };
+ /** Register a tool with the kernel's tool registry. */
+ readonly defineTool: (tool: ToolContract) => void;
+
+ /** Register a provider with the kernel's provider registry. */
+ readonly defineProvider: (provider: ProviderContract) => void;
+
+ /** Register an auth provider with the kernel's auth registry. */
+ readonly defineAuth: (auth: AuthContract) => void;
+
+ /** Subscribe to an event hook. Handlers are error-isolated per call. */
+ readonly on: <TPayload>(
+ hook: EventHookDescriptor<TPayload>,
+ handler: EventHandler<TPayload>,
+ ) => () => void;
+
+ /**
+ * Emit an event hook: fire-and-forget dispatch to every `on` subscriber,
+ * error-isolated per handler (a thrown handler is caught + logged, never
+ * breaks the caller). The counterpart of `on`.
+ *
+ * This lets a core extension that OWNS a lifecycle publish typed events that
+ * standard extensions react to — e.g. the session-orchestrator emitting
+ * per-turn start/settle events a cache-warming extension subscribes to. The
+ * kernel owns the mechanism; the owner declares the typed `EventHookDescriptor`.
+ */
+ readonly emit: <TPayload>(hook: EventHookDescriptor<TPayload>, payload: TPayload) => void;
+
+ /** Add a filter to a filter hook chain. Filters are awaited in-band. */
+ readonly addFilter: <TValue>(
+ hook: FilterDescriptor<TValue>,
+ fn: FilterHandler<TValue>,
+ ) => () => void;
+
+ /**
+ * Run a filter chain: thread `value` through every filter registered for
+ * `hook` in priority/registration order and return the final value. The
+ * single-value-in/value-out counterpart to `addFilter`. Awaited in-band.
+ *
+ * Fail-open by default (a thrown filter is logged and the value passes
+ * through unchanged); pass `{ failClosed: true }` to make a thrown filter
+ * reject. With no registered filters the input value is returned as-is.
+ *
+ * This is what lets a core extension expose a contribution point (e.g. the
+ * session-orchestrator running a per-turn tool/context-assembly chain) that
+ * standard extensions plug into via `addFilter` — the kernel owns the
+ * mechanism, the owner declares the typed `FilterDescriptor`.
+ */
+ readonly applyFilters: <TValue>(
+ hook: FilterDescriptor<TValue>,
+ value: TValue,
+ opts?: { readonly failClosed?: boolean },
+ ) => Promise<TValue>;
+
+ /** Provide an implementation for a typed service handle. */
+ readonly provideService: <T>(handle: ServiceHandle<T>, impl: T) => void;
+
+ /** Retrieve the implementation for a typed service handle. */
+ readonly getService: <T>(handle: ServiceHandle<T>) => T;
+
+ /** Get a namespaced storage interface for this extension. */
+ readonly storage: (namespace: string) => StorageNamespace;
+
+ /** Read-only access to merged config (global → project → extension). */
+ readonly config: ConfigAccess;
+
+ /** Capability-gated access to the secret/credential vault. */
+ readonly secrets: SecretsAccess;
+
+ /** Permission gate — check whether an action is allowed. */
+ readonly permissions: PermissionGate;
+
+ /** Emit outward events (transport pushes these to clients). */
+ readonly events: EventsEmitter;
+
+ /** Logger — always available, even before other extensions activate. */
+ readonly logger: Logger;
+
+ /** Read-only view of all registered providers. */
+ readonly getProviders: () => ReadonlyMap<string, ProviderContract>;
+
+ /** Read-only view of all registered tools. */
+ readonly getTools: () => ReadonlyMap<string, ToolContract>;
+
+ /** Read-only view of all registered auth providers. */
+ readonly getAuthProviders: () => ReadonlyMap<string, AuthContract>;
+
+ /** Look up a single auth provider by id. */
+ readonly getAuthProvider: (id: string) => AuthContract | undefined;
+
+ /** Read-only view of all activated extensions' manifests (what is loaded). */
+ readonly getExtensions: () => readonly Manifest[];
+
+ /** Register a scheduled job with the host's scheduler. */
+ readonly scheduler: {
+ readonly register: (job: ScheduledJob) => void;
+ };
}
// --- Extension lifecycle ---
@@ -281,18 +281,18 @@ export interface HostAPI {
* `deactivate` is optional and called on shutdown or reload.
*/
export interface Extension {
- /** The extension's manifest — its declaration of identity and capabilities. */
- readonly manifest: Manifest;
-
- /**
- * Called by the host to activate the extension. The extension registers
- * its contributions (tools, providers, hooks, services) through the Host API.
- */
- readonly activate: (host: HostAPI) => void | Promise<void>;
-
- /**
- * Optional cleanup called when the extension is deactivated (shutdown,
- * reload, or auto-disable). Should dispose resources the extension owns.
- */
- readonly deactivate?: () => void | Promise<void>;
+ /** The extension's manifest — its declaration of identity and capabilities. */
+ readonly manifest: Manifest;
+
+ /**
+ * Called by the host to activate the extension. The extension registers
+ * its contributions (tools, providers, hooks, services) through the Host API.
+ */
+ readonly activate: (host: HostAPI) => void | Promise<void>;
+
+ /**
+ * Optional cleanup called when the extension is deactivated (shutdown,
+ * reload, or auto-disable). Should dispose resources the extension owns.
+ */
+ readonly deactivate?: () => void | Promise<void>;
}
diff --git a/packages/kernel/src/contracts/hooks.ts b/packages/kernel/src/contracts/hooks.ts
index eb94465..8f2bd1f 100644
--- a/packages/kernel/src/contracts/hooks.ts
+++ b/packages/kernel/src/contracts/hooks.ts
@@ -21,9 +21,9 @@
* (a thrown handler is caught and logged — it never breaks the turn).
*/
export interface EventHookDescriptor<TPayload> {
- readonly kind: "event";
- readonly id: string;
- readonly _payload?: TPayload;
+ readonly kind: "event";
+ readonly id: string;
+ readonly _payload?: TPayload;
}
/**
@@ -35,9 +35,9 @@ export interface EventHookDescriptor<TPayload> {
* the owner may mark a chain fail-closed.
*/
export interface FilterDescriptor<TValue> {
- readonly kind: "filter";
- readonly id: string;
- readonly _value?: TValue;
+ readonly kind: "filter";
+ readonly id: string;
+ readonly _value?: TValue;
}
/** Union of hook descriptor kinds the kernel mechanism supports. */
@@ -49,9 +49,9 @@ export type HookDescriptor<TPayload> = EventHookDescriptor<TPayload> | FilterDes
* "which of N handlers wins?" ambiguity; a service has exactly one provider.
*/
export interface ServiceHandle<T> {
- readonly kind: "service";
- readonly id: string;
- readonly _type?: T;
+ readonly kind: "service";
+ readonly id: string;
+ readonly _type?: T;
}
/**
@@ -61,7 +61,7 @@ export interface ServiceHandle<T> {
* @param id - Namespaced hook id in `owner/name` form (e.g. "kernel/turn.sealed").
*/
export function defineEventHook<TPayload>(id: string): EventHookDescriptor<TPayload> {
- return { kind: "event", id };
+ return { kind: "event", id };
}
/**
@@ -71,7 +71,7 @@ export function defineEventHook<TPayload>(id: string): EventHookDescriptor<TPayl
* @param id - Namespaced filter id in `owner/name` form.
*/
export function defineFilter<TValue>(id: string): FilterDescriptor<TValue> {
- return { kind: "filter", id };
+ return { kind: "filter", id };
}
/**
@@ -82,7 +82,7 @@ export function defineFilter<TValue>(id: string): FilterDescriptor<TValue> {
* @param id - Namespaced service id in `owner/name` form.
*/
export function defineService<T>(id: string): ServiceHandle<T> {
- return { kind: "service", id };
+ return { kind: "service", id };
}
/** Handler function for an event hook subscription. */
diff --git a/packages/kernel/src/contracts/index.ts b/packages/kernel/src/contracts/index.ts
index f3e5bca..09e0a56 100644
--- a/packages/kernel/src/contracts/index.ts
+++ b/packages/kernel/src/contracts/index.ts
@@ -7,118 +7,118 @@
*/
export type {
- ApiKeyCredentials,
- AuthContract,
- BearerTokenCredentials,
- Credentials,
+ ApiKeyCredentials,
+ AuthContract,
+ BearerTokenCredentials,
+ Credentials,
} from "./auth.js";
export type {
- ChatMessage,
- Chunk,
- CompactionResult,
- ConversationMeta,
- ConversationStatus,
- ErrorChunk,
- Role,
- StepId,
- StepMetrics,
- StoredChunk,
- SystemChunk,
- TextChunk,
- ThinkingChunk,
- ToolCallChunk,
- ToolResultChunk,
- TurnId,
- TurnMetrics,
- Workspace,
- WorkspaceEntry,
+ ChatMessage,
+ Chunk,
+ CompactionResult,
+ ConversationMeta,
+ ConversationStatus,
+ ErrorChunk,
+ Role,
+ StepId,
+ StepMetrics,
+ StoredChunk,
+ SystemChunk,
+ TextChunk,
+ ThinkingChunk,
+ ToolCallChunk,
+ ToolResultChunk,
+ TurnId,
+ TurnMetrics,
+ Workspace,
+ WorkspaceEntry,
} from "./conversation.js";
export type { ToolDispatchPolicy } from "./dispatch.js";
export type {
- AgentEvent,
- StatusEvent,
- TurnDoneEvent,
- TurnErrorEvent,
- TurnInputEvent,
- TurnProviderRetryEvent,
- TurnReasoningDeltaEvent,
- TurnSealedEvent,
- TurnStartEvent,
- TurnSteeringEvent,
- TurnStepCompleteEvent,
- TurnTextDeltaEvent,
- TurnToolCallEvent,
- TurnToolOutputEvent,
- TurnToolResultEvent,
- TurnUsageEvent,
+ AgentEvent,
+ StatusEvent,
+ TurnDoneEvent,
+ TurnErrorEvent,
+ TurnInputEvent,
+ TurnProviderRetryEvent,
+ TurnReasoningDeltaEvent,
+ TurnSealedEvent,
+ TurnStartEvent,
+ TurnSteeringEvent,
+ TurnStepCompleteEvent,
+ TurnTextDeltaEvent,
+ TurnToolCallEvent,
+ TurnToolOutputEvent,
+ TurnToolResultEvent,
+ TurnUsageEvent,
} from "./events.js";
export type {
- ConfigAccess,
- EventsEmitter,
- Extension,
- HostAPI,
- Manifest,
- ManifestCapabilities,
- ManifestContributions,
- PermissionDecision,
- PermissionGate,
- PermissionRequest,
- ScheduledJob,
- SecretsAccess,
- StorageNamespace,
- TrustLevel,
+ ConfigAccess,
+ EventsEmitter,
+ Extension,
+ HostAPI,
+ Manifest,
+ ManifestCapabilities,
+ ManifestContributions,
+ PermissionDecision,
+ PermissionGate,
+ PermissionRequest,
+ ScheduledJob,
+ SecretsAccess,
+ StorageNamespace,
+ TrustLevel,
} from "./extension.js";
export type {
- EventHandler,
- EventHookDescriptor,
- FilterDescriptor,
- FilterHandler,
- HookDescriptor,
- ServiceHandle,
+ EventHandler,
+ EventHookDescriptor,
+ FilterDescriptor,
+ FilterHandler,
+ HookDescriptor,
+ ServiceHandle,
} from "./hooks.js";
export { defineEventHook, defineFilter, defineService } from "./hooks.js";
export type {
- Attributes,
- ErrorAttributes,
- Level,
- LogContext,
- LogDeps,
- Logger,
- LogLineRecord,
- LogRecord,
- LogSink,
- Span,
- SpanCloseRecord,
- SpanLink,
- SpanOpenRecord,
- SpanStatus,
+ Attributes,
+ ErrorAttributes,
+ Level,
+ LogContext,
+ LogDeps,
+ Logger,
+ LogLineRecord,
+ LogRecord,
+ LogSink,
+ Span,
+ SpanCloseRecord,
+ SpanLink,
+ SpanOpenRecord,
+ SpanStatus,
} from "./logging.js";
export type {
- FinishEvent,
- ModelInfo,
- ProviderContract,
- ProviderErrorEvent,
- ProviderEvent,
- ProviderStreamOptions,
- ProviderToolCallEvent,
- ReasoningDeltaEvent,
- ReasoningEffort,
- TextDeltaEvent,
- Usage,
- UsageEvent,
+ FinishEvent,
+ ModelInfo,
+ ProviderContract,
+ ProviderErrorEvent,
+ ProviderEvent,
+ ProviderStreamOptions,
+ ProviderToolCallEvent,
+ ReasoningDeltaEvent,
+ ReasoningEffort,
+ TextDeltaEvent,
+ Usage,
+ UsageEvent,
} from "./provider.js";
export type {
- EventEmitter,
- FinishReason,
- RetryStrategy,
- RunTurnInput,
- RunTurnResult,
+ EventEmitter,
+ FinishReason,
+ RetryStrategy,
+ RunTurnInput,
+ RunTurnResult,
} from "./runtime.js";
export type {
- JsonSchemaProperty,
- ToolCall,
- ToolContract,
- ToolExecuteContext,
- ToolParameterSchema,
- ToolResult,
+ JsonSchemaProperty,
+ ToolCall,
+ ToolContract,
+ ToolExecuteContext,
+ ToolParameterSchema,
+ ToolResult,
} from "./tool.js";
diff --git a/packages/kernel/src/contracts/logging.ts b/packages/kernel/src/contracts/logging.ts
index a8bab7c..d121777 100644
--- a/packages/kernel/src/contracts/logging.ts
+++ b/packages/kernel/src/contracts/logging.ts
@@ -27,12 +27,12 @@ export type Attributes = Readonly<Record<string, string | number | boolean | nul
/** Correlation context carried on every log record and span. */
export interface LogContext {
- /** Auto-stamped by host from manifest.id (D6) — never caller-supplied. */
- readonly extensionId: string;
- readonly conversationId?: string;
- readonly turnId?: string;
- readonly spanId?: string;
- readonly parentSpanId?: string;
+ /** Auto-stamped by host from manifest.id (D6) — never caller-supplied. */
+ readonly extensionId: string;
+ readonly conversationId?: string;
+ readonly turnId?: string;
+ readonly spanId?: string;
+ readonly parentSpanId?: string;
}
// --- Span ---
@@ -43,27 +43,27 @@ export interface LogContext {
* crashed turn is reconstructable from the journal (D3).
*/
export interface Span {
- readonly id: string;
- /** Pre-bound Logger scoped to this span's correlation. */
- readonly log: Logger;
- /** Add or overwrite attributes on this span. */
- readonly setAttributes: (attrs: Attributes) => void;
- /** Record a causal link to another span (D4 cross-feature causality). */
- readonly addLink: (
- target: { readonly spanId: string; readonly turnId?: string },
- reason?: string,
- ) => void;
- /** Open a child span nested under this one. */
- readonly child: (name: string, attrs?: Attributes, body?: string) => Span;
- /**
- * Close this span. Records duration + status. Optionally records an
- * error, additional attributes, and/or a body payload.
- */
- readonly end: (outcome?: {
- readonly err?: unknown;
- readonly attrs?: Attributes;
- readonly body?: string;
- }) => void;
+ readonly id: string;
+ /** Pre-bound Logger scoped to this span's correlation. */
+ readonly log: Logger;
+ /** Add or overwrite attributes on this span. */
+ readonly setAttributes: (attrs: Attributes) => void;
+ /** Record a causal link to another span (D4 cross-feature causality). */
+ readonly addLink: (
+ target: { readonly spanId: string; readonly turnId?: string },
+ reason?: string,
+ ) => void;
+ /** Open a child span nested under this one. */
+ readonly child: (name: string, attrs?: Attributes, body?: string) => Span;
+ /**
+ * Close this span. Records duration + status. Optionally records an
+ * error, additional attributes, and/or a body payload.
+ */
+ readonly end: (outcome?: {
+ readonly err?: unknown;
+ readonly attrs?: Attributes;
+ readonly body?: string;
+ }) => void;
}
// --- Logger ---
@@ -75,17 +75,17 @@ export interface Span {
* `info("msg")` must still compile — attrs is optional (backward compat).
*/
export interface Logger {
- readonly debug: (msg: string, attrs?: Attributes) => void;
- readonly info: (msg: string, attrs?: Attributes) => void;
- readonly warn: (msg: string, attrs?: Attributes) => void;
- readonly error: (msg: string, attrs?: ErrorAttributes) => void;
- /**
- * Create a child logger with additional correlation context.
- * Explicit values passed down (P3 — no ambient state).
- */
- readonly child: (ctx: Partial<LogContext> & { readonly attrs?: Attributes }) => Logger;
- /** Open a new span. Emits a `span-open` record immediately (D3). */
- readonly span: (name: string, attrs?: Attributes, body?: string) => Span;
+ readonly debug: (msg: string, attrs?: Attributes) => void;
+ readonly info: (msg: string, attrs?: Attributes) => void;
+ readonly warn: (msg: string, attrs?: Attributes) => void;
+ readonly error: (msg: string, attrs?: ErrorAttributes) => void;
+ /**
+ * Create a child logger with additional correlation context.
+ * Explicit values passed down (P3 — no ambient state).
+ */
+ readonly child: (ctx: Partial<LogContext> & { readonly attrs?: Attributes }) => Logger;
+ /** Open a new span. Emits a `span-open` record immediately (D3). */
+ readonly span: (name: string, attrs?: Attributes, body?: string) => Span;
}
/**
@@ -94,8 +94,8 @@ export interface Logger {
* pass `error("msg", { err })` directly.
*/
export interface ErrorAttributes {
- readonly err?: unknown;
- readonly [key: string]: unknown;
+ readonly err?: unknown;
+ readonly [key: string]: unknown;
}
// --- LogRecord (discriminated union) ---
@@ -109,9 +109,9 @@ export type SpanStatus = "ok" | "error";
* A link to another span, recorded at a handoff moment (D4).
*/
export interface SpanLink {
- readonly spanId: string;
- readonly turnId?: string;
- readonly reason?: string;
+ readonly spanId: string;
+ readonly turnId?: string;
+ readonly reason?: string;
}
/**
@@ -126,50 +126,50 @@ export type LogRecord = LogLineRecord | SpanOpenRecord | SpanCloseRecord;
/** A structured log line (debug/info/warn/error). */
export interface LogLineRecord {
- readonly kind: "log";
- readonly level: Level;
- readonly msg: string;
- readonly timestamp: number;
- readonly extensionId: string;
- readonly conversationId?: string;
- readonly turnId?: string;
- readonly spanId?: string;
- readonly parentSpanId?: string;
- readonly attributes?: Attributes;
- /** Optional large verbatim payload (store-fat, serve-thin). */
- readonly body?: string;
+ readonly kind: "log";
+ readonly level: Level;
+ readonly msg: string;
+ readonly timestamp: number;
+ readonly extensionId: string;
+ readonly conversationId?: string;
+ readonly turnId?: string;
+ readonly spanId?: string;
+ readonly parentSpanId?: string;
+ readonly attributes?: Attributes;
+ /** Optional large verbatim payload (store-fat, serve-thin). */
+ readonly body?: string;
}
/** Emitted when a span is opened (at `logger.span(name)`). */
export interface SpanOpenRecord {
- readonly kind: "span-open";
- readonly spanId: string;
- readonly name: string;
- readonly timestamp: number;
- readonly extensionId: string;
- readonly conversationId?: string;
- readonly turnId?: string;
- readonly parentSpanId?: string;
- readonly attributes?: Attributes;
- readonly links?: readonly SpanLink[];
- readonly body?: string;
+ readonly kind: "span-open";
+ readonly spanId: string;
+ readonly name: string;
+ readonly timestamp: number;
+ readonly extensionId: string;
+ readonly conversationId?: string;
+ readonly turnId?: string;
+ readonly parentSpanId?: string;
+ readonly attributes?: Attributes;
+ readonly links?: readonly SpanLink[];
+ readonly body?: string;
}
/** Emitted when a span is closed (at `span.end()`). Carries duration + status. */
export interface SpanCloseRecord {
- readonly kind: "span-close";
- readonly spanId: string;
- readonly name: string;
- readonly timestamp: number;
- readonly durationMs: number;
- readonly status: SpanStatus;
- readonly extensionId: string;
- readonly conversationId?: string;
- readonly turnId?: string;
- readonly parentSpanId?: string;
- readonly attributes?: Attributes;
- readonly links?: readonly SpanLink[];
- readonly body?: string;
+ readonly kind: "span-close";
+ readonly spanId: string;
+ readonly name: string;
+ readonly timestamp: number;
+ readonly durationMs: number;
+ readonly status: SpanStatus;
+ readonly extensionId: string;
+ readonly conversationId?: string;
+ readonly turnId?: string;
+ readonly parentSpanId?: string;
+ readonly attributes?: Attributes;
+ readonly links?: readonly SpanLink[];
+ readonly body?: string;
}
// --- LogSink ---
@@ -179,13 +179,13 @@ export interface SpanCloseRecord {
* a concrete implementation. Kernel never lets sink errors escape (D7).
*/
export interface LogSink {
- readonly emit: (record: LogRecord) => void;
+ readonly emit: (record: LogRecord) => void;
}
// --- Deterministic helpers (injected for testability) ---
/** Clock + id generator injected into the logger factory. */
export interface LogDeps {
- readonly now: () => number;
- readonly newId: () => string;
+ readonly now: () => number;
+ readonly newId: () => string;
}
diff --git a/packages/kernel/src/contracts/provider.ts b/packages/kernel/src/contracts/provider.ts
index 52d853b..b6dc8ca 100644
--- a/packages/kernel/src/contracts/provider.ts
+++ b/packages/kernel/src/contracts/provider.ts
@@ -19,23 +19,23 @@ export type { ReasoningEffort, Usage } from "@dispatch/wire";
* Discriminated by `type`.
*/
export type ProviderEvent =
- | TextDeltaEvent
- | ReasoningDeltaEvent
- | ProviderToolCallEvent
- | UsageEvent
- | FinishEvent
- | ProviderErrorEvent;
+ | TextDeltaEvent
+ | ReasoningDeltaEvent
+ | ProviderToolCallEvent
+ | UsageEvent
+ | FinishEvent
+ | ProviderErrorEvent;
/** Incremental text content from the model. */
export interface TextDeltaEvent {
- readonly type: "text-delta";
- readonly delta: string;
+ readonly type: "text-delta";
+ readonly delta: string;
}
/** Incremental reasoning / thinking content from the model. */
export interface ReasoningDeltaEvent {
- readonly type: "reasoning-delta";
- readonly delta: string;
+ readonly type: "reasoning-delta";
+ readonly delta: string;
}
/**
@@ -43,16 +43,16 @@ export interface ReasoningDeltaEvent {
* dispatch to the matching `ToolContract`.
*/
export interface ProviderToolCallEvent {
- readonly type: "tool-call";
- readonly toolCallId: string;
- readonly toolName: string;
- readonly input: unknown;
+ readonly type: "tool-call";
+ readonly toolCallId: string;
+ readonly toolName: string;
+ readonly input: unknown;
}
/** Token usage report, typically emitted at step end. */
export interface UsageEvent {
- readonly type: "usage";
- readonly usage: Usage;
+ readonly type: "usage";
+ readonly usage: Usage;
}
/**
@@ -60,16 +60,16 @@ export interface UsageEvent {
* generating (e.g. "stop", "tool-calls", "length", "content-filter").
*/
export interface FinishEvent {
- readonly type: "finish";
- readonly reason: string;
+ readonly type: "finish";
+ readonly reason: string;
}
/** An error from the provider (network, rate-limit, model error, etc.). */
export interface ProviderErrorEvent {
- readonly type: "error";
- readonly message: string;
- readonly code?: string;
- readonly retryable?: boolean;
+ readonly type: "error";
+ readonly message: string;
+ readonly code?: string;
+ readonly retryable?: boolean;
}
/**
@@ -77,30 +77,30 @@ export interface ProviderErrorEvent {
* Kept minimal — providers may ignore fields they don't support.
*/
export interface ProviderStreamOptions {
- /** Model identifier to use. */
- readonly model?: string;
- /** Sampling temperature override. */
- readonly temperature?: number;
- /** Maximum output tokens override. */
- readonly maxTokens?: number;
- /** System prompt to prepend. */
- readonly systemPrompt?: string;
- /**
- * Reasoning-effort level for this request (already RESOLVED by the caller —
- * the session-orchestrator applies the request → conversation → `"high"`
- * default chain, so a provider receiving `undefined` may treat it as "no
- * preference"). The provider maps the level to its native thinking knob in
- * its own code; providers without such a knob ignore it.
- */
- readonly reasoningEffort?: ReasoningEffort;
- /**
- * Correlated logger for this turn's step (Phase A logging ABI). When present,
- * the provider should open a child `provider.request` span and capture the
- * verbatim post-transform request + raw response/error there, self-redacting
- * secrets in its own code. Optional so non-instrumented callers/tests still
- * compile (the provider falls back to no capture).
- */
- readonly logger?: Logger;
+ /** Model identifier to use. */
+ readonly model?: string;
+ /** Sampling temperature override. */
+ readonly temperature?: number;
+ /** Maximum output tokens override. */
+ readonly maxTokens?: number;
+ /** System prompt to prepend. */
+ readonly systemPrompt?: string;
+ /**
+ * Reasoning-effort level for this request (already RESOLVED by the caller —
+ * the session-orchestrator applies the request → conversation → `"high"`
+ * default chain, so a provider receiving `undefined` may treat it as "no
+ * preference"). The provider maps the level to its native thinking knob in
+ * its own code; providers without such a knob ignore it.
+ */
+ readonly reasoningEffort?: ReasoningEffort;
+ /**
+ * Correlated logger for this turn's step (Phase A logging ABI). When present,
+ * the provider should open a child `provider.request` span and capture the
+ * verbatim post-transform request + raw response/error there, self-redacting
+ * secrets in its own code. Optional so non-instrumented callers/tests still
+ * compile (the provider falls back to no capture).
+ */
+ readonly logger?: Logger;
}
/**
@@ -110,10 +110,10 @@ export interface ProviderStreamOptions {
* is the wire model identifier; `displayName` is an optional human label.
*/
export interface ModelInfo {
- readonly id: string;
- readonly displayName?: string;
- /** The model's max context window in tokens (e.g. 200000). Optional — providers that don't report it leave it undefined. */
- readonly contextWindow?: number;
+ readonly id: string;
+ readonly displayName?: string;
+ /** The model's max context window in tokens (e.g. 200000). Optional — providers that don't report it leave it undefined. */
+ readonly contextWindow?: number;
}
/**
@@ -122,26 +122,26 @@ export interface ModelInfo {
* concrete LLM API is behind it.
*/
export interface ProviderContract {
- /** Unique identifier for this provider (e.g. "anthropic", "openai-compat"). */
- readonly id: string;
+ /** Unique identifier for this provider (e.g. "anthropic", "openai-compat"). */
+ readonly id: string;
- /**
- * Stream a response for the given messages and available tools.
- * The provider yields `ProviderEvent`s incrementally; the kernel drives
- * tool dispatch and chunk assembly from them.
- */
- readonly stream: (
- messages: readonly ChatMessage[],
- tools: readonly ToolContract[],
- opts?: ProviderStreamOptions,
- ) => AsyncIterable<ProviderEvent>;
+ /**
+ * Stream a response for the given messages and available tools.
+ * The provider yields `ProviderEvent`s incrementally; the kernel drives
+ * tool dispatch and chunk assembly from them.
+ */
+ readonly stream: (
+ messages: readonly ChatMessage[],
+ tools: readonly ToolContract[],
+ opts?: ProviderStreamOptions,
+ ) => AsyncIterable<ProviderEvent>;
- /**
- * Enumerate the models this provider can serve, each in its own way (e.g. an
- * OpenAI-compatible provider GETs `/v1/models`). Optional: a provider that
- * cannot (or chooses not to) enumerate omits it, and a catalog simply lists
- * none for it. A future multi-credential design may pass per-credential
- * credentials in; today the provider uses the key it resolved at activate.
- */
- readonly listModels?: () => Promise<readonly ModelInfo[]>;
+ /**
+ * Enumerate the models this provider can serve, each in its own way (e.g. an
+ * OpenAI-compatible provider GETs `/v1/models`). Optional: a provider that
+ * cannot (or chooses not to) enumerate omits it, and a catalog simply lists
+ * none for it. A future multi-credential design may pass per-credential
+ * credentials in; today the provider uses the key it resolved at activate.
+ */
+ readonly listModels?: () => Promise<readonly ModelInfo[]>;
}
diff --git a/packages/kernel/src/contracts/runtime.ts b/packages/kernel/src/contracts/runtime.ts
index dc74c84..71d2211 100644
--- a/packages/kernel/src/contracts/runtime.ts
+++ b/packages/kernel/src/contracts/runtime.ts
@@ -26,14 +26,14 @@ export type EventEmitter = (event: AgentEvent) => void;
* passed through verbatim without losing autocomplete on the known values.
*/
export type FinishReason =
- | "stop"
- | "tool-calls"
- | "length"
- | "content-filter"
- | "max-steps"
- | "error"
- | "aborted"
- | (string & {});
+ | "stop"
+ | "tool-calls"
+ | "length"
+ | "content-filter"
+ | "max-steps"
+ | "error"
+ | "aborted"
+ | (string & {});
/**
* Input to `runTurn` — everything the kernel needs to execute one turn.
@@ -41,121 +41,121 @@ export type FinishReason =
* the kernel never reads config or resolves providers/tools itself.
*/
export interface RunTurnInput {
- /** The resolved provider to stream from. */
- readonly provider: ProviderContract;
-
- /** The conversation history (including system prompt as first message). */
- readonly messages: readonly ChatMessage[];
-
- /** The tool set available for this turn (may be empty). */
- readonly tools: readonly ToolContract[];
-
- /** How to dispatch tool calls within each step. */
- readonly dispatch: ToolDispatchPolicy;
-
- /** The emitter the kernel calls for each outward event. */
- readonly emit: EventEmitter;
-
- /**
- * Identifiers used to attribute every emitted `AgentEvent`. The kernel does
- * not generate these — the session-orchestrator owns turn/conversation identity
- * and passes them in, so events are traceable to their conversation.
- */
- readonly conversationId: string;
- readonly turnId: string;
-
- /**
- * Optional per-turn provider options (model, temperature, maxTokens,
- * systemPrompt). The orchestrator resolves these; the kernel forwards them
- * verbatim to `provider.stream` and never interprets them. A provider may
- * also be pre-configured at construction and ignore these.
- */
- readonly providerOpts?: ProviderStreamOptions;
-
- /** Cancellation signal for the entire turn. */
- readonly signal?: AbortSignal;
-
- /**
- * Working directory for this turn's tool execution. The kernel does NOT
- * interpret it — it forwards the value verbatim to each `ToolExecuteContext.cwd`
- * so tools resolve/contain paths against it. It never enters the model prompt,
- * so it does not affect prompt caching. When omitted, tools fall back to their
- * own configured/default workdir.
- */
- readonly cwd?: string;
-
- /**
- * The computer to execute this turn's tools on (SSH support). Omitted/undefined
- * = LOCAL (today's behavior). When set, it is an SSH config alias; the kernel
- * does NOT interpret it — it forwards the value verbatim to each
- * `ToolExecuteContext.computerId`, exactly like `cwd`. It never enters the
- * model prompt, so it does not affect prompt caching. Tools resolve their
- * execution backend (local vs. remote) from this; see
- * `notes/ssh-support-plan.md`.
- */
- readonly computerId?: string;
-
- /**
- * Optional logger for structured span instrumentation. The runtime opens
- * turn/step/tool-call spans using this logger. If omitted, no spans are
- * emitted (backward-compatible with callers that don't yet pass a logger).
- */
- readonly logger?: Logger;
-
- /**
- * Optional monotonic-ish clock (milliseconds) for emitting wall-clock timing
- * on outward events: per-step `step-complete` (ttft/decode/genTotal), tool
- * execution `durationMs` on `tool-result`, and turn `durationMs` on `done`.
- * Injected (not ambient) so the runtime stays pure and deterministic in tests.
- * If omitted, the runtime emits no such timing (the optional fields stay
- * absent) — backward-compatible with callers that don't provide a clock.
- */
- readonly now?: () => number;
-
- /**
- * Optional. Called by the runtime at the tool-result boundary — after a
- * step whose tool calls have all executed, before the next step begins —
- * to drain messages to inject alongside the tool results. Whatever it
- * returns is appended as user-role messages to the next step's input, so
- * a caller can inject mid-turn guidance the model sees with the tool
- * results. When omitted or returning an empty array, no injection happens
- * (the runtime is unchanged).
- *
- * Injected (not ambient) so the kernel stays pure: it owns no queue and
- * names no feature — it just calls the callback and appends what it gets.
- * Only invoked when a step PRODUCED tool calls (the tool-result boundary);
- * a step that ends without tool calls does not drain (the caller decides
- * what to do with any pending messages after the turn ends).
- */
- readonly drainSteering?: () => readonly ChatMessage[];
-
- /**
- * Optional. Called by the runtime after each step's messages are finalized
- * (the assistant message + tool-result messages are built). The caller can
- * use this to persist step messages incrementally — assigning seq numbers
- * during generation so consumers can `GET /conversations/:id?sinceSeq=N`
- * mid-turn. When omitted, the caller must persist all messages at turn end
- * (via `RunTurnResult.messages`). The messages passed to this callback are
- * the SAME objects in `RunTurnResult.messages` — the caller must NOT
- * double-persist them.
- */
- readonly onStepComplete?: (messages: readonly ChatMessage[]) => Promise<void> | void;
-
- /**
- * Optional injected retry strategy for retryable provider errors (e.g. HTTP
- * 429 / 5xx "overloaded"). When omitted, a retryable error ends the step
- * exactly as before (backward-compatible). When provided, the runtime wraps
- * `provider.stream()` consumption in a retry loop: on a retryable error
- * (an emitted `error` ProviderEvent with `retryable === true`, OR a thrown
- * error) — ONLY when no content was emitted yet this step (the safety
- * invariant — never duplicate partial output) — it asks `retry.delayFor`
- * for a delay, emits a transient `provider-retry` AgentEvent, sleeps via the
- * injected `retry.sleep` (abortable), and re-calls `provider.stream()`.
- *
- * Injected (not ambient): the kernel imports no timer and owns no schedule.
- * Mirrors the `now`/`logger` injection pattern — optional + backward-compatible.
- */
- readonly retry?: RetryStrategy;
+ /** The resolved provider to stream from. */
+ readonly provider: ProviderContract;
+
+ /** The conversation history (including system prompt as first message). */
+ readonly messages: readonly ChatMessage[];
+
+ /** The tool set available for this turn (may be empty). */
+ readonly tools: readonly ToolContract[];
+
+ /** How to dispatch tool calls within each step. */
+ readonly dispatch: ToolDispatchPolicy;
+
+ /** The emitter the kernel calls for each outward event. */
+ readonly emit: EventEmitter;
+
+ /**
+ * Identifiers used to attribute every emitted `AgentEvent`. The kernel does
+ * not generate these — the session-orchestrator owns turn/conversation identity
+ * and passes them in, so events are traceable to their conversation.
+ */
+ readonly conversationId: string;
+ readonly turnId: string;
+
+ /**
+ * Optional per-turn provider options (model, temperature, maxTokens,
+ * systemPrompt). The orchestrator resolves these; the kernel forwards them
+ * verbatim to `provider.stream` and never interprets them. A provider may
+ * also be pre-configured at construction and ignore these.
+ */
+ readonly providerOpts?: ProviderStreamOptions;
+
+ /** Cancellation signal for the entire turn. */
+ readonly signal?: AbortSignal;
+
+ /**
+ * Working directory for this turn's tool execution. The kernel does NOT
+ * interpret it — it forwards the value verbatim to each `ToolExecuteContext.cwd`
+ * so tools resolve/contain paths against it. It never enters the model prompt,
+ * so it does not affect prompt caching. When omitted, tools fall back to their
+ * own configured/default workdir.
+ */
+ readonly cwd?: string;
+
+ /**
+ * The computer to execute this turn's tools on (SSH support). Omitted/undefined
+ * = LOCAL (today's behavior). When set, it is an SSH config alias; the kernel
+ * does NOT interpret it — it forwards the value verbatim to each
+ * `ToolExecuteContext.computerId`, exactly like `cwd`. It never enters the
+ * model prompt, so it does not affect prompt caching. Tools resolve their
+ * execution backend (local vs. remote) from this; see
+ * `notes/ssh-support-plan.md`.
+ */
+ readonly computerId?: string;
+
+ /**
+ * Optional logger for structured span instrumentation. The runtime opens
+ * turn/step/tool-call spans using this logger. If omitted, no spans are
+ * emitted (backward-compatible with callers that don't yet pass a logger).
+ */
+ readonly logger?: Logger;
+
+ /**
+ * Optional monotonic-ish clock (milliseconds) for emitting wall-clock timing
+ * on outward events: per-step `step-complete` (ttft/decode/genTotal), tool
+ * execution `durationMs` on `tool-result`, and turn `durationMs` on `done`.
+ * Injected (not ambient) so the runtime stays pure and deterministic in tests.
+ * If omitted, the runtime emits no such timing (the optional fields stay
+ * absent) — backward-compatible with callers that don't provide a clock.
+ */
+ readonly now?: () => number;
+
+ /**
+ * Optional. Called by the runtime at the tool-result boundary — after a
+ * step whose tool calls have all executed, before the next step begins —
+ * to drain messages to inject alongside the tool results. Whatever it
+ * returns is appended as user-role messages to the next step's input, so
+ * a caller can inject mid-turn guidance the model sees with the tool
+ * results. When omitted or returning an empty array, no injection happens
+ * (the runtime is unchanged).
+ *
+ * Injected (not ambient) so the kernel stays pure: it owns no queue and
+ * names no feature — it just calls the callback and appends what it gets.
+ * Only invoked when a step PRODUCED tool calls (the tool-result boundary);
+ * a step that ends without tool calls does not drain (the caller decides
+ * what to do with any pending messages after the turn ends).
+ */
+ readonly drainSteering?: () => readonly ChatMessage[];
+
+ /**
+ * Optional. Called by the runtime after each step's messages are finalized
+ * (the assistant message + tool-result messages are built). The caller can
+ * use this to persist step messages incrementally — assigning seq numbers
+ * during generation so consumers can `GET /conversations/:id?sinceSeq=N`
+ * mid-turn. When omitted, the caller must persist all messages at turn end
+ * (via `RunTurnResult.messages`). The messages passed to this callback are
+ * the SAME objects in `RunTurnResult.messages` — the caller must NOT
+ * double-persist them.
+ */
+ readonly onStepComplete?: (messages: readonly ChatMessage[]) => Promise<void> | void;
+
+ /**
+ * Optional injected retry strategy for retryable provider errors (e.g. HTTP
+ * 429 / 5xx "overloaded"). When omitted, a retryable error ends the step
+ * exactly as before (backward-compatible). When provided, the runtime wraps
+ * `provider.stream()` consumption in a retry loop: on a retryable error
+ * (an emitted `error` ProviderEvent with `retryable === true`, OR a thrown
+ * error) — ONLY when no content was emitted yet this step (the safety
+ * invariant — never duplicate partial output) — it asks `retry.delayFor`
+ * for a delay, emits a transient `provider-retry` AgentEvent, sleeps via the
+ * injected `retry.sleep` (abortable), and re-calls `provider.stream()`.
+ *
+ * Injected (not ambient): the kernel imports no timer and owns no schedule.
+ * Mirrors the `now`/`logger` injection pattern — optional + backward-compatible.
+ */
+ readonly retry?: RetryStrategy;
}
/**
@@ -163,14 +163,14 @@ export interface RunTurnInput {
* persist the new messages and report usage.
*/
export interface RunTurnResult {
- /** The assistant messages produced by this turn (appended to history). */
- readonly messages: readonly ChatMessage[];
+ /** The assistant messages produced by this turn (appended to history). */
+ readonly messages: readonly ChatMessage[];
- /** Aggregated token usage across all steps in the turn. */
- readonly usage: Usage;
+ /** Aggregated token usage across all steps in the turn. */
+ readonly usage: Usage;
- /** Why the turn ended. */
- readonly finishReason: FinishReason;
+ /** Why the turn ended. */
+ readonly finishReason: FinishReason;
}
/**
@@ -187,16 +187,16 @@ export interface RunTurnResult {
* the step exactly as before).
*/
export interface RetryStrategy {
- /**
- * Pure, deterministic decision: given the 0-based attempt index, return the
- * delay in ms to sleep before the next retry, or `undefined` to stop (budget
- * exhausted). No I/O, no clock — fully testable.
- */
- readonly delayFor: (attempt: number) => number | undefined;
- /**
- * Injected effect: actually sleep for the given ms. Must honor the abort
- * signal — reject when aborted so the turn seals `aborted`. The kernel
- * imports no timer; the shell provides a `setTimeout`-based implementation.
- */
- readonly sleep: (ms: number, signal: AbortSignal) => Promise<void>;
+ /**
+ * Pure, deterministic decision: given the 0-based attempt index, return the
+ * delay in ms to sleep before the next retry, or `undefined` to stop (budget
+ * exhausted). No I/O, no clock — fully testable.
+ */
+ readonly delayFor: (attempt: number) => number | undefined;
+ /**
+ * Injected effect: actually sleep for the given ms. Must honor the abort
+ * signal — reject when aborted so the turn seals `aborted`. The kernel
+ * imports no timer; the shell provides a `setTimeout`-based implementation.
+ */
+ readonly sleep: (ms: number, signal: AbortSignal) => Promise<void>;
}
diff --git a/packages/kernel/src/contracts/tool.ts b/packages/kernel/src/contracts/tool.ts
index 589fbd0..897b86e 100644
--- a/packages/kernel/src/contracts/tool.ts
+++ b/packages/kernel/src/contracts/tool.ts
@@ -16,22 +16,22 @@ import type { Logger } from "./logging.js";
* Using a structural type (not a library) keeps the kernel dependency-free.
*/
export interface ToolParameterSchema {
- readonly type: "object";
- readonly properties?: Readonly<Record<string, JsonSchemaProperty>>;
- readonly required?: readonly string[];
- readonly additionalProperties?: boolean;
- readonly description?: string;
+ readonly type: "object";
+ readonly properties?: Readonly<Record<string, JsonSchemaProperty>>;
+ readonly required?: readonly string[];
+ readonly additionalProperties?: boolean;
+ readonly description?: string;
}
/** A single property within a tool's parameter schema. */
export interface JsonSchemaProperty {
- readonly type?: string;
- readonly description?: string;
- readonly enum?: readonly string[];
- readonly items?: JsonSchemaProperty;
- readonly properties?: Readonly<Record<string, JsonSchemaProperty>>;
- readonly required?: readonly string[];
- readonly default?: unknown;
+ readonly type?: string;
+ readonly description?: string;
+ readonly enum?: readonly string[];
+ readonly items?: JsonSchemaProperty;
+ readonly properties?: Readonly<Record<string, JsonSchemaProperty>>;
+ readonly required?: readonly string[];
+ readonly default?: unknown;
}
/**
@@ -40,56 +40,56 @@ export interface JsonSchemaProperty {
* concurrent tool output is never interleaved ambiguously.
*/
export interface ToolExecuteContext {
- /** Unique id of the tool-call this execution serves. */
- readonly toolCallId: string;
-
- /**
- * Stream output from the tool. The kernel attributes every call to the
- * tool-call id, so concurrent shell output from different tools is
- * correctly separated.
- */
- readonly onOutput: (data: string, stream: "stdout" | "stderr") => void;
-
- /**
- * Cancellation signal. An aborted turn sets this so in-flight tool work
- * can clean up rather than leak.
- */
- readonly signal: AbortSignal;
-
- /**
- * Pre-bound Logger scoped to this tool-call span. Tools log correlated
- * without a global (P3). The kernel stamps extensionId, conversationId,
- * turnId, and spanId automatically.
- */
- readonly log: Logger;
-
- /**
- * Working directory for this turn, forwarded verbatim from `RunTurnInput.cwd`.
- * Tools that touch the filesystem resolve and contain paths against it.
- * Optional: when omitted, a tool falls back to its own configured/default
- * workdir. The kernel never interprets it.
- */
- readonly cwd?: string;
-
- /**
- * The conversation this tool-call belongs to. Tools that maintain
- * per-conversation state (e.g. a todo list) key on this. Forwarded
- * verbatim from `RunTurnInput.conversationId`. Optional: when omitted,
- * a tool has no conversation scope (e.g. a global tool).
- */
- readonly conversationId?: string;
-
- /**
- * The computer this tool-call executes on (SSH support). When
- * omitted/undefined, execution is LOCAL (today's behavior — the tool uses
- * the local node fs/child_process). When set, it is an SSH config alias
- * (see `notes/ssh-support-plan.md` §3); a tool resolves a remote
- * `ExecBackend` for it via its injected resolver. The kernel never
- * interprets it — it forwards the value verbatim from
- * `RunTurnInput.computerId`, exactly like `cwd`. It never enters the model
- * prompt, so it does not affect prompt caching.
- */
- readonly computerId?: string;
+ /** Unique id of the tool-call this execution serves. */
+ readonly toolCallId: string;
+
+ /**
+ * Stream output from the tool. The kernel attributes every call to the
+ * tool-call id, so concurrent shell output from different tools is
+ * correctly separated.
+ */
+ readonly onOutput: (data: string, stream: "stdout" | "stderr") => void;
+
+ /**
+ * Cancellation signal. An aborted turn sets this so in-flight tool work
+ * can clean up rather than leak.
+ */
+ readonly signal: AbortSignal;
+
+ /**
+ * Pre-bound Logger scoped to this tool-call span. Tools log correlated
+ * without a global (P3). The kernel stamps extensionId, conversationId,
+ * turnId, and spanId automatically.
+ */
+ readonly log: Logger;
+
+ /**
+ * Working directory for this turn, forwarded verbatim from `RunTurnInput.cwd`.
+ * Tools that touch the filesystem resolve and contain paths against it.
+ * Optional: when omitted, a tool falls back to its own configured/default
+ * workdir. The kernel never interprets it.
+ */
+ readonly cwd?: string;
+
+ /**
+ * The conversation this tool-call belongs to. Tools that maintain
+ * per-conversation state (e.g. a todo list) key on this. Forwarded
+ * verbatim from `RunTurnInput.conversationId`. Optional: when omitted,
+ * a tool has no conversation scope (e.g. a global tool).
+ */
+ readonly conversationId?: string;
+
+ /**
+ * The computer this tool-call executes on (SSH support). When
+ * omitted/undefined, execution is LOCAL (today's behavior — the tool uses
+ * the local node fs/child_process). When set, it is an SSH config alias
+ * (see `notes/ssh-support-plan.md` §3); a tool resolves a remote
+ * `ExecBackend` for it via its injected resolver. The kernel never
+ * interprets it — it forwards the value verbatim from
+ * `RunTurnInput.computerId`, exactly like `cwd`. It never enters the model
+ * prompt, so it does not affect prompt caching.
+ */
+ readonly computerId?: string;
}
/**
@@ -98,8 +98,8 @@ export interface ToolExecuteContext {
* react without the kernel interpreting the content.
*/
export interface ToolResult {
- readonly content: string;
- readonly isError?: boolean;
+ readonly content: string;
+ readonly isError?: boolean;
}
/**
@@ -108,9 +108,9 @@ export interface ToolResult {
* to the matched tool's `execute`.
*/
export interface ToolCall {
- readonly id: string;
- readonly name: string;
- readonly input: unknown;
+ readonly id: string;
+ readonly name: string;
+ readonly input: unknown;
}
/**
@@ -119,26 +119,26 @@ export interface ToolCall {
* concrete tools exist.
*/
export interface ToolContract {
- /** Unique name the model uses to invoke this tool. */
- readonly name: string;
-
- /** Human-readable description shown to the model. */
- readonly description: string;
-
- /** JSON-Schema-ish parameter declaration (structural, no library dep). */
- readonly parameters: ToolParameterSchema;
-
- /**
- * Execute the tool with parsed input. The kernel provides a per-call
- * context (cancellation, output streaming, attribution).
- */
- readonly execute: (args: unknown, ctx: ToolExecuteContext) => Promise<ToolResult>;
-
- /**
- * Whether this tool is safe to run concurrently with other tools.
- * When `false`, the kernel serializes this tool's calls even when the
- * dispatch policy allows parallelism. Defaults to `true` if omitted.
- * This overrides the global setting downward only (never widens parallelism).
- */
- readonly concurrencySafe?: boolean;
+ /** Unique name the model uses to invoke this tool. */
+ readonly name: string;
+
+ /** Human-readable description shown to the model. */
+ readonly description: string;
+
+ /** JSON-Schema-ish parameter declaration (structural, no library dep). */
+ readonly parameters: ToolParameterSchema;
+
+ /**
+ * Execute the tool with parsed input. The kernel provides a per-call
+ * context (cancellation, output streaming, attribution).
+ */
+ readonly execute: (args: unknown, ctx: ToolExecuteContext) => Promise<ToolResult>;
+
+ /**
+ * Whether this tool is safe to run concurrently with other tools.
+ * When `false`, the kernel serializes this tool's calls even when the
+ * dispatch policy allows parallelism. Defaults to `true` if omitted.
+ * This overrides the global setting downward only (never widens parallelism).
+ */
+ readonly concurrencySafe?: boolean;
}