@standardagents/spec 0.39.1 → 0.40.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.d.ts CHANGED
@@ -4275,1186 +4275,683 @@ type PromptInput<T extends PromptDefinition<string, ToolArgs>> = T['requiredSche
4275
4275
  declare function definePrompt<N extends string, S extends ToolArgs = never>(options: PromptDefinition<N, S>): PromptDefinition<N, S>;
4276
4276
 
4277
4277
  /**
4278
- * Hook definition types for Standard Agents.
4279
- *
4280
- * Hooks allow intercepting and modifying agent behavior at key points
4281
- * in the execution lifecycle. They enable logging, validation,
4282
- * transformation, and side effects.
4278
+ * Agent definition types for Standard Agents.
4283
4279
  *
4284
- * Hooks receive ThreadState as their first parameter, providing full
4285
- * access to thread operations and execution state.
4280
+ * Agents orchestrate conversations between AI models (dual_ai) or between
4281
+ * AI and human users (ai_human). They define the prompts, stop conditions,
4282
+ * and behavioral rules for each side of the conversation.
4286
4283
  *
4287
4284
  * @module
4288
4285
  */
4289
-
4290
4286
  /**
4291
- * Hook context is ThreadState.
4292
- *
4293
- * Hooks receive the full ThreadState, which includes identity, message access,
4294
- * resource loading, event emission, and execution state (when available).
4287
+ * Agent conversation type.
4295
4288
  *
4296
- * @example
4297
- * ```typescript
4298
- * const hook = defineHook({
4299
- * hook: 'filter_messages',
4300
- * id: 'keep_recent',
4301
- * execute: async (state, messages) => {
4302
- * console.log(`Thread: ${state.threadId}`);
4303
- * if (state.execution) {
4304
- * console.log(`Step: ${state.execution.stepCount}`);
4305
- * }
4306
- * return messages.slice(-10);
4307
- * },
4308
- * });
4309
- * ```
4289
+ * - `ai_human`: AI conversing with a human user (most common)
4290
+ * - `dual_ai`: Two AI participants conversing with each other
4310
4291
  */
4311
- type HookContext = ThreadState;
4292
+ type AgentType = 'ai_human' | 'dual_ai';
4312
4293
  /**
4313
- * Message structure for hook processing.
4314
- *
4315
- * Re-exported from threads.ts for convenience.
4316
- * @see Message
4294
+ * Ordering position for a child agent's required hook on its parent.
4317
4295
  */
4318
- type HookMessage = Message;
4296
+ type ParentHookStage = 'before_parent' | 'after_parent';
4319
4297
  /**
4320
- * Tool call structure for hook processing.
4298
+ * A thread-scoped hook that a child agent requires on every direct parent.
4299
+ *
4300
+ * Downloading a package is inert. The hook becomes active automatically when
4301
+ * an enabled parent prompt exposes this `dual_ai` agent as a child tool, and
4302
+ * remains active for exactly the lifetime of that relationship.
4321
4303
  */
4322
- interface HookToolCall {
4323
- /** Unique tool call identifier */
4324
- id: string;
4325
- /** Always 'function' for tool calls */
4326
- type: 'function';
4327
- /** Function details */
4328
- function: {
4329
- /** Tool name */
4330
- name: string;
4331
- /** JSON-encoded arguments */
4332
- arguments: string;
4333
- };
4304
+ interface ParentHookRequirement<Hook extends string = StandardAgentSpec.HookIds> {
4305
+ /** Child-local hook ID from `defineHook({ id })`. */
4306
+ hook: Hook;
4307
+ /** Human-facing explanation shown by installers and composition UIs. */
4308
+ reason: string;
4309
+ /** Whether the hook runs before or after the parent's own hook set. */
4310
+ stage?: ParentHookStage;
4334
4311
  }
4335
4312
  /**
4336
- * Tool result structure for hook processing.
4313
+ * Configuration for a session lifecycle tool.
4314
+ *
4315
+ * Used by side-level lifecycle options (`sessionStop`, `sessionFail`, `sessionStatus`)
4316
+ * to declare:
4317
+ * - which tool controls that lifecycle action
4318
+ * - which tool-argument field should be forwarded as message text
4319
+ * - which tool-argument field contains attachment path(s) to forward
4320
+ *
4321
+ * @template Callable - Callable name type
4337
4322
  */
4338
- interface HookToolResult {
4339
- /** Execution status */
4340
- status: 'success' | 'error';
4341
- /** Result string (for successful executions) */
4342
- result?: string;
4343
- /** Error message (for failed executions) */
4344
- error?: string;
4345
- /** Stack trace (for debugging) */
4346
- stack?: string;
4323
+ interface SessionToolConfig<Callable extends string = StandardAgentSpec.Callables> {
4347
4324
  /**
4348
- * File attachments returned by the tool.
4349
- *
4350
- * Can contain either:
4351
- * - ToolAttachment: New files with base64 data to be stored
4352
- * - AttachmentRef: References to existing files in the thread filesystem
4325
+ * Tool name for this session lifecycle action.
4353
4326
  */
4354
- attachments?: Array<ToolAttachment | AttachmentRef>;
4327
+ name: Callable;
4328
+ /**
4329
+ * Optional tool-argument property used as lifecycle message text.
4330
+ */
4331
+ messageProperty?: string;
4332
+ /**
4333
+ * Optional tool-argument property containing attachment path(s).
4334
+ * Supports a single path string or an array of path strings.
4335
+ */
4336
+ attachmentsProperty?: string;
4355
4337
  }
4356
4338
  /**
4357
- * LLM message format for prefilter hook.
4339
+ * Session lifecycle tool reference.
4358
4340
  *
4359
- * Messages passed to the prefilter_llm_history hook are in chat completion
4360
- * format. Content can be a plain string or a multimodal array (for messages
4361
- * with images). Additional provider-specific fields may also be present.
4341
+ * Can be either:
4342
+ * - a plain tool name string (legacy/simple form)
4343
+ * - a {@link SessionToolConfig} object (explicit mapping form)
4362
4344
  */
4363
- interface LLMMessage {
4364
- /** Message role */
4365
- role: string;
4366
- /** Message content (string for text-only, array for multimodal) */
4367
- content?: string | null | unknown[];
4368
- /** Tool calls (parsed) */
4369
- tool_calls?: unknown;
4370
- /** Tool call ID */
4371
- tool_call_id?: string;
4372
- /** Tool name */
4373
- name?: string;
4374
- /** Reasoning content (for models like o1) */
4375
- reasoning_content?: string;
4376
- /** Allow additional provider-specific fields */
4377
- [key: string]: unknown;
4378
- }
4345
+ type SessionToolBinding<Callable extends string = StandardAgentSpec.Callables> = Callable | SessionToolConfig<Callable>;
4379
4346
  /**
4380
- * Hook signatures for all available hooks.
4347
+ * Configuration for one side of an agent conversation.
4381
4348
  *
4382
- * Each hook has a specific signature that defines when it's called
4383
- * and what data it receives. Hooks can modify data by returning
4384
- * transformed values or perform side effects.
4349
+ * Each side has a prompt, stop conditions, and turn limits.
4350
+ * For `ai_human` agents, only sideA (the AI) needs configuration.
4351
+ * For `dual_ai` agents, both sides need configuration.
4385
4352
  *
4386
- * All hooks receive ThreadState as their first parameter.
4353
+ * @template Prompt - The prompt reference type (string or type-safe union)
4354
+ * @template Callable - The callable reference type (string or type-safe union)
4387
4355
  *
4388
- * @template State - The state type (defaults to ThreadState)
4389
- * @template Msg - The message type (defaults to HookMessage)
4390
- * @template ToolCall - The tool call type (defaults to HookToolCall)
4391
- * @template ToolResult - The tool result type (defaults to HookToolResult)
4356
+ * @example
4357
+ * ```typescript
4358
+ * const sideConfig: SideConfig = {
4359
+ * label: 'Support Agent',
4360
+ * prompt: 'customer_support',
4361
+ * stopOnResponse: true,
4362
+ * maxSteps: 10,
4363
+ * };
4364
+ * ```
4392
4365
  */
4393
- interface HookSignatures<State = ThreadState, Msg = HookMessage, ToolCall = HookToolCall, ToolResult = HookToolResult> {
4366
+ interface SideConfig<Prompt extends string = StandardAgentSpec.Prompts, Callable extends string = StandardAgentSpec.Callables> {
4394
4367
  /**
4395
- * Called after a thread is created, before initial message processing or LLM
4396
- * requests for that thread.
4397
- *
4398
- * Only ThreadState is passed. Use for initializing durable thread values,
4399
- * env placeholders, files, or side-effect instrumentation.
4368
+ * Custom label for this side of the conversation.
4369
+ * Used in UI and logs for clarity.
4400
4370
  *
4401
- * @param state - Created thread state
4371
+ * @example 'Support Agent', 'Customer', 'ATC', 'Pilot'
4402
4372
  */
4403
- after_thread_created: (state: State) => Promise<void>;
4373
+ label?: string;
4404
4374
  /**
4405
- * Called on the parent thread immediately after a subagent child thread is
4406
- * created.
4407
- *
4408
- * Receives parent ThreadState and child ThreadState. Use for copying or
4409
- * linking metadata, bookkeeping, or side-effect instrumentation.
4410
- *
4411
- * @param state - Parent thread state
4412
- * @param childState - Created child thread state
4375
+ * The prompt to use for this side.
4376
+ * Must reference a prompt defined in agents/prompts/.
4413
4377
  */
4414
- after_subagent_created: (state: State, childState: State) => Promise<void>;
4378
+ prompt: Prompt;
4415
4379
  /**
4416
- * Called after the prompt system message is rendered for a model request.
4417
- *
4418
- * Return a string to replace the rendered system message. Return null or
4419
- * undefined to keep the original message.
4420
- *
4421
- * @param state - Thread state
4422
- * @param systemMessage - Rendered system message for this request
4423
- * @returns Replacement system message, or null/undefined for no change
4380
+ * Stop this side's turn when it returns a text response (no tool calls).
4381
+ * When true, the side's turn ends after producing a message without tools.
4382
+ * @default true
4424
4383
  */
4425
- after_system_message: (state: State, systemMessage: string) => string | Promise<string | null | undefined> | null | undefined;
4384
+ stopOnResponse?: boolean;
4426
4385
  /**
4427
- * Called exactly once per model-context assembly on a stable snapshot of
4428
- * eligible persisted history.
4386
+ * Stop this side's turn when a specific tool is called.
4387
+ * Overrides stopOnResponse when the named tool is invoked.
4388
+ * Requires stopToolResponseProperty to extract the result.
4389
+ */
4390
+ stopTool?: Callable;
4391
+ /**
4392
+ * Property to extract from the stop tool's result.
4393
+ * Required when stopTool is set.
4394
+ * The extracted value is used to determine the conversation outcome.
4395
+ */
4396
+ stopToolResponseProperty?: string;
4397
+ /**
4398
+ * Maximum steps for this side before forcing a stop.
4399
+ * Safety limit to prevent runaway execution.
4400
+ * A step is one complete LLM request/response cycle.
4401
+ */
4402
+ maxSteps?: number;
4403
+ /**
4404
+ * Tool that ends the entire session when called.
4429
4405
  *
4430
- * Receives raw message rows from storage before any transformation.
4431
- * Use for filtering, sorting, or augmenting message history.
4432
- * The result is the request context and does not replace
4433
- * `state.execution.messageHistory`. Messages injected while this hook runs
4434
- * become eligible for the following context assembly.
4406
+ * Different from stopTool: this ends the session for both sides,
4407
+ * not just this side's turn.
4435
4408
  *
4436
- * @param state - Thread state
4437
- * @param messages - Array of messages from storage
4438
- * @returns Modified message array
4409
+ * Use object form to explicitly map message/attachment argument fields.
4439
4410
  *
4440
- * @example
4441
- * ```typescript
4442
- * defineHook({
4443
- * hook: 'filter_messages',
4444
- * id: 'limit_to_10',
4445
- * execute: async (state, messages) => messages.slice(-10),
4446
- * });
4447
- * ```
4411
+ * In subagent runtimes, mapped message/attachments are the canonical
4412
+ * completion payload returned from child -> parent.
4448
4413
  */
4449
- filter_messages: (state: State, messages: Msg[]) => Promise<Msg[]>;
4414
+ sessionStop?: SessionToolBinding<Callable>;
4450
4415
  /**
4451
- * Called after filtered history is transformed into provider messages and
4452
- * before dispatch.
4416
+ * Tool that fails the entire session when called.
4453
4417
  *
4454
- * Receives messages already transformed into chat completion format.
4455
- * Use for final request-local adjustments. Messages injected while this
4456
- * hook runs become eligible for the following context assembly.
4418
+ * Use this when the side cannot fulfill the request and needs to terminate
4419
+ * the session with an error outcome.
4457
4420
  *
4458
- * @param state - Thread state
4459
- * @param messages - Array of LLM-formatted messages
4460
- * @returns Modified LLM message array
4421
+ * When used inside a subagent, this failure is propagated back to the parent
4422
+ * as a terminal failure result.
4461
4423
  *
4462
- * @example
4463
- * ```typescript
4464
- * defineHook({
4465
- * hook: 'prefilter_llm_history',
4466
- * id: 'add_reminder',
4467
- * execute: async (state, messages) => messages,
4468
- * });
4469
- * ```
4424
+ * Use object form to explicitly map message/attachment argument fields.
4425
+ *
4426
+ * In subagent runtimes, mapped message/attachments are the canonical
4427
+ * failure payload returned from child -> parent.
4470
4428
  */
4471
- prefilter_llm_history: (state: State, messages: LLMMessage[]) => Promise<LLMMessage[]>;
4429
+ sessionFail?: SessionToolBinding<Callable>;
4472
4430
  /**
4473
- * Called before a message is created in the database.
4431
+ * Tool that publishes status updates to the parent thread when used as a subagent.
4474
4432
  *
4475
- * Receives the full message object before insertion. Return modified
4476
- * message to transform before storage.
4433
+ * When called from a `dual_ai` subagent, message text is forwarded to
4434
+ * the parent thread's subagent registry as this instance's current status.
4477
4435
  *
4478
- * A hook that throws during `injectMessage()` rejects the injection and
4479
- * no message is persisted. Other runtime insertions log the failure and
4480
- * keep the last good message.
4436
+ * Use object form to explicitly map message/attachment argument fields.
4481
4437
  *
4482
- * @param state - Thread state
4483
- * @param message - Message to be created
4484
- * @returns Modified message
4438
+ * Status updates do not terminate the session.
4485
4439
  *
4486
4440
  * @example
4487
4441
  * ```typescript
4488
- * defineHook({
4489
- * hook: 'before_create_message',
4490
- * id: 'prefix_content',
4491
- * execute: async (state, message) => {
4492
- * // message has full type: id, role, content, created_at, etc.
4493
- * if (message.role === 'user' && message.content) {
4494
- * return { ...message, content: `[user] ${message.content}` };
4495
- * }
4496
- * return message;
4497
- * }
4498
- * });
4442
+ * sideA: {
4443
+ * prompt: 'research_worker',
4444
+ * sessionStatus: { name: 'update_status', messageProperty: 'status' },
4445
+ * }
4499
4446
  * ```
4500
4447
  */
4501
- before_create_message: (state: State, message: Msg) => Promise<Msg>;
4448
+ sessionStatus?: SessionToolBinding<Callable>;
4449
+ }
4450
+ /**
4451
+ * Agent definition configuration.
4452
+ *
4453
+ * @template N - The agent name as a string literal type
4454
+ * @template Prompt - The prompt reference type (string or type-safe union)
4455
+ * @template Callable - The callable reference type (string or type-safe union)
4456
+ *
4457
+ * @example
4458
+ * ```typescript
4459
+ * import { defineAgent } from '@standardagents/spec';
4460
+ *
4461
+ * export default defineAgent({
4462
+ * name: 'support_agent',
4463
+ * title: 'Customer Support Agent',
4464
+ * type: 'ai_human',
4465
+ * sideA: {
4466
+ * label: 'Support',
4467
+ * prompt: 'customer_support',
4468
+ * stopOnResponse: true,
4469
+ * },
4470
+ * });
4471
+ * ```
4472
+ */
4473
+ interface AgentDefinition<N extends string = string, Prompt extends string = StandardAgentSpec.Prompts, Callable extends string = StandardAgentSpec.Callables> {
4502
4474
  /**
4503
- * Called after a message is created in the database.
4504
- *
4505
- * Use for logging, analytics, or triggering side effects after
4506
- * message creation. Cannot modify the message.
4507
- *
4508
- * @param state - Thread state
4509
- * @param message - The created message
4510
- *
4511
- * @example
4512
- * ```typescript
4513
- * defineHook({
4514
- * hook: 'after_create_message',
4515
- * id: 'log_creation',
4516
- * execute: async (state, message) => {
4517
- * console.log(`Created ${message.role} message: ${message.id}`);
4518
- * }
4519
- * });
4520
- * ```
4475
+ * Unique name for this agent.
4476
+ * Used as the identifier for thread creation and handoffs.
4477
+ * Should be snake_case (e.g., 'support_agent', 'research_flow').
4521
4478
  */
4522
- after_create_message: (state: State, message: Msg) => Promise<void>;
4479
+ name: N;
4523
4480
  /**
4524
- * Called before a message is updated in the database.
4525
- *
4526
- * Receives the message ID and update data. Return modified updates
4527
- * to transform the changes before storage.
4481
+ * Human-readable title for the agent.
4482
+ * Optional - if not provided, the name will be used.
4483
+ * @deprecated Use name instead. Title will be removed in a future version.
4484
+ */
4485
+ title?: string;
4486
+ /**
4487
+ * Agent conversation type.
4528
4488
  *
4529
- * @param state - Thread state
4530
- * @param messageId - ID of message being updated
4531
- * @param updates - Update data
4532
- * @returns Modified update data
4489
+ * - `ai_human`: AI conversing with a human user (default)
4490
+ * - `dual_ai`: Two AI participants conversing
4533
4491
  *
4534
- * @example
4535
- * ```typescript
4536
- * defineHook({
4537
- * hook: 'before_update_message',
4538
- * id: 'stamp_updated_at',
4539
- * execute: async (state, messageId, updates) => ({
4540
- * ...updates,
4541
- * updated_at: Date.now(),
4542
- * }),
4543
- * });
4544
- * ```
4492
+ * @default 'ai_human'
4545
4493
  */
4546
- before_update_message: (state: State, messageId: string, updates: Record<string, unknown>) => Promise<Record<string, unknown>>;
4494
+ type?: AgentType;
4547
4495
  /**
4548
- * Called after a message is updated in the database.
4549
- *
4550
- * Use for logging, analytics, or triggering side effects after
4551
- * message update. Cannot modify the message.
4496
+ * Maximum total turns across both sides.
4497
+ * Only applies to `dual_ai` agents.
4498
+ * Prevents infinite loops in AI-to-AI conversations.
4499
+ */
4500
+ maxSessionTurns?: number;
4501
+ /**
4502
+ * Configuration for Side A.
4503
+ * For `ai_human`: This is the AI side.
4504
+ * For `dual_ai`: This is the first AI participant.
4505
+ */
4506
+ sideA: SideConfig<Prompt, Callable>;
4507
+ /**
4508
+ * Configuration for Side B.
4509
+ * For `ai_human`: Optional, the human side doesn't need config.
4510
+ * For `dual_ai`: Required, the second AI participant.
4511
+ */
4512
+ sideB?: SideConfig<Prompt, Callable>;
4513
+ /**
4514
+ * Expose this agent as a tool for other prompts.
4515
+ * Enables agent composition and handoffs.
4516
+ * When true, other prompts can invoke this agent as a tool.
4517
+ * @default false
4518
+ */
4519
+ exposeAsTool?: boolean;
4520
+ /**
4521
+ * Whether this agent is a PRIMARY, user-facing entry point (default true).
4552
4522
  *
4553
- * @param state - Thread state
4554
- * @param message - The updated message
4523
+ * Set `primary: false` on agents that exist only as internal handoff
4524
+ * targets or infrastructure — e.g. the specialist variants of a
4525
+ * domain-routing gearbox. Non-primary agents remain fully addressable
4526
+ * (handoffs, invokeTool, thread resumption) and their threads remain
4527
+ * visible; UIs use this flag to keep them out of top-level agent lists
4528
+ * and pickers so one logical product agent doesn't show up N times.
4529
+ */
4530
+ primary?: boolean;
4531
+ /**
4532
+ * Description shown when agent is used as a tool.
4533
+ * Required if exposeAsTool is true.
4534
+ * Should clearly describe what this agent does.
4535
+ */
4536
+ toolDescription?: string;
4537
+ /**
4538
+ * Brief description of what this agent does.
4539
+ * Useful for UIs and documentation.
4555
4540
  *
4556
- * @example
4557
- * ```typescript
4558
- * defineHook({
4559
- * hook: 'after_update_message',
4560
- * id: 'log_update',
4561
- * execute: async (state, message) => {
4562
- * console.log(`Message updated: ${message.id}`);
4563
- * },
4564
- * });
4565
- * ```
4541
+ * @example 'Handles customer support inquiries and resolves issues'
4566
4542
  */
4567
- after_update_message: (state: State, message: Msg) => Promise<void>;
4543
+ description?: string;
4568
4544
  /**
4569
- * Called before a tool result is stored in the database.
4545
+ * Icon URL or absolute path for the agent.
4546
+ * Absolute paths (starting with `/`) are converted to full URLs in API responses.
4570
4547
  *
4571
- * Receives the tool call and result before storage. Return modified
4572
- * result to transform before storage.
4548
+ * @example 'https://example.com/icon.svg' or '/icons/support.svg'
4549
+ */
4550
+ icon?: string;
4551
+ /**
4552
+ * Environment values provided by this agent.
4553
+ * Agent values are lower priority than thread/account/instance values.
4554
+ */
4555
+ env?: Record<string, string>;
4556
+ /**
4557
+ * @deprecated Use `env` instead.
4558
+ */
4559
+ tenvs?: Record<string, unknown>;
4560
+ /**
4561
+ * npm package name for this agent when packed.
4562
+ * Used by the packing system to maintain consistent package identity
4563
+ * across pack/unpack cycles.
4573
4564
  *
4574
- * @param state - Thread state
4575
- * @param toolCall - The tool call that was executed
4576
- * @param toolResult - The result from tool execution
4577
- * @returns Modified tool result
4565
+ * @example 'standardagent-support-agent', '@myorg/support-agent'
4566
+ */
4567
+ packageName?: string;
4568
+ /**
4569
+ * Package version (semver format).
4570
+ * Used by the packing system to track versions across pack/unpack cycles.
4571
+ * When re-packing, this version is auto-incremented by the pack modal.
4578
4572
  *
4579
- * @example
4580
- * ```typescript
4581
- * defineHook({
4582
- * hook: 'before_store_tool_result',
4583
- * id: 'sanitize_results',
4584
- * execute: async (state, toolCall, toolResult) => {
4585
- * // toolCall has: id, type, function.name, function.arguments
4586
- * // toolResult has: status, result?, error?, stack?, attachments?
4587
- * if (toolResult.result) {
4588
- * return { ...toolResult, result: sanitize(toolResult.result) };
4589
- * }
4590
- * return toolResult;
4591
- * }
4592
- * });
4593
- * ```
4573
+ * @example '1.0.0', '2.3.1-beta.1'
4594
4574
  */
4595
- before_store_tool_result: (state: State, toolCall: ToolCall, toolResult: ToolResult) => Promise<ToolResult>;
4575
+ version?: string;
4596
4576
  /**
4597
- * Called after a successful tool call.
4577
+ * Package author/copyright holder.
4578
+ * Used by the packing system for the LICENSE file and package.json author field.
4598
4579
  *
4599
- * Receives the tool call and its result. Can return a modified result
4600
- * or null to use the original. Use for logging, transformation, or
4601
- * post-processing of successful tool executions.
4580
+ * @example 'John Doe', 'Acme Corp'
4581
+ */
4582
+ author?: string;
4583
+ /**
4584
+ * License identifier (SPDX format).
4585
+ * Used by the packing system for LICENSE file generation.
4602
4586
  *
4603
- * @param state - Thread state
4604
- * @param toolCall - The executed tool call
4605
- * @param toolResult - The successful result
4606
- * @returns Modified result or null for original
4587
+ * @example 'MIT', 'Apache-2.0', 'ISC'
4588
+ */
4589
+ license?: string;
4590
+ /**
4591
+ * Hook IDs to run for this agent.
4592
+ * References hooks by their unique `id` property from defineHook().
4593
+ * These run when prompts don't specify their own hooks.
4607
4594
  *
4608
4595
  * @example
4609
4596
  * ```typescript
4610
- * defineHook({
4611
- * hook: 'after_tool_call_success',
4612
- * id: 'log_success',
4613
- * execute: async (state, toolCall, toolResult) => {
4614
- * console.log(`Tool ${toolCall.function.name} succeeded`);
4615
- * return null; // use original result
4616
- * },
4617
- * });
4597
+ * hooks: ['log_messages', 'track_tool_usage']
4618
4598
  * ```
4619
4599
  */
4620
- after_tool_call_success: (state: State, toolCall: ToolCall, toolResult: ToolResult) => Promise<ToolResult | null>;
4600
+ hooks?: StandardAgentSpec.HookIds[];
4621
4601
  /**
4622
- * Called after a failed tool call.
4623
- *
4624
- * Receives the tool call and error result. Can return a modified result
4625
- * or null to use the original. Use for error handling, logging, or
4626
- * recovery attempts.
4602
+ * Hooks this agent requires on a direct parent when composed as a `dual_ai`
4603
+ * child tool.
4627
4604
  *
4628
- * @param state - Thread state
4629
- * @param toolCall - The failed tool call
4630
- * @param toolResult - The error result
4631
- * @returns Modified result or null for original
4605
+ * Adding an enabled relationship activates these hooks automatically;
4606
+ * downloading the package alone does not. Hooks execute against the
4607
+ * parent's ThreadState and are resolved in this agent's package namespace.
4608
+ * `reason` is required so composition tooling can disclose the behavior.
4632
4609
  *
4633
4610
  * @example
4634
4611
  * ```typescript
4635
- * defineHook({
4636
- * hook: 'after_tool_call_failure',
4637
- * id: 'log_failure',
4638
- * execute: async (state, toolCall, toolResult) => {
4639
- * console.error(`Tool ${toolCall.function.name} failed: ${toolResult.error}`);
4640
- * return null; // use original error
4641
- * },
4642
- * });
4612
+ * parentHooks: [{
4613
+ * hook: 'context_compaction',
4614
+ * reason: 'Compacts the parent history before its context limit is reached.',
4615
+ * stage: 'after_parent',
4616
+ * }]
4643
4617
  * ```
4644
4618
  */
4645
- after_tool_call_failure: (state: State, toolCall: ToolCall, toolResult: ToolResult) => Promise<ToolResult | null>;
4646
- /**
4647
- * Called when a tool call STARTS — before the tool executes, while the model
4648
- * may still be generating the rest of the call. Fires exactly once per tool
4649
- * call. Observational: the return value is ignored.
4650
- *
4651
- * Timing depends on whether the tool declares a `progressArgument`:
4652
- * - **With** `progressArgument`: fires as soon as *just that argument* has
4653
- * finished streaming (not the whole call), passing its value as `progress`.
4654
- * This lets a UI show "fixing index.html" before a large `content` argument
4655
- * finishes generating.
4656
- * - **Without** `progressArgument`: fires as soon as the tool call appears in
4657
- * the stream — its name is known but `toolCall.function.arguments` may be an
4658
- * incomplete JSON fragment (or empty), and `progress` is undefined.
4659
- *
4660
- * Conforming runtimes also broadcast a `tool_call_started` thread event to
4661
- * connected clients with `{ id, name, progress }` at the same moment.
4662
- *
4663
- * @param state - Thread state
4664
- * @param toolCall - The starting tool call; `function.arguments` may be incomplete
4665
- * @param progress - The declared progress-argument value, when available
4666
- *
4667
- * @example
4668
- * ```typescript
4669
- * defineHook({
4670
- * hook: 'tool_call_started',
4671
- * id: 'log_tool_start',
4672
- * execute: async (state, toolCall, progress) => {
4673
- * console.log(`▸ ${toolCall.function.name}${progress ? `: ${progress}` : ''}`);
4674
- * },
4675
- * });
4676
- * ```
4677
- */
4678
- tool_call_started: (state: State, toolCall: ToolCall, progress?: string) => Promise<void>;
4679
- /**
4680
- * Called when a tool call is DONE — after it has executed, with its result,
4681
- * regardless of success or failure. Fires once per tool call. Observational:
4682
- * the return value is ignored.
4683
- *
4684
- * This complements `after_tool_call_success` / `after_tool_call_failure`,
4685
- * which can additionally *modify* the stored result; `tool_call_done` is a
4686
- * single unified observation point that always fires. Conforming runtimes
4687
- * also broadcast a `tool_call_done` thread event to connected clients with
4688
- * `{ id, name, status }` at the same moment.
4689
- *
4690
- * @param state - Thread state
4691
- * @param toolCall - The completed tool call
4692
- * @param toolResult - The tool's result (`status` is `'success'` or `'error'`)
4693
- */
4694
- tool_call_done: (state: State, toolCall: ToolCall, toolResult: ToolResult) => Promise<void>;
4695
- /**
4696
- * Called when a socket attaches to the user's event stream
4697
- * (`{runtime_prefix}/users/me/stream`).
4698
- *
4699
- * @param user - User scope state
4700
- * @param client - The attached client's id and self-declared kind
4701
- */
4702
- user_client_attached: (user: UserState, client: UserClientInfo) => Promise<void>;
4703
- /**
4704
- * Called when a socket detaches from the user's event stream.
4705
- *
4706
- * @param user - User scope state
4707
- * @param client - The detached client's id and self-declared kind
4708
- */
4709
- user_client_detached: (user: UserState, client: UserClientInfo) => Promise<void>;
4710
- /**
4711
- * Called after a durable user value changes, regardless of the writer
4712
- * (user endpoint, thread-side handle, or REST).
4713
- *
4714
- * @param user - User scope state
4715
- * @param key - The key that changed
4716
- */
4717
- user_value_changed: (user: UserState, key: string) => Promise<void>;
4718
- }
4719
- /**
4720
- * Valid hook names.
4721
- */
4722
- type HookName = keyof HookSignatures;
4723
- /**
4724
- * Hook definition tuple.
4725
- *
4726
- * A hook definition is a tuple containing the hook name and implementation.
4727
- *
4728
- * @template K - The hook name
4729
- * @template State - The state type (defaults to ThreadState)
4730
- * @template Msg - The message type
4731
- * @template ToolCall - The tool call type
4732
- * @template ToolResult - The tool result type
4733
- * @deprecated Use the object-based defineHook signature instead
4734
- */
4735
- type HookDefinition<K extends HookName = HookName, State = ThreadState, Msg = HookMessage, ToolCall = HookToolCall, ToolResult = HookToolResult> = [K, HookSignatures<State, Msg, ToolCall, ToolResult>[K]];
4736
- /**
4737
- * Options for defining a hook with explicit ID.
4738
- * The generic K ensures `execute` is properly typed for the specific hook type.
4739
- *
4740
- * @template K - Hook type from HookSignatures (e.g., 'filter_messages')
4741
- */
4742
- interface HookDefinitionOptions<K extends HookName> {
4743
- /** The hook type (e.g., 'filter_messages', 'before_create_message') */
4744
- hook: K;
4745
- /** Unique identifier for this hook implementation (snake_case) */
4746
- id: string;
4747
- /**
4748
- * The hook implementation function.
4749
- * Type is automatically inferred from the hook type.
4750
- */
4751
- execute: HookSignatures<ThreadState, HookMessage, HookToolCall, HookToolResult>[K];
4752
- }
4753
- /**
4754
- * Return type from defineHook - preserves the hook type for type-safe usage.
4755
- *
4756
- * @template K - Hook type from HookSignatures
4757
- */
4758
- interface HookDefinitionResult<K extends HookName> {
4759
- /** The hook type name */
4760
- hook: K;
4761
- /** The unique hook ID */
4762
- id: string;
4763
- /** The typed execute function */
4764
- execute: HookSignatures<ThreadState, HookMessage, HookToolCall, HookToolResult>[K];
4619
+ parentHooks?: ParentHookRequirement[];
4765
4620
  }
4766
4621
  /**
4767
- * Define a hook with a unique identifier and strict typing.
4622
+ * Defines an agent configuration.
4768
4623
  *
4769
- * The hook type determines the execute function signature.
4770
- * TypeScript will enforce correct parameter and return types.
4624
+ * Agents orchestrate conversations between AI models, or between AI and
4625
+ * human users. They use prompts to configure each side of the conversation
4626
+ * and define stop conditions to control conversation flow.
4771
4627
  *
4772
- * @template K - The hook type (inferred from options.hook)
4773
- * @param options - Hook definition with hook type, ID, and execute function
4774
- * @returns Typed hook definition for registration
4628
+ * @template N - The agent name as a string literal type
4629
+ * @param options - Agent configuration options
4630
+ * @returns The agent definition for registration
4775
4631
  *
4776
4632
  * @example
4777
4633
  * ```typescript
4778
- * // Filter messages - execute receives (state: ThreadState, messages: HookMessage[])
4779
- * export default defineHook({
4780
- * hook: 'filter_messages',
4781
- * id: 'limit_to_20_messages',
4782
- * execute: async (state, messages) => {
4783
- * return messages.slice(-20);
4784
- * }
4785
- * });
4786
- * ```
4634
+ * // agents/agents/support_agent.ts
4635
+ * import { defineAgent } from '@standardagents/spec';
4787
4636
  *
4788
- * @example
4789
- * ```typescript
4790
- * // Before create message - execute receives (state: ThreadState, message: Message)
4791
- * export default defineHook({
4792
- * hook: 'before_create_message',
4793
- * id: 'prefix_content',
4794
- * execute: async (state, message) => {
4795
- * if (message.role === 'user' && message.content) {
4796
- * return { ...message, content: `[user] ${message.content}` };
4797
- * }
4798
- * return message;
4799
- * }
4637
+ * export default defineAgent({
4638
+ * name: 'support_agent',
4639
+ * title: 'Customer Support Agent',
4640
+ * type: 'ai_human',
4641
+ * sideA: {
4642
+ * label: 'Support',
4643
+ * prompt: 'customer_support',
4644
+ * stopOnResponse: true,
4645
+ * sessionStop: 'close_ticket',
4646
+ * },
4647
+ * exposeAsTool: true,
4648
+ * toolDescription: 'Hand off to customer support',
4800
4649
  * });
4801
4650
  * ```
4802
4651
  *
4803
4652
  * @example
4804
4653
  * ```typescript
4805
- * // After tool call success - execute receives (state, toolCall, toolResult)
4806
- * export default defineHook({
4807
- * hook: 'after_tool_call_success',
4808
- * id: 'log_tool_usage',
4809
- * execute: async (state, toolCall, toolResult) => {
4810
- * console.log(`Tool ${toolCall.function.name} returned: ${toolResult.result}`);
4811
- * return null; // Use original result
4812
- * }
4654
+ * // Dual AI agent (two AIs conversing)
4655
+ * export default defineAgent({
4656
+ * name: 'debate_agent',
4657
+ * title: 'AI Debate',
4658
+ * type: 'dual_ai',
4659
+ * maxSessionTurns: 10,
4660
+ * sideA: {
4661
+ * label: 'Pro',
4662
+ * prompt: 'debate_pro',
4663
+ * stopOnResponse: true,
4664
+ * },
4665
+ * sideB: {
4666
+ * label: 'Con',
4667
+ * prompt: 'debate_con',
4668
+ * stopOnResponse: true,
4669
+ * sessionStop: 'conclude_debate',
4670
+ * },
4813
4671
  * });
4814
4672
  * ```
4815
4673
  */
4816
- declare function defineHook<K extends HookName>(options: HookDefinitionOptions<K>): HookDefinitionResult<K>;
4674
+ declare function defineAgent<N extends string>(options: AgentDefinition<N>): AgentDefinition<N>;
4817
4675
 
4818
4676
  /**
4819
- * Agent definition types for Standard Agents.
4677
+ * Endpoint definition types for Standard Agents.
4820
4678
  *
4821
- * Agents orchestrate conversations between AI models (dual_ai) or between
4822
- * AI and human users (ai_human). They define the prompts, stop conditions,
4823
- * and behavioral rules for each side of the conversation.
4679
+ * Endpoints expose HTTP APIs for interacting with agent threads.
4680
+ * They can be standard controllers or thread-specific endpoints
4681
+ * with automatic ThreadState injection.
4824
4682
  *
4825
4683
  * @module
4826
4684
  */
4685
+
4827
4686
  /**
4828
- * Agent conversation type.
4687
+ * The access level of an endpoint.
4829
4688
  *
4830
- * - `ai_human`: AI conversing with a human user (most common)
4831
- * - `dual_ai`: Two AI participants conversing with each other
4689
+ * - `authenticated` — the runtime MUST authenticate the request before
4690
+ * invoking the handler, and for thread endpoints MUST additionally enforce
4691
+ * its thread access policy. Requests that fail authentication or
4692
+ * authorization MUST be rejected without invoking the handler.
4693
+ * - `public` — the runtime MUST invoke the handler regardless of the
4694
+ * request's authentication state. The runtime MUST still attempt caller
4695
+ * resolution: a request presenting credentials the runtime can verify is
4696
+ * attributed to that caller, and any other request is treated as anonymous
4697
+ * rather than rejected.
4698
+ *
4699
+ * When no access level is declared, the access level is `authenticated`.
4832
4700
  */
4833
- type AgentType = 'ai_human' | 'dual_ai';
4701
+ type EndpointAccess = 'authenticated' | 'public';
4834
4702
  /**
4835
- * Configuration for a session lifecycle tool.
4836
- *
4837
- * Used by side-level lifecycle options (`sessionStop`, `sessionFail`, `sessionStatus`)
4838
- * to declare:
4839
- * - which tool controls that lifecycle action
4840
- * - which tool-argument field should be forwarded as message text
4841
- * - which tool-argument field contains attachment path(s) to forward
4703
+ * Symbol used to carry an endpoint's declared access level.
4842
4704
  *
4843
- * @template Callable - Callable name type
4705
+ * The declaration is attached to the handler at definition time so that
4706
+ * runtimes can read it before routing the request through their
4707
+ * authentication layer.
4844
4708
  */
4845
- interface SessionToolConfig<Callable extends string = StandardAgentSpec.Callables> {
4846
- /**
4847
- * Tool name for this session lifecycle action.
4848
- */
4849
- name: Callable;
4850
- /**
4851
- * Optional tool-argument property used as lifecycle message text.
4852
- */
4853
- messageProperty?: string;
4709
+ declare const ENDPOINT_ACCESS_SYMBOL: unique symbol;
4710
+ /**
4711
+ * Options accepted when defining an endpoint.
4712
+ */
4713
+ interface EndpointOptions {
4854
4714
  /**
4855
- * Optional tool-argument property containing attachment path(s).
4856
- * Supports a single path string or an array of path strings.
4715
+ * The endpoint's access level. Defaults to `authenticated`.
4857
4716
  */
4858
- attachmentsProperty?: string;
4717
+ access?: EndpointAccess;
4859
4718
  }
4860
4719
  /**
4861
- * Session lifecycle tool reference.
4720
+ * Resolve the declared access level of an endpoint handler.
4862
4721
  *
4863
- * Can be either:
4864
- * - a plain tool name string (legacy/simple form)
4865
- * - a {@link SessionToolConfig} object (explicit mapping form)
4722
+ * Returns `authenticated` when the value carries no declaration.
4866
4723
  */
4867
- type SessionToolBinding<Callable extends string = StandardAgentSpec.Callables> = Callable | SessionToolConfig<Callable>;
4724
+ declare function getEndpointAccess(value: unknown): EndpointAccess;
4868
4725
  /**
4869
- * Configuration for one side of an agent conversation.
4726
+ * The authentication context supplied to a thread endpoint handler.
4870
4727
  *
4871
- * Each side has a prompt, stop conditions, and turn limits.
4872
- * For `ai_human` agents, only sideA (the AI) needs configuration.
4873
- * For `dual_ai` agents, both sides need configuration.
4728
+ * The runtime constructs this context for every invocation:
4874
4729
  *
4875
- * @template Prompt - The prompt reference type (string or type-safe union)
4876
- * @template Callable - The callable reference type (string or type-safe union)
4730
+ * - `caller` MUST be the authenticated identity when the request presented
4731
+ * credentials the runtime verified, and `null` otherwise. For an endpoint
4732
+ * whose access level is `authenticated`, `caller` is always non-null.
4733
+ * - `threadAccess` MUST be `true` if and only if the runtime's thread access
4734
+ * policy grants the resolved caller access to the requested thread. It
4735
+ * MUST be `false` when `caller` is `null`. For an endpoint whose access
4736
+ * level is `authenticated`, `threadAccess` is always `true`.
4877
4737
  *
4878
- * @example
4879
- * ```typescript
4880
- * const sideConfig: SideConfig = {
4881
- * label: 'Support Agent',
4882
- * prompt: 'customer_support',
4883
- * stopOnResponse: true,
4884
- * maxSteps: 10,
4885
- * };
4886
- * ```
4738
+ * The mechanism by which a runtime authenticates requests is
4739
+ * implementation-defined; this interface defines only the identity contract
4740
+ * observable by endpoint code.
4887
4741
  */
4888
- interface SideConfig<Prompt extends string = StandardAgentSpec.Prompts, Callable extends string = StandardAgentSpec.Callables> {
4889
- /**
4890
- * Custom label for this side of the conversation.
4891
- * Used in UI and logs for clarity.
4892
- *
4893
- * @example 'Support Agent', 'Customer', 'ATC', 'Pilot'
4894
- */
4895
- label?: string;
4896
- /**
4897
- * The prompt to use for this side.
4898
- * Must reference a prompt defined in agents/prompts/.
4899
- */
4900
- prompt: Prompt;
4901
- /**
4902
- * Stop this side's turn when it returns a text response (no tool calls).
4903
- * When true, the side's turn ends after producing a message without tools.
4904
- * @default true
4905
- */
4906
- stopOnResponse?: boolean;
4907
- /**
4908
- * Stop this side's turn when a specific tool is called.
4909
- * Overrides stopOnResponse when the named tool is invoked.
4910
- * Requires stopToolResponseProperty to extract the result.
4911
- */
4912
- stopTool?: Callable;
4913
- /**
4914
- * Property to extract from the stop tool's result.
4915
- * Required when stopTool is set.
4916
- * The extracted value is used to determine the conversation outcome.
4917
- */
4918
- stopToolResponseProperty?: string;
4919
- /**
4920
- * Maximum steps for this side before forcing a stop.
4921
- * Safety limit to prevent runaway execution.
4922
- * A step is one complete LLM request/response cycle.
4923
- */
4924
- maxSteps?: number;
4925
- /**
4926
- * Tool that ends the entire session when called.
4927
- *
4928
- * Different from stopTool: this ends the session for both sides,
4929
- * not just this side's turn.
4930
- *
4931
- * Use object form to explicitly map message/attachment argument fields.
4932
- *
4933
- * In subagent runtimes, mapped message/attachments are the canonical
4934
- * completion payload returned from child -> parent.
4935
- */
4936
- sessionStop?: SessionToolBinding<Callable>;
4742
+ interface ThreadEndpointAuth {
4743
+ /** The authenticated caller, or `null` for an anonymous request. */
4744
+ caller: ThreadUser | null;
4745
+ /** Whether the caller satisfies the runtime's access policy for the requested thread. */
4746
+ threadAccess: boolean;
4747
+ }
4748
+ /**
4749
+ * Symbol used to identify thread endpoint handlers.
4750
+ *
4751
+ * When a handler is created with defineThreadEndpoint, it's marked with this
4752
+ * symbol so the runtime can detect it and wrap it with proper ThreadState injection.
4753
+ */
4754
+ declare const THREAD_ENDPOINT_SYMBOL: unique symbol;
4755
+ /**
4756
+ * A controller that has been marked as a thread endpoint.
4757
+ *
4758
+ * The runtime detects this marker and wraps the handler with ThreadState injection.
4759
+ * The `__handler` property contains the original handler function that expects
4760
+ * `(req: Request, state: ThreadState)` arguments.
4761
+ */
4762
+ interface MarkedThreadEndpoint extends Controller {
4763
+ /** Marker symbol indicating this is a thread endpoint */
4764
+ [THREAD_ENDPOINT_SYMBOL]: true;
4765
+ /** The original handler function before wrapping */
4766
+ __handler: ThreadEndpointHandler;
4767
+ }
4768
+ /**
4769
+ * Type guard to check if a controller is a marked thread endpoint.
4770
+ */
4771
+ declare function isThreadEndpoint(controller: Controller): controller is MarkedThreadEndpoint;
4772
+ /**
4773
+ * Virtual module loader function.
4774
+ *
4775
+ * Lazy-loads a module definition on demand.
4776
+ */
4777
+ type VirtualModuleLoader<T> = () => Promise<T>;
4778
+ /**
4779
+ * Registry of virtual modules by name.
4780
+ *
4781
+ * Maps module names to their lazy loaders.
4782
+ */
4783
+ type VirtualModuleRegistry<T> = Record<string, VirtualModuleLoader<T>>;
4784
+ /**
4785
+ * Controller context passed to route handlers.
4786
+ *
4787
+ * Contains the request, URL parameters, environment bindings,
4788
+ * and optionally the virtual module registries injected at runtime.
4789
+ *
4790
+ * Note: The `env` property contains implementation-specific bindings.
4791
+ * Its shape is defined by the runtime — typical surfaces include handles
4792
+ * to storage, queues, key-value stores, or other platform resources the
4793
+ * runtime chooses to expose.
4794
+ */
4795
+ interface ControllerContext {
4796
+ /** The incoming HTTP request */
4797
+ req: Request;
4937
4798
  /**
4938
- * Tool that fails the entire session when called.
4939
- *
4940
- * Use this when the side cannot fulfill the request and needs to terminate
4941
- * the session with an error outcome.
4942
- *
4943
- * When used inside a subagent, this failure is propagated back to the parent
4944
- * as a terminal failure result.
4945
- *
4946
- * Use object form to explicitly map message/attachment argument fields.
4799
+ * The authenticated caller, or `null` for an anonymous request.
4947
4800
  *
4948
- * In subagent runtimes, mapped message/attachments are the canonical
4949
- * failure payload returned from child -> parent.
4801
+ * Populated under the same contract as {@link ThreadEndpointAuth.caller}:
4802
+ * non-null whenever the request presented credentials the runtime
4803
+ * verified, and always non-null for a controller whose access level is
4804
+ * `authenticated`.
4950
4805
  */
4951
- sessionFail?: SessionToolBinding<Callable>;
4806
+ caller?: ThreadUser | null;
4807
+ /** URL path parameters extracted by the router */
4808
+ params: Record<string, string>;
4809
+ /** Parsed URL object */
4810
+ url: URL;
4952
4811
  /**
4953
- * Tool that publishes status updates to the parent thread when used as a subagent.
4954
- *
4955
- * When called from a `dual_ai` subagent, message text is forwarded to
4956
- * the parent thread's subagent registry as this instance's current status.
4957
- *
4958
- * Use object form to explicitly map message/attachment argument fields.
4959
- *
4960
- * Status updates do not terminate the session.
4812
+ * Environment bindings (implementation-specific).
4961
4813
  *
4962
- * @example
4963
- * ```typescript
4964
- * sideA: {
4965
- * prompt: 'research_worker',
4966
- * sessionStatus: { name: 'update_status', messageProperty: 'status' },
4967
- * }
4968
- * ```
4814
+ * Contains platform-specific bindings — handles to storage,
4815
+ * queues, key-value stores, or other resources the runtime exposes.
4816
+ * The exact contents depend on the runtime implementation.
4969
4817
  */
4970
- sessionStatus?: SessionToolBinding<Callable>;
4818
+ env: Record<string, unknown>;
4819
+ /** Registry of agent definitions (injected at runtime) */
4820
+ agents?: VirtualModuleRegistry<unknown>;
4821
+ /** Available agent names */
4822
+ agentNames?: string[];
4823
+ /** Registry of prompt definitions (injected at runtime) */
4824
+ prompts?: VirtualModuleRegistry<unknown>;
4825
+ /** Available prompt names */
4826
+ promptNames?: string[];
4827
+ /** Registry of model definitions (injected at runtime) */
4828
+ models?: VirtualModuleRegistry<unknown>;
4829
+ /** Available model names */
4830
+ modelNames?: string[];
4831
+ /** Registry of tool definitions (injected at runtime) */
4832
+ tools?: VirtualModuleRegistry<unknown>;
4833
+ /** Registry of hook definitions (injected at runtime) */
4834
+ hooks?: VirtualModuleRegistry<unknown>;
4835
+ /** Additional configuration (injected at runtime) */
4836
+ config?: Record<string, unknown>;
4971
4837
  }
4972
4838
  /**
4973
- * Agent definition configuration.
4839
+ * Controller return types.
4974
4840
  *
4975
- * @template N - The agent name as a string literal type
4976
- * @template Prompt - The prompt reference type (string or type-safe union)
4977
- * @template Callable - The callable reference type (string or type-safe union)
4841
+ * Controllers can return various types that are automatically
4842
+ * converted to appropriate HTTP responses.
4843
+ */
4844
+ type ControllerReturn = string | Promise<string> | Response | Promise<Response> | ReadableStream | Promise<ReadableStream> | null | Promise<null> | void | Promise<void> | Promise<object> | object;
4845
+ /**
4846
+ * Controller function type.
4847
+ *
4848
+ * Controllers handle HTTP requests and return responses.
4849
+ * The return value is automatically converted to a Response.
4978
4850
  *
4979
4851
  * @example
4980
4852
  * ```typescript
4981
- * import { defineAgent } from '@standardagents/spec';
4982
- *
4983
- * export default defineAgent({
4984
- * name: 'support_agent',
4985
- * title: 'Customer Support Agent',
4986
- * type: 'ai_human',
4987
- * sideA: {
4988
- * label: 'Support',
4989
- * prompt: 'customer_support',
4990
- * stopOnResponse: true,
4991
- * },
4992
- * });
4853
+ * const handler: Controller = async ({ req, params, url }) => {
4854
+ * return { message: 'Hello, World!' };
4855
+ * };
4993
4856
  * ```
4994
4857
  */
4995
- interface AgentDefinition<N extends string = string, Prompt extends string = StandardAgentSpec.Prompts, Callable extends string = StandardAgentSpec.Callables> {
4996
- /**
4997
- * Unique name for this agent.
4998
- * Used as the identifier for thread creation and handoffs.
4999
- * Should be snake_case (e.g., 'support_agent', 'research_flow').
5000
- */
5001
- name: N;
5002
- /**
5003
- * Human-readable title for the agent.
5004
- * Optional - if not provided, the name will be used.
5005
- * @deprecated Use name instead. Title will be removed in a future version.
5006
- */
5007
- title?: string;
5008
- /**
5009
- * Agent conversation type.
5010
- *
5011
- * - `ai_human`: AI conversing with a human user (default)
5012
- * - `dual_ai`: Two AI participants conversing
5013
- *
5014
- * @default 'ai_human'
5015
- */
5016
- type?: AgentType;
5017
- /**
5018
- * Maximum total turns across both sides.
5019
- * Only applies to `dual_ai` agents.
5020
- * Prevents infinite loops in AI-to-AI conversations.
5021
- */
5022
- maxSessionTurns?: number;
5023
- /**
5024
- * Configuration for Side A.
5025
- * For `ai_human`: This is the AI side.
5026
- * For `dual_ai`: This is the first AI participant.
5027
- */
5028
- sideA: SideConfig<Prompt, Callable>;
5029
- /**
5030
- * Configuration for Side B.
5031
- * For `ai_human`: Optional, the human side doesn't need config.
5032
- * For `dual_ai`: Required, the second AI participant.
5033
- */
5034
- sideB?: SideConfig<Prompt, Callable>;
5035
- /**
5036
- * Expose this agent as a tool for other prompts.
5037
- * Enables agent composition and handoffs.
5038
- * When true, other prompts can invoke this agent as a tool.
5039
- * @default false
5040
- */
5041
- exposeAsTool?: boolean;
5042
- /**
5043
- * Whether this agent is a PRIMARY, user-facing entry point (default true).
5044
- *
5045
- * Set `primary: false` on agents that exist only as internal handoff
5046
- * targets or infrastructure — e.g. the specialist variants of a
5047
- * domain-routing gearbox. Non-primary agents remain fully addressable
5048
- * (handoffs, invokeTool, thread resumption) and their threads remain
5049
- * visible; UIs use this flag to keep them out of top-level agent lists
5050
- * and pickers so one logical product agent doesn't show up N times.
5051
- */
5052
- primary?: boolean;
5053
- /**
5054
- * Description shown when agent is used as a tool.
5055
- * Required if exposeAsTool is true.
5056
- * Should clearly describe what this agent does.
5057
- */
5058
- toolDescription?: string;
5059
- /**
5060
- * Brief description of what this agent does.
5061
- * Useful for UIs and documentation.
5062
- *
5063
- * @example 'Handles customer support inquiries and resolves issues'
5064
- */
5065
- description?: string;
5066
- /**
5067
- * Icon URL or absolute path for the agent.
5068
- * Absolute paths (starting with `/`) are converted to full URLs in API responses.
5069
- *
5070
- * @example 'https://example.com/icon.svg' or '/icons/support.svg'
5071
- */
5072
- icon?: string;
5073
- /**
5074
- * Environment values provided by this agent.
5075
- * Agent values are lower priority than thread/account/instance values.
5076
- */
5077
- env?: Record<string, string>;
5078
- /**
5079
- * @deprecated Use `env` instead.
5080
- */
5081
- tenvs?: Record<string, unknown>;
5082
- /**
5083
- * npm package name for this agent when packed.
5084
- * Used by the packing system to maintain consistent package identity
5085
- * across pack/unpack cycles.
5086
- *
5087
- * @example 'standardagent-support-agent', '@myorg/support-agent'
5088
- */
5089
- packageName?: string;
5090
- /**
5091
- * Package version (semver format).
5092
- * Used by the packing system to track versions across pack/unpack cycles.
5093
- * When re-packing, this version is auto-incremented by the pack modal.
5094
- *
5095
- * @example '1.0.0', '2.3.1-beta.1'
5096
- */
5097
- version?: string;
5098
- /**
5099
- * Package author/copyright holder.
5100
- * Used by the packing system for the LICENSE file and package.json author field.
5101
- *
5102
- * @example 'John Doe', 'Acme Corp'
5103
- */
5104
- author?: string;
5105
- /**
5106
- * License identifier (SPDX format).
5107
- * Used by the packing system for LICENSE file generation.
5108
- *
5109
- * @example 'MIT', 'Apache-2.0', 'ISC'
5110
- */
5111
- license?: string;
5112
- /**
5113
- * Hook IDs to run for this agent.
5114
- * References hooks by their unique `id` property from defineHook().
5115
- * These run when prompts don't specify their own hooks.
5116
- *
5117
- * @example
5118
- * ```typescript
5119
- * hooks: ['log_messages', 'track_tool_usage']
5120
- * ```
5121
- */
5122
- hooks?: StandardAgentSpec.HookIds[];
5123
- }
4858
+ type Controller = (context: ControllerContext) => ControllerReturn;
5124
4859
  /**
5125
- * Defines an agent configuration.
4860
+ * Thread endpoint handler function.
5126
4861
  *
5127
- * Agents orchestrate conversations between AI models, or between AI and
5128
- * human users. They use prompts to configure each side of the conversation
5129
- * and define stop conditions to control conversation flow.
4862
+ * Receives the HTTP request and the ThreadState for the requested thread.
4863
+ * The supplied ThreadState includes the standard thread surface, including
4864
+ * `runCode`. The thread is automatically looked up by ID from URL parameters. Route
4865
+ * parameters are derived from the endpoint file path. For a dynamic segment
4866
+ * such as `[id].ts`, `params.id` contains the matched segment. For a catch-all
4867
+ * segment `[*].ts`, `params['*']` contains the unmatched path suffix,
4868
+ * including `/` separators when the match spans multiple segments.
5130
4869
  *
5131
- * @template N - The agent name as a string literal type
5132
- * @param options - Agent configuration options
5133
- * @returns The agent definition for registration
4870
+ * Thread endpoint routes are scoped beneath the runtime's thread ID prefix.
4871
+ * A request under that prefix that does not match any local or packed thread
4872
+ * endpoint route MUST return HTTP 404.
4873
+ *
4874
+ * The fourth argument carries the request's authentication context under the
4875
+ * contract defined by {@link ThreadEndpointAuth}. Handlers that do not
4876
+ * consume it may omit the parameter.
4877
+ */
4878
+ type ThreadEndpointRouteParams = Record<string, string>;
4879
+ type ThreadEndpointHandler = (req: Request, state: ThreadState, params: ThreadEndpointRouteParams, auth: ThreadEndpointAuth) => Response | Promise<Response>;
4880
+ /**
4881
+ * Define a standard HTTP controller.
4882
+ *
4883
+ * Controllers handle HTTP requests and return responses. They have
4884
+ * access to the controller context including virtual module registries.
4885
+ *
4886
+ * A controller's access level is declared through `options.access` and
4887
+ * defaults to `authenticated`; see {@link EndpointAccess} for the
4888
+ * requirements each level places on the runtime.
4889
+ *
4890
+ * @param controller - The controller function
4891
+ * @param options - Endpoint options, including the access level
4892
+ * @returns The controller for registration
5134
4893
  *
5135
4894
  * @example
5136
4895
  * ```typescript
5137
- * // agents/agents/support_agent.ts
5138
- * import { defineAgent } from '@standardagents/spec';
4896
+ * // agents/api/health.get.ts
4897
+ * import { defineController } from '@standardagents/spec';
5139
4898
  *
5140
- * export default defineAgent({
5141
- * name: 'support_agent',
5142
- * title: 'Customer Support Agent',
5143
- * type: 'ai_human',
5144
- * sideA: {
5145
- * label: 'Support',
5146
- * prompt: 'customer_support',
5147
- * stopOnResponse: true,
5148
- * sessionStop: 'close_ticket',
5149
- * },
5150
- * exposeAsTool: true,
5151
- * toolDescription: 'Hand off to customer support',
4899
+ * export default defineController(async () => {
4900
+ * return { status: 'ok', timestamp: Date.now() };
5152
4901
  * });
5153
4902
  * ```
5154
4903
  *
5155
4904
  * @example
5156
4905
  * ```typescript
5157
- * // Dual AI agent (two AIs conversing)
5158
- * export default defineAgent({
5159
- * name: 'debate_agent',
5160
- * title: 'AI Debate',
5161
- * type: 'dual_ai',
5162
- * maxSessionTurns: 10,
5163
- * sideA: {
5164
- * label: 'Pro',
5165
- * prompt: 'debate_pro',
5166
- * stopOnResponse: true,
5167
- * },
5168
- * sideB: {
5169
- * label: 'Con',
5170
- * prompt: 'debate_con',
5171
- * stopOnResponse: true,
5172
- * sessionStop: 'conclude_debate',
5173
- * },
4906
+ * // agents/api/agents/index.get.ts
4907
+ * import { defineController } from '@standardagents/spec';
4908
+ *
4909
+ * export default defineController(async ({ agentNames }) => {
4910
+ * return { agents: agentNames };
5174
4911
  * });
5175
4912
  * ```
5176
4913
  */
5177
- declare function defineAgent<N extends string>(options: AgentDefinition<N>): AgentDefinition<N>;
5178
-
4914
+ declare function defineController(controller: Controller, options?: EndpointOptions): Controller;
5179
4915
  /**
5180
- * Endpoint definition types for Standard Agents.
4916
+ * Define a thread-specific endpoint.
5181
4917
  *
5182
- * Endpoints expose HTTP APIs for interacting with agent threads.
5183
- * They can be standard controllers or thread-specific endpoints
5184
- * with automatic ThreadState injection.
4918
+ * Thread endpoints automatically look up the thread by ID from URL params
4919
+ * and provide access to the ThreadState. The handler receives full
4920
+ * ThreadState with messages, logs, resource loading, event emission,
4921
+ * and (when executing) execution state.
5185
4922
  *
5186
- * @module
5187
- */
5188
-
5189
- /**
5190
- * The access level of an endpoint.
4923
+ * Runtimes MUST return HTTP 404 for requests beneath the thread endpoint
4924
+ * prefix that do not match any local or packed thread endpoint route.
5191
4925
  *
5192
- * - `authenticated` — the runtime MUST authenticate the request before
5193
- * invoking the handler, and for thread endpoints MUST additionally enforce
5194
- * its thread access policy. Requests that fail authentication or
5195
- * authorization MUST be rejected without invoking the handler.
5196
- * - `public` — the runtime MUST invoke the handler regardless of the
5197
- * request's authentication state. The runtime MUST still attempt caller
5198
- * resolution: a request presenting credentials the runtime can verify is
5199
- * attributed to that caller, and any other request is treated as anonymous
5200
- * rather than rejected.
4926
+ * A thread endpoint's access level is declared through `options.access` and
4927
+ * defaults to `authenticated`; see {@link EndpointAccess} for the
4928
+ * requirements each level places on the runtime. Handlers receive the
4929
+ * request's authentication context as their fourth argument under the
4930
+ * contract defined by {@link ThreadEndpointAuth}.
5201
4931
  *
5202
- * When no access level is declared, the access level is `authenticated`.
5203
- */
5204
- type EndpointAccess = 'authenticated' | 'public';
5205
- /**
5206
- * Symbol used to carry an endpoint's declared access level.
4932
+ * @param handler - The handler function receiving request, ThreadState, route params, and authentication context
4933
+ * @param options - Endpoint options, including the access level
4934
+ * @returns A Controller that can be used with the router
5207
4935
  *
5208
- * The declaration is attached to the handler at definition time so that
5209
- * runtimes can read it before routing the request through their
5210
- * authentication layer.
5211
- */
5212
- declare const ENDPOINT_ACCESS_SYMBOL: unique symbol;
5213
- /**
5214
- * Options accepted when defining an endpoint.
5215
- */
5216
- interface EndpointOptions {
5217
- /**
5218
- * The endpoint's access level. Defaults to `authenticated`.
5219
- */
5220
- access?: EndpointAccess;
5221
- }
5222
- /**
5223
- * Resolve the declared access level of an endpoint handler.
4936
+ * @example
4937
+ * ```typescript
4938
+ * // agents/api/status.ts
4939
+ * import { defineThreadEndpoint } from '@standardagents/spec';
5224
4940
  *
5225
- * Returns `authenticated` when the value carries no declaration.
5226
- */
5227
- declare function getEndpointAccess(value: unknown): EndpointAccess;
5228
- /**
5229
- * The authentication context supplied to a thread endpoint handler.
4941
+ * export default defineThreadEndpoint(async (req, state) => {
4942
+ * const { messages, total } = await state.getMessages({ limit: 1 });
4943
+ * return Response.json({
4944
+ * threadId: state.threadId,
4945
+ * agent: state.agentId,
4946
+ * messageCount: total,
4947
+ * });
4948
+ * });
4949
+ * ```
5230
4950
  *
5231
- * The runtime constructs this context for every invocation:
5232
- *
5233
- * - `caller` MUST be the authenticated identity when the request presented
5234
- * credentials the runtime verified, and `null` otherwise. For an endpoint
5235
- * whose access level is `authenticated`, `caller` is always non-null.
5236
- * - `threadAccess` MUST be `true` if and only if the runtime's thread access
5237
- * policy grants the resolved caller access to the requested thread. It
5238
- * MUST be `false` when `caller` is `null`. For an endpoint whose access
5239
- * level is `authenticated`, `threadAccess` is always `true`.
5240
- *
5241
- * The mechanism by which a runtime authenticates requests is
5242
- * implementation-defined; this interface defines only the identity contract
5243
- * observable by endpoint code.
5244
- */
5245
- interface ThreadEndpointAuth {
5246
- /** The authenticated caller, or `null` for an anonymous request. */
5247
- caller: ThreadUser | null;
5248
- /** Whether the caller satisfies the runtime's access policy for the requested thread. */
5249
- threadAccess: boolean;
5250
- }
5251
- /**
5252
- * Symbol used to identify thread endpoint handlers.
5253
- *
5254
- * When a handler is created with defineThreadEndpoint, it's marked with this
5255
- * symbol so the runtime can detect it and wrap it with proper ThreadState injection.
5256
- */
5257
- declare const THREAD_ENDPOINT_SYMBOL: unique symbol;
5258
- /**
5259
- * A controller that has been marked as a thread endpoint.
5260
- *
5261
- * The runtime detects this marker and wraps the handler with ThreadState injection.
5262
- * The `__handler` property contains the original handler function that expects
5263
- * `(req: Request, state: ThreadState)` arguments.
5264
- */
5265
- interface MarkedThreadEndpoint extends Controller {
5266
- /** Marker symbol indicating this is a thread endpoint */
5267
- [THREAD_ENDPOINT_SYMBOL]: true;
5268
- /** The original handler function before wrapping */
5269
- __handler: ThreadEndpointHandler;
5270
- }
5271
- /**
5272
- * Type guard to check if a controller is a marked thread endpoint.
5273
- */
5274
- declare function isThreadEndpoint(controller: Controller): controller is MarkedThreadEndpoint;
5275
- /**
5276
- * Virtual module loader function.
5277
- *
5278
- * Lazy-loads a module definition on demand.
5279
- */
5280
- type VirtualModuleLoader<T> = () => Promise<T>;
5281
- /**
5282
- * Registry of virtual modules by name.
5283
- *
5284
- * Maps module names to their lazy loaders.
5285
- */
5286
- type VirtualModuleRegistry<T> = Record<string, VirtualModuleLoader<T>>;
5287
- /**
5288
- * Controller context passed to route handlers.
5289
- *
5290
- * Contains the request, URL parameters, environment bindings,
5291
- * and optionally the virtual module registries injected at runtime.
5292
- *
5293
- * Note: The `env` property contains implementation-specific bindings.
5294
- * Its shape is defined by the runtime — typical surfaces include handles
5295
- * to storage, queues, key-value stores, or other platform resources the
5296
- * runtime chooses to expose.
5297
- */
5298
- interface ControllerContext {
5299
- /** The incoming HTTP request */
5300
- req: Request;
5301
- /**
5302
- * The authenticated caller, or `null` for an anonymous request.
5303
- *
5304
- * Populated under the same contract as {@link ThreadEndpointAuth.caller}:
5305
- * non-null whenever the request presented credentials the runtime
5306
- * verified, and always non-null for a controller whose access level is
5307
- * `authenticated`.
5308
- */
5309
- caller?: ThreadUser | null;
5310
- /** URL path parameters extracted by the router */
5311
- params: Record<string, string>;
5312
- /** Parsed URL object */
5313
- url: URL;
5314
- /**
5315
- * Environment bindings (implementation-specific).
5316
- *
5317
- * Contains platform-specific bindings — handles to storage,
5318
- * queues, key-value stores, or other resources the runtime exposes.
5319
- * The exact contents depend on the runtime implementation.
5320
- */
5321
- env: Record<string, unknown>;
5322
- /** Registry of agent definitions (injected at runtime) */
5323
- agents?: VirtualModuleRegistry<unknown>;
5324
- /** Available agent names */
5325
- agentNames?: string[];
5326
- /** Registry of prompt definitions (injected at runtime) */
5327
- prompts?: VirtualModuleRegistry<unknown>;
5328
- /** Available prompt names */
5329
- promptNames?: string[];
5330
- /** Registry of model definitions (injected at runtime) */
5331
- models?: VirtualModuleRegistry<unknown>;
5332
- /** Available model names */
5333
- modelNames?: string[];
5334
- /** Registry of tool definitions (injected at runtime) */
5335
- tools?: VirtualModuleRegistry<unknown>;
5336
- /** Registry of hook definitions (injected at runtime) */
5337
- hooks?: VirtualModuleRegistry<unknown>;
5338
- /** Additional configuration (injected at runtime) */
5339
- config?: Record<string, unknown>;
5340
- }
5341
- /**
5342
- * Controller return types.
5343
- *
5344
- * Controllers can return various types that are automatically
5345
- * converted to appropriate HTTP responses.
5346
- */
5347
- type ControllerReturn = string | Promise<string> | Response | Promise<Response> | ReadableStream | Promise<ReadableStream> | null | Promise<null> | void | Promise<void> | Promise<object> | object;
5348
- /**
5349
- * Controller function type.
5350
- *
5351
- * Controllers handle HTTP requests and return responses.
5352
- * The return value is automatically converted to a Response.
5353
- *
5354
- * @example
5355
- * ```typescript
5356
- * const handler: Controller = async ({ req, params, url }) => {
5357
- * return { message: 'Hello, World!' };
5358
- * };
5359
- * ```
5360
- */
5361
- type Controller = (context: ControllerContext) => ControllerReturn;
5362
- /**
5363
- * Thread endpoint handler function.
5364
- *
5365
- * Receives the HTTP request and the ThreadState for the requested thread.
5366
- * The supplied ThreadState includes the standard thread surface, including
5367
- * `runCode`. The thread is automatically looked up by ID from URL parameters. Route
5368
- * parameters are derived from the endpoint file path. For a dynamic segment
5369
- * such as `[id].ts`, `params.id` contains the matched segment. For a catch-all
5370
- * segment `[*].ts`, `params['*']` contains the unmatched path suffix,
5371
- * including `/` separators when the match spans multiple segments.
5372
- *
5373
- * Thread endpoint routes are scoped beneath the runtime's thread ID prefix.
5374
- * A request under that prefix that does not match any local or packed thread
5375
- * endpoint route MUST return HTTP 404.
5376
- *
5377
- * The fourth argument carries the request's authentication context under the
5378
- * contract defined by {@link ThreadEndpointAuth}. Handlers that do not
5379
- * consume it may omit the parameter.
5380
- */
5381
- type ThreadEndpointRouteParams = Record<string, string>;
5382
- type ThreadEndpointHandler = (req: Request, state: ThreadState, params: ThreadEndpointRouteParams, auth: ThreadEndpointAuth) => Response | Promise<Response>;
5383
- /**
5384
- * Define a standard HTTP controller.
5385
- *
5386
- * Controllers handle HTTP requests and return responses. They have
5387
- * access to the controller context including virtual module registries.
5388
- *
5389
- * A controller's access level is declared through `options.access` and
5390
- * defaults to `authenticated`; see {@link EndpointAccess} for the
5391
- * requirements each level places on the runtime.
5392
- *
5393
- * @param controller - The controller function
5394
- * @param options - Endpoint options, including the access level
5395
- * @returns The controller for registration
5396
- *
5397
- * @example
5398
- * ```typescript
5399
- * // agents/api/health.get.ts
5400
- * import { defineController } from '@standardagents/spec';
5401
- *
5402
- * export default defineController(async () => {
5403
- * return { status: 'ok', timestamp: Date.now() };
5404
- * });
5405
- * ```
5406
- *
5407
- * @example
5408
- * ```typescript
5409
- * // agents/api/agents/index.get.ts
5410
- * import { defineController } from '@standardagents/spec';
5411
- *
5412
- * export default defineController(async ({ agentNames }) => {
5413
- * return { agents: agentNames };
5414
- * });
5415
- * ```
5416
- */
5417
- declare function defineController(controller: Controller, options?: EndpointOptions): Controller;
5418
- /**
5419
- * Define a thread-specific endpoint.
5420
- *
5421
- * Thread endpoints automatically look up the thread by ID from URL params
5422
- * and provide access to the ThreadState. The handler receives full
5423
- * ThreadState with messages, logs, resource loading, event emission,
5424
- * and (when executing) execution state.
5425
- *
5426
- * Runtimes MUST return HTTP 404 for requests beneath the thread endpoint
5427
- * prefix that do not match any local or packed thread endpoint route.
5428
- *
5429
- * A thread endpoint's access level is declared through `options.access` and
5430
- * defaults to `authenticated`; see {@link EndpointAccess} for the
5431
- * requirements each level places on the runtime. Handlers receive the
5432
- * request's authentication context as their fourth argument under the
5433
- * contract defined by {@link ThreadEndpointAuth}.
5434
- *
5435
- * @param handler - The handler function receiving request, ThreadState, route params, and authentication context
5436
- * @param options - Endpoint options, including the access level
5437
- * @returns A Controller that can be used with the router
5438
- *
5439
- * @example
5440
- * ```typescript
5441
- * // agents/api/status.ts
5442
- * import { defineThreadEndpoint } from '@standardagents/spec';
5443
- *
5444
- * export default defineThreadEndpoint(async (req, state) => {
5445
- * const { messages, total } = await state.getMessages({ limit: 1 });
5446
- * return Response.json({
5447
- * threadId: state.threadId,
5448
- * agent: state.agentId,
5449
- * messageCount: total,
5450
- * });
5451
- * });
5452
- * ```
5453
- *
5454
- * @example
5455
- * ```typescript
5456
- * // agents/api/export.ts
5457
- * import { defineThreadEndpoint } from '@standardagents/spec';
4951
+ * @example
4952
+ * ```typescript
4953
+ * // agents/api/export.ts
4954
+ * import { defineThreadEndpoint } from '@standardagents/spec';
5458
4955
  *
5459
4956
  * export default defineThreadEndpoint(async (req, state) => {
5460
4957
  * const { messages } = await state.getMessages();
@@ -5562,167 +5059,749 @@ interface GlobalNamespaceContext {
5562
5059
  type: 'global';
5563
5060
  }
5564
5061
  /**
5565
- * Packed namespace context - for code inside a packed agent.
5566
- *
5567
- * Packed code sees:
5568
- * - Only items within its own package (by packageId)
5569
- * - Other packed agent entry points (for handoffs)
5570
- * - NOT unpacked global code
5062
+ * Packed namespace context - for code inside a packed agent.
5063
+ *
5064
+ * Packed code sees:
5065
+ * - Only items within its own package (by packageId)
5066
+ * - Other packed agent entry points (for handoffs)
5067
+ * - NOT unpacked global code
5068
+ */
5069
+ interface PackedNamespaceContext {
5070
+ type: 'packed';
5071
+ /** Package ID this code belongs to */
5072
+ packageId: string;
5073
+ }
5074
+ /**
5075
+ * Namespace context for execution.
5076
+ * Determines what items are visible during execution.
5077
+ *
5078
+ * @example
5079
+ * ```typescript
5080
+ * // Global namespace (unpacked code)
5081
+ * const globalNs: NamespaceContext = { type: 'global' };
5082
+ *
5083
+ * // Packed namespace (inside a packed agent)
5084
+ * const packedNs: NamespaceContext = {
5085
+ * type: 'packed',
5086
+ * packageId: 'standardagent-my-workflow',
5087
+ * };
5088
+ * ```
5089
+ */
5090
+ type NamespaceContext = GlobalNamespaceContext | PackedNamespaceContext;
5091
+ /**
5092
+ * Extended metadata for packed definitions.
5093
+ *
5094
+ * This interface is added to agent, prompt, tool, model, and hook
5095
+ * definitions when they are packed. It marks them as belonging
5096
+ * to a specific package and optionally as readonly.
5097
+ */
5098
+ interface PackedMetadata {
5099
+ /**
5100
+ * Package signature if this definition is part of a packed agent.
5101
+ * Undefined for unpacked (global) definitions.
5102
+ */
5103
+ __package?: PackageSignature;
5104
+ /**
5105
+ * Whether this definition is readonly (cannot be edited in UI).
5106
+ * Packed definitions are typically readonly.
5107
+ */
5108
+ __readonly?: boolean;
5109
+ }
5110
+ /**
5111
+ * Helper type to check if a definition is packed.
5112
+ */
5113
+ declare function isPacked(metadata: PackedMetadata | undefined): metadata is PackedMetadata & {
5114
+ __package: PackageSignature;
5115
+ };
5116
+ /**
5117
+ * Helper to check if a definition belongs to a specific package.
5118
+ */
5119
+ declare function belongsToPackage(metadata: PackedMetadata | undefined, packageId: string): boolean;
5120
+ /**
5121
+ * Helper to check if a definition is visible in a given namespace.
5122
+ *
5123
+ * @param metadata - The definition's packed metadata
5124
+ * @param namespace - The current namespace context
5125
+ * @param isEntryPoint - Whether this is an agent with exposeAsTool: true
5126
+ */
5127
+ declare function isVisibleInNamespace(metadata: PackedMetadata | undefined, namespace: NamespaceContext, isEntryPoint?: boolean): boolean;
5128
+ /**
5129
+ * Metadata export for packed packages.
5130
+ *
5131
+ * This is exported as `__meta` from the packed package's index.ts,
5132
+ * replacing the manifest.json file from the old format.
5133
+ *
5134
+ * A packed package's required execution behavior must use capabilities
5135
+ * defined by the Standard Agent Specification. It must not depend on private
5136
+ * runtime RPCs, storage schemas, bindings, undocumented events, sidecars that
5137
+ * replace runtime primitives, or `ThreadState._notPackableRuntimeContext`.
5138
+ *
5139
+ * @example
5140
+ * ```typescript
5141
+ * export const __meta: PackedMeta = {
5142
+ * packageId: 'standardagent-my-workflow',
5143
+ * version: '1.0.0',
5144
+ * entryAgents: ['my_agent'],
5145
+ * packedAt: 1705012800000,
5146
+ * };
5147
+ * ```
5148
+ */
5149
+ interface PackedMeta {
5150
+ /** Unique package identifier (npm name or local identifier) */
5151
+ packageId: string;
5152
+ /** Package version (semver) */
5153
+ version: string;
5154
+ /** Entry agents exposed as tools (visible from outside the package) */
5155
+ entryAgents: string[];
5156
+ /** When the package was packed (timestamp in milliseconds) */
5157
+ packedAt: number;
5158
+ /** Optional description from the entry agent */
5159
+ description?: string;
5160
+ /**
5161
+ * Required parent-hook disclosures keyed by entry agent name.
5162
+ * This duplicates the executable agent definitions so installers can show
5163
+ * composition effects without importing package code.
5164
+ */
5165
+ parentHooks?: Record<string, ParentHookRequirement[]>;
5166
+ }
5167
+ /**
5168
+ * Lazy loader type for packed definitions.
5169
+ *
5170
+ * Each definition in a packed package is wrapped in a function
5171
+ * that returns a Promise, enabling lazy loading.
5172
+ *
5173
+ * @example
5174
+ * ```typescript
5175
+ * export const tools = {
5176
+ * search: async (): Promise<ToolDefinition> => ({
5177
+ * description: 'Search for information',
5178
+ * execute: async (state, args) => { ... },
5179
+ * }),
5180
+ * } as const;
5181
+ * ```
5182
+ */
5183
+ type DefinitionLoader<T> = () => Promise<T>;
5184
+
5185
+ /**
5186
+ * Standard shape for packed package exports.
5187
+ *
5188
+ * Every packed package exports this structure, enabling
5189
+ * consistent discovery and loading.
5190
+ *
5191
+ * @example
5192
+ * ```typescript
5193
+ * import type { PackedExports } from '@standardagents/spec';
5194
+ *
5195
+ * const pkg: PackedExports = await import('standardagent-my-workflow');
5196
+ * const tool = await pkg.tools.search();
5197
+ * ```
5198
+ */
5199
+ interface PackedExports {
5200
+ /** Agent definitions, keyed by name */
5201
+ agents: Record<string, DefinitionLoader<AgentDefinition>>;
5202
+ /** Prompt definitions, keyed by name */
5203
+ prompts: Record<string, DefinitionLoader<PromptDefinition>>;
5204
+ /** Tool definitions, keyed by name */
5205
+ tools: Record<string, DefinitionLoader<ToolDefinition<unknown, any, any>>>;
5206
+ /** Model definitions, keyed by name */
5207
+ models: Record<string, DefinitionLoader<ModelDefinition>>;
5208
+ /** Hook definitions, keyed by name */
5209
+ hooks: Record<string, DefinitionLoader<HookDefinitionResult<HookName>>>;
5210
+ /**
5211
+ * Effect definitions, keyed by name.
5212
+ *
5213
+ * Optional for backward compatibility with previously packed packages.
5214
+ */
5215
+ effects?: Record<string, DefinitionLoader<EffectDefinition<any, any>>>;
5216
+ /**
5217
+ * Thread endpoint handlers, keyed by packed route key (for example, `status.get`).
5218
+ *
5219
+ * Runtimes mount these beneath the same thread ID prefix as local thread
5220
+ * endpoints, using the packed route key to derive the endpoint path.
5221
+ * Requests beneath that prefix that do not match any local or packed thread
5222
+ * endpoint route MUST return HTTP 404.
5223
+ *
5224
+ * Optional for backward compatibility with previously packed packages.
5225
+ */
5226
+ threadEndpoints?: Record<string, DefinitionLoader<MarkedThreadEndpoint | Controller>>;
5227
+ /** Package metadata */
5228
+ __meta: PackedMeta;
5229
+ }
5230
+
5231
+ /**
5232
+ * Hook definition types for Standard Agents.
5233
+ *
5234
+ * Hooks allow intercepting and modifying agent behavior at key points
5235
+ * in the execution lifecycle. They enable logging, validation,
5236
+ * transformation, and side effects.
5237
+ *
5238
+ * Thread hooks receive ThreadState as their first parameter and deterministic
5239
+ * execution provenance as their final parameter, providing full access to
5240
+ * thread operations and the reason each hook is active.
5241
+ *
5242
+ * @module
5243
+ */
5244
+
5245
+ /**
5246
+ * Hook context is ThreadState.
5247
+ *
5248
+ * Hooks receive the full ThreadState, which includes identity, message access,
5249
+ * resource loading, event emission, and execution state (when available).
5250
+ *
5251
+ * @example
5252
+ * ```typescript
5253
+ * const hook = defineHook({
5254
+ * hook: 'filter_messages',
5255
+ * id: 'keep_recent',
5256
+ * execute: async (state, messages) => {
5257
+ * console.log(`Thread: ${state.threadId}`);
5258
+ * if (state.execution) {
5259
+ * console.log(`Step: ${state.execution.stepCount}`);
5260
+ * }
5261
+ * return messages.slice(-10);
5262
+ * },
5263
+ * });
5264
+ * ```
5265
+ */
5266
+ type HookContext = ThreadState;
5267
+ /**
5268
+ * Describes why a thread-scoped hook is active for the current invocation.
5269
+ *
5270
+ * Parent hooks contributed by a child agent still receive the parent's
5271
+ * ThreadState. The child source additionally identifies the exact relationship
5272
+ * that activated the hook, including the exposed relationship name that may be
5273
+ * passed to `state.invokeTool()` or `state.queueTool()`.
5274
+ */
5275
+ type HookExecutionSource = {
5276
+ kind: 'prompt';
5277
+ /** Active prompt whose hook list selected this hook. */
5278
+ promptName: string;
5279
+ } | {
5280
+ kind: 'agent';
5281
+ /** Active agent whose fallback hook list selected this hook. */
5282
+ agentName: string;
5283
+ } | {
5284
+ kind: 'child';
5285
+ /** Model/runtime-facing name of the direct parent-child relationship. */
5286
+ relationshipName: string;
5287
+ /** Implementing child agent reference after alias resolution. */
5288
+ agentName: string;
5289
+ /** Package provenance when the child came from a packed package. */
5290
+ package?: PackageSignature;
5291
+ };
5292
+ /**
5293
+ * Final argument supplied to every thread-scoped hook invocation.
5294
+ */
5295
+ interface HookExecutionContext {
5296
+ source: HookExecutionSource;
5297
+ }
5298
+ /**
5299
+ * Message structure for hook processing.
5300
+ *
5301
+ * Re-exported from threads.ts for convenience.
5302
+ * @see Message
5303
+ */
5304
+ type HookMessage = Message;
5305
+ /**
5306
+ * Tool call structure for hook processing.
5307
+ */
5308
+ interface HookToolCall {
5309
+ /** Unique tool call identifier */
5310
+ id: string;
5311
+ /** Always 'function' for tool calls */
5312
+ type: 'function';
5313
+ /** Function details */
5314
+ function: {
5315
+ /** Tool name */
5316
+ name: string;
5317
+ /** JSON-encoded arguments */
5318
+ arguments: string;
5319
+ };
5320
+ }
5321
+ /**
5322
+ * Tool result structure for hook processing.
5323
+ */
5324
+ interface HookToolResult {
5325
+ /** Execution status */
5326
+ status: 'success' | 'error';
5327
+ /** Result string (for successful executions) */
5328
+ result?: string;
5329
+ /** Error message (for failed executions) */
5330
+ error?: string;
5331
+ /** Stack trace (for debugging) */
5332
+ stack?: string;
5333
+ /**
5334
+ * File attachments returned by the tool.
5335
+ *
5336
+ * Can contain either:
5337
+ * - ToolAttachment: New files with base64 data to be stored
5338
+ * - AttachmentRef: References to existing files in the thread filesystem
5339
+ */
5340
+ attachments?: Array<ToolAttachment | AttachmentRef>;
5341
+ }
5342
+ /**
5343
+ * LLM message format for prefilter hook.
5344
+ *
5345
+ * Messages passed to the prefilter_llm_history hook are in chat completion
5346
+ * format. Content can be a plain string or a multimodal array (for messages
5347
+ * with images). Additional provider-specific fields may also be present.
5348
+ */
5349
+ interface LLMMessage {
5350
+ /** Message role */
5351
+ role: string;
5352
+ /** Message content (string for text-only, array for multimodal) */
5353
+ content?: string | null | unknown[];
5354
+ /** Tool calls (parsed) */
5355
+ tool_calls?: unknown;
5356
+ /** Tool call ID */
5357
+ tool_call_id?: string;
5358
+ /** Tool name */
5359
+ name?: string;
5360
+ /** Reasoning content (for models like o1) */
5361
+ reasoning_content?: string;
5362
+ /** Allow additional provider-specific fields */
5363
+ [key: string]: unknown;
5364
+ }
5365
+ /**
5366
+ * Hook signatures for all available hooks.
5367
+ *
5368
+ * Each hook has a specific signature that defines when it's called
5369
+ * and what data it receives. Hooks can modify data by returning
5370
+ * transformed values or perform side effects.
5371
+ *
5372
+ * All thread hooks receive ThreadState as their first parameter and
5373
+ * HookExecutionContext as their final parameter.
5374
+ *
5375
+ * @template State - The state type (defaults to ThreadState)
5376
+ * @template Msg - The message type (defaults to HookMessage)
5377
+ * @template ToolCall - The tool call type (defaults to HookToolCall)
5378
+ * @template ToolResult - The tool result type (defaults to HookToolResult)
5379
+ */
5380
+ interface HookSignatures<State = ThreadState, Msg = HookMessage, ToolCall = HookToolCall, ToolResult = HookToolResult, Context = HookExecutionContext> {
5381
+ /**
5382
+ * Called after a thread is created, before initial message processing or LLM
5383
+ * requests for that thread.
5384
+ *
5385
+ * Only ThreadState is passed. Use for initializing durable thread values,
5386
+ * env placeholders, files, or side-effect instrumentation.
5387
+ *
5388
+ * @param state - Created thread state
5389
+ */
5390
+ after_thread_created: (state: State, context: Context) => Promise<void>;
5391
+ /**
5392
+ * Called on the parent thread immediately after a subagent child thread is
5393
+ * created.
5394
+ *
5395
+ * Receives parent ThreadState and child ThreadState. Use for copying or
5396
+ * linking metadata, bookkeeping, or side-effect instrumentation.
5397
+ *
5398
+ * @param state - Parent thread state
5399
+ * @param childState - Created child thread state
5400
+ */
5401
+ after_subagent_created: (state: State, childState: State, context: Context) => Promise<void>;
5402
+ /**
5403
+ * Called after the prompt system message is rendered for a model request.
5404
+ *
5405
+ * Return a string to replace the rendered system message. Return null or
5406
+ * undefined to keep the original message.
5407
+ *
5408
+ * @param state - Thread state
5409
+ * @param systemMessage - Rendered system message for this request
5410
+ * @returns Replacement system message, or null/undefined for no change
5411
+ */
5412
+ after_system_message: (state: State, systemMessage: string, context: Context) => string | Promise<string | null | undefined> | null | undefined;
5413
+ /**
5414
+ * Called exactly once per model-context assembly on a stable snapshot of
5415
+ * eligible persisted history.
5416
+ *
5417
+ * Receives raw message rows from storage before any transformation.
5418
+ * Use for filtering, sorting, or augmenting message history.
5419
+ * The result is the request context and does not replace
5420
+ * `state.execution.messageHistory`. Messages injected while this hook runs
5421
+ * become eligible for the following context assembly.
5422
+ *
5423
+ * @param state - Thread state
5424
+ * @param messages - Array of messages from storage
5425
+ * @returns Modified message array
5426
+ *
5427
+ * @example
5428
+ * ```typescript
5429
+ * defineHook({
5430
+ * hook: 'filter_messages',
5431
+ * id: 'limit_to_10',
5432
+ * execute: async (state, messages) => messages.slice(-10),
5433
+ * });
5434
+ * ```
5435
+ */
5436
+ filter_messages: (state: State, messages: Msg[], context: Context) => Promise<Msg[]>;
5437
+ /**
5438
+ * Called after filtered history is transformed into provider messages and
5439
+ * before dispatch.
5440
+ *
5441
+ * Receives messages already transformed into chat completion format.
5442
+ * Use for final request-local adjustments. Messages injected while this
5443
+ * hook runs become eligible for the following context assembly.
5444
+ *
5445
+ * @param state - Thread state
5446
+ * @param messages - Array of LLM-formatted messages
5447
+ * @returns Modified LLM message array
5448
+ *
5449
+ * @example
5450
+ * ```typescript
5451
+ * defineHook({
5452
+ * hook: 'prefilter_llm_history',
5453
+ * id: 'add_reminder',
5454
+ * execute: async (state, messages) => messages,
5455
+ * });
5456
+ * ```
5457
+ */
5458
+ prefilter_llm_history: (state: State, messages: LLMMessage[], context: Context) => Promise<LLMMessage[]>;
5459
+ /**
5460
+ * Called before a message is created in the database.
5461
+ *
5462
+ * Receives the full message object before insertion. Return modified
5463
+ * message to transform before storage.
5464
+ *
5465
+ * A hook that throws during `injectMessage()` rejects the injection and
5466
+ * no message is persisted. Other runtime insertions log the failure and
5467
+ * keep the last good message.
5468
+ *
5469
+ * @param state - Thread state
5470
+ * @param message - Message to be created
5471
+ * @returns Modified message
5472
+ *
5473
+ * @example
5474
+ * ```typescript
5475
+ * defineHook({
5476
+ * hook: 'before_create_message',
5477
+ * id: 'prefix_content',
5478
+ * execute: async (state, message) => {
5479
+ * // message has full type: id, role, content, created_at, etc.
5480
+ * if (message.role === 'user' && message.content) {
5481
+ * return { ...message, content: `[user] ${message.content}` };
5482
+ * }
5483
+ * return message;
5484
+ * }
5485
+ * });
5486
+ * ```
5487
+ */
5488
+ before_create_message: (state: State, message: Msg, context: Context) => Promise<Msg>;
5489
+ /**
5490
+ * Called after a message is created in the database.
5491
+ *
5492
+ * Use for logging, analytics, or triggering side effects after
5493
+ * message creation. Cannot modify the message.
5494
+ *
5495
+ * @param state - Thread state
5496
+ * @param message - The created message
5497
+ *
5498
+ * @example
5499
+ * ```typescript
5500
+ * defineHook({
5501
+ * hook: 'after_create_message',
5502
+ * id: 'log_creation',
5503
+ * execute: async (state, message) => {
5504
+ * console.log(`Created ${message.role} message: ${message.id}`);
5505
+ * }
5506
+ * });
5507
+ * ```
5508
+ */
5509
+ after_create_message: (state: State, message: Msg, context: Context) => Promise<void>;
5510
+ /**
5511
+ * Called before a message is updated in the database.
5512
+ *
5513
+ * Receives the message ID and update data. Return modified updates
5514
+ * to transform the changes before storage.
5515
+ *
5516
+ * @param state - Thread state
5517
+ * @param messageId - ID of message being updated
5518
+ * @param updates - Update data
5519
+ * @returns Modified update data
5520
+ *
5521
+ * @example
5522
+ * ```typescript
5523
+ * defineHook({
5524
+ * hook: 'before_update_message',
5525
+ * id: 'stamp_updated_at',
5526
+ * execute: async (state, messageId, updates) => ({
5527
+ * ...updates,
5528
+ * updated_at: Date.now(),
5529
+ * }),
5530
+ * });
5531
+ * ```
5532
+ */
5533
+ before_update_message: (state: State, messageId: string, updates: Record<string, unknown>, context: Context) => Promise<Record<string, unknown>>;
5534
+ /**
5535
+ * Called after a message is updated in the database.
5536
+ *
5537
+ * Use for logging, analytics, or triggering side effects after
5538
+ * message update. Cannot modify the message.
5539
+ *
5540
+ * @param state - Thread state
5541
+ * @param message - The updated message
5542
+ *
5543
+ * @example
5544
+ * ```typescript
5545
+ * defineHook({
5546
+ * hook: 'after_update_message',
5547
+ * id: 'log_update',
5548
+ * execute: async (state, message) => {
5549
+ * console.log(`Message updated: ${message.id}`);
5550
+ * },
5551
+ * });
5552
+ * ```
5553
+ */
5554
+ after_update_message: (state: State, message: Msg, context: Context) => Promise<void>;
5555
+ /**
5556
+ * Called before a tool result is stored in the database.
5557
+ *
5558
+ * Receives the tool call and result before storage. Return modified
5559
+ * result to transform before storage.
5560
+ *
5561
+ * @param state - Thread state
5562
+ * @param toolCall - The tool call that was executed
5563
+ * @param toolResult - The result from tool execution
5564
+ * @returns Modified tool result
5565
+ *
5566
+ * @example
5567
+ * ```typescript
5568
+ * defineHook({
5569
+ * hook: 'before_store_tool_result',
5570
+ * id: 'sanitize_results',
5571
+ * execute: async (state, toolCall, toolResult) => {
5572
+ * // toolCall has: id, type, function.name, function.arguments
5573
+ * // toolResult has: status, result?, error?, stack?, attachments?
5574
+ * if (toolResult.result) {
5575
+ * return { ...toolResult, result: sanitize(toolResult.result) };
5576
+ * }
5577
+ * return toolResult;
5578
+ * }
5579
+ * });
5580
+ * ```
5581
+ */
5582
+ before_store_tool_result: (state: State, toolCall: ToolCall, toolResult: ToolResult, context: Context) => Promise<ToolResult>;
5583
+ /**
5584
+ * Called after a successful tool call.
5585
+ *
5586
+ * Receives the tool call and its result. Can return a modified result
5587
+ * or null to use the original. Use for logging, transformation, or
5588
+ * post-processing of successful tool executions.
5589
+ *
5590
+ * @param state - Thread state
5591
+ * @param toolCall - The executed tool call
5592
+ * @param toolResult - The successful result
5593
+ * @returns Modified result or null for original
5594
+ *
5595
+ * @example
5596
+ * ```typescript
5597
+ * defineHook({
5598
+ * hook: 'after_tool_call_success',
5599
+ * id: 'log_success',
5600
+ * execute: async (state, toolCall, toolResult) => {
5601
+ * console.log(`Tool ${toolCall.function.name} succeeded`);
5602
+ * return null; // use original result
5603
+ * },
5604
+ * });
5605
+ * ```
5606
+ */
5607
+ after_tool_call_success: (state: State, toolCall: ToolCall, toolResult: ToolResult, context: Context) => Promise<ToolResult | null>;
5608
+ /**
5609
+ * Called after a failed tool call.
5610
+ *
5611
+ * Receives the tool call and error result. Can return a modified result
5612
+ * or null to use the original. Use for error handling, logging, or
5613
+ * recovery attempts.
5614
+ *
5615
+ * @param state - Thread state
5616
+ * @param toolCall - The failed tool call
5617
+ * @param toolResult - The error result
5618
+ * @returns Modified result or null for original
5619
+ *
5620
+ * @example
5621
+ * ```typescript
5622
+ * defineHook({
5623
+ * hook: 'after_tool_call_failure',
5624
+ * id: 'log_failure',
5625
+ * execute: async (state, toolCall, toolResult) => {
5626
+ * console.error(`Tool ${toolCall.function.name} failed: ${toolResult.error}`);
5627
+ * return null; // use original error
5628
+ * },
5629
+ * });
5630
+ * ```
5631
+ */
5632
+ after_tool_call_failure: (state: State, toolCall: ToolCall, toolResult: ToolResult, context: Context) => Promise<ToolResult | null>;
5633
+ /**
5634
+ * Called when a tool call STARTS — before the tool executes, while the model
5635
+ * may still be generating the rest of the call. Fires exactly once per tool
5636
+ * call. Observational: the return value is ignored.
5637
+ *
5638
+ * Timing depends on whether the tool declares a `progressArgument`:
5639
+ * - **With** `progressArgument`: fires as soon as *just that argument* has
5640
+ * finished streaming (not the whole call), passing its value as `progress`.
5641
+ * This lets a UI show "fixing index.html" before a large `content` argument
5642
+ * finishes generating.
5643
+ * - **Without** `progressArgument`: fires as soon as the tool call appears in
5644
+ * the stream — its name is known but `toolCall.function.arguments` may be an
5645
+ * incomplete JSON fragment (or empty), and `progress` is undefined.
5646
+ *
5647
+ * Conforming runtimes also broadcast a `tool_call_started` thread event to
5648
+ * connected clients with `{ id, name, progress }` at the same moment.
5649
+ *
5650
+ * @param state - Thread state
5651
+ * @param toolCall - The starting tool call; `function.arguments` may be incomplete
5652
+ * @param progress - The declared progress-argument value, when available
5653
+ *
5654
+ * @example
5655
+ * ```typescript
5656
+ * defineHook({
5657
+ * hook: 'tool_call_started',
5658
+ * id: 'log_tool_start',
5659
+ * execute: async (state, toolCall, progress) => {
5660
+ * console.log(`▸ ${toolCall.function.name}${progress ? `: ${progress}` : ''}`);
5661
+ * },
5662
+ * });
5663
+ * ```
5664
+ */
5665
+ tool_call_started: (state: State, toolCall: ToolCall, progress: string | undefined, context: Context) => Promise<void>;
5666
+ /**
5667
+ * Called when a tool call is DONE — after it has executed, with its result,
5668
+ * regardless of success or failure. Fires once per tool call. Observational:
5669
+ * the return value is ignored.
5670
+ *
5671
+ * This complements `after_tool_call_success` / `after_tool_call_failure`,
5672
+ * which can additionally *modify* the stored result; `tool_call_done` is a
5673
+ * single unified observation point that always fires. Conforming runtimes
5674
+ * also broadcast a `tool_call_done` thread event to connected clients with
5675
+ * `{ id, name, status }` at the same moment.
5676
+ *
5677
+ * @param state - Thread state
5678
+ * @param toolCall - The completed tool call
5679
+ * @param toolResult - The tool's result (`status` is `'success'` or `'error'`)
5680
+ */
5681
+ tool_call_done: (state: State, toolCall: ToolCall, toolResult: ToolResult, context: Context) => Promise<void>;
5682
+ /**
5683
+ * Called when a socket attaches to the user's event stream
5684
+ * (`{runtime_prefix}/users/me/stream`).
5685
+ *
5686
+ * @param user - User scope state
5687
+ * @param client - The attached client's id and self-declared kind
5688
+ */
5689
+ user_client_attached: (user: UserState, client: UserClientInfo) => Promise<void>;
5690
+ /**
5691
+ * Called when a socket detaches from the user's event stream.
5692
+ *
5693
+ * @param user - User scope state
5694
+ * @param client - The detached client's id and self-declared kind
5695
+ */
5696
+ user_client_detached: (user: UserState, client: UserClientInfo) => Promise<void>;
5697
+ /**
5698
+ * Called after a durable user value changes, regardless of the writer
5699
+ * (user endpoint, thread-side handle, or REST).
5700
+ *
5701
+ * @param user - User scope state
5702
+ * @param key - The key that changed
5703
+ */
5704
+ user_value_changed: (user: UserState, key: string) => Promise<void>;
5705
+ }
5706
+ /**
5707
+ * Valid hook names.
5571
5708
  */
5572
- interface PackedNamespaceContext {
5573
- type: 'packed';
5574
- /** Package ID this code belongs to */
5575
- packageId: string;
5576
- }
5709
+ type HookName = keyof HookSignatures;
5710
+ /** Hook names that execute in thread scope and may be contributed to a parent. */
5711
+ type ThreadHookName = Exclude<HookName, 'user_client_attached' | 'user_client_detached' | 'user_value_changed'>;
5577
5712
  /**
5578
- * Namespace context for execution.
5579
- * Determines what items are visible during execution.
5713
+ * Hook definition tuple.
5580
5714
  *
5581
- * @example
5582
- * ```typescript
5583
- * // Global namespace (unpacked code)
5584
- * const globalNs: NamespaceContext = { type: 'global' };
5715
+ * A hook definition is a tuple containing the hook name and implementation.
5585
5716
  *
5586
- * // Packed namespace (inside a packed agent)
5587
- * const packedNs: NamespaceContext = {
5588
- * type: 'packed',
5589
- * packageId: 'standardagent-my-workflow',
5590
- * };
5591
- * ```
5717
+ * @template K - The hook name
5718
+ * @template State - The state type (defaults to ThreadState)
5719
+ * @template Msg - The message type
5720
+ * @template ToolCall - The tool call type
5721
+ * @template ToolResult - The tool result type
5722
+ * @deprecated Use the object-based defineHook signature instead
5592
5723
  */
5593
- type NamespaceContext = GlobalNamespaceContext | PackedNamespaceContext;
5724
+ type HookDefinition<K extends HookName = HookName, State = ThreadState, Msg = HookMessage, ToolCall = HookToolCall, ToolResult = HookToolResult> = [K, HookSignatures<State, Msg, ToolCall, ToolResult>[K]];
5594
5725
  /**
5595
- * Extended metadata for packed definitions.
5726
+ * Options for defining a hook with explicit ID.
5727
+ * The generic K ensures `execute` is properly typed for the specific hook type.
5596
5728
  *
5597
- * This interface is added to agent, prompt, tool, model, and hook
5598
- * definitions when they are packed. It marks them as belonging
5599
- * to a specific package and optionally as readonly.
5729
+ * @template K - Hook type from HookSignatures (e.g., 'filter_messages')
5600
5730
  */
5601
- interface PackedMetadata {
5602
- /**
5603
- * Package signature if this definition is part of a packed agent.
5604
- * Undefined for unpacked (global) definitions.
5605
- */
5606
- __package?: PackageSignature;
5731
+ interface HookDefinitionOptions<K extends HookName> {
5732
+ /** The hook type (e.g., 'filter_messages', 'before_create_message') */
5733
+ hook: K;
5734
+ /** Unique identifier for this hook implementation (snake_case) */
5735
+ id: string;
5607
5736
  /**
5608
- * Whether this definition is readonly (cannot be edited in UI).
5609
- * Packed definitions are typically readonly.
5737
+ * The hook implementation function.
5738
+ * Type is automatically inferred from the hook type.
5610
5739
  */
5611
- __readonly?: boolean;
5740
+ execute: HookSignatures<ThreadState, HookMessage, HookToolCall, HookToolResult>[K];
5612
5741
  }
5613
5742
  /**
5614
- * Helper type to check if a definition is packed.
5615
- */
5616
- declare function isPacked(metadata: PackedMetadata | undefined): metadata is PackedMetadata & {
5617
- __package: PackageSignature;
5618
- };
5619
- /**
5620
- * Helper to check if a definition belongs to a specific package.
5621
- */
5622
- declare function belongsToPackage(metadata: PackedMetadata | undefined, packageId: string): boolean;
5623
- /**
5624
- * Helper to check if a definition is visible in a given namespace.
5743
+ * Return type from defineHook - preserves the hook type for type-safe usage.
5625
5744
  *
5626
- * @param metadata - The definition's packed metadata
5627
- * @param namespace - The current namespace context
5628
- * @param isEntryPoint - Whether this is an agent with exposeAsTool: true
5745
+ * @template K - Hook type from HookSignatures
5629
5746
  */
5630
- declare function isVisibleInNamespace(metadata: PackedMetadata | undefined, namespace: NamespaceContext, isEntryPoint?: boolean): boolean;
5747
+ interface HookDefinitionResult<K extends HookName> {
5748
+ /** The hook type name */
5749
+ hook: K;
5750
+ /** The unique hook ID */
5751
+ id: string;
5752
+ /** The typed execute function */
5753
+ execute: HookSignatures<ThreadState, HookMessage, HookToolCall, HookToolResult>[K];
5754
+ }
5631
5755
  /**
5632
- * Metadata export for packed packages.
5756
+ * Define a hook with a unique identifier and strict typing.
5633
5757
  *
5634
- * This is exported as `__meta` from the packed package's index.ts,
5635
- * replacing the manifest.json file from the old format.
5758
+ * The hook type determines the execute function signature.
5759
+ * TypeScript will enforce correct parameter and return types.
5636
5760
  *
5637
- * A packed package's required execution behavior must use capabilities
5638
- * defined by the Standard Agent Specification. It must not depend on private
5639
- * runtime RPCs, storage schemas, bindings, undocumented events, sidecars that
5640
- * replace runtime primitives, or `ThreadState._notPackableRuntimeContext`.
5761
+ * @template K - The hook type (inferred from options.hook)
5762
+ * @param options - Hook definition with hook type, ID, and execute function
5763
+ * @returns Typed hook definition for registration
5641
5764
  *
5642
5765
  * @example
5643
5766
  * ```typescript
5644
- * export const __meta: PackedMeta = {
5645
- * packageId: 'standardagent-my-workflow',
5646
- * version: '1.0.0',
5647
- * entryAgents: ['my_agent'],
5648
- * packedAt: 1705012800000,
5649
- * };
5767
+ * // Filter messages - execute receives (state: ThreadState, messages: HookMessage[])
5768
+ * export default defineHook({
5769
+ * hook: 'filter_messages',
5770
+ * id: 'limit_to_20_messages',
5771
+ * execute: async (state, messages) => {
5772
+ * return messages.slice(-20);
5773
+ * }
5774
+ * });
5650
5775
  * ```
5651
- */
5652
- interface PackedMeta {
5653
- /** Unique package identifier (npm name or local identifier) */
5654
- packageId: string;
5655
- /** Package version (semver) */
5656
- version: string;
5657
- /** Entry agents exposed as tools (visible from outside the package) */
5658
- entryAgents: string[];
5659
- /** When the package was packed (timestamp in milliseconds) */
5660
- packedAt: number;
5661
- /** Optional description from the entry agent */
5662
- description?: string;
5663
- }
5664
- /**
5665
- * Lazy loader type for packed definitions.
5666
- *
5667
- * Each definition in a packed package is wrapped in a function
5668
- * that returns a Promise, enabling lazy loading.
5669
5776
  *
5670
5777
  * @example
5671
5778
  * ```typescript
5672
- * export const tools = {
5673
- * search: async (): Promise<ToolDefinition> => ({
5674
- * description: 'Search for information',
5675
- * execute: async (state, args) => { ... },
5676
- * }),
5677
- * } as const;
5779
+ * // Before create message - execute receives (state: ThreadState, message: Message)
5780
+ * export default defineHook({
5781
+ * hook: 'before_create_message',
5782
+ * id: 'prefix_content',
5783
+ * execute: async (state, message) => {
5784
+ * if (message.role === 'user' && message.content) {
5785
+ * return { ...message, content: `[user] ${message.content}` };
5786
+ * }
5787
+ * return message;
5788
+ * }
5789
+ * });
5678
5790
  * ```
5679
- */
5680
- type DefinitionLoader<T> = () => Promise<T>;
5681
-
5682
- /**
5683
- * Standard shape for packed package exports.
5684
- *
5685
- * Every packed package exports this structure, enabling
5686
- * consistent discovery and loading.
5687
5791
  *
5688
5792
  * @example
5689
5793
  * ```typescript
5690
- * import type { PackedExports } from '@standardagents/spec';
5691
- *
5692
- * const pkg: PackedExports = await import('standardagent-my-workflow');
5693
- * const tool = await pkg.tools.search();
5794
+ * // After tool call success - execute receives (state, toolCall, toolResult)
5795
+ * export default defineHook({
5796
+ * hook: 'after_tool_call_success',
5797
+ * id: 'log_tool_usage',
5798
+ * execute: async (state, toolCall, toolResult) => {
5799
+ * console.log(`Tool ${toolCall.function.name} returned: ${toolResult.result}`);
5800
+ * return null; // Use original result
5801
+ * }
5802
+ * });
5694
5803
  * ```
5695
5804
  */
5696
- interface PackedExports {
5697
- /** Agent definitions, keyed by name */
5698
- agents: Record<string, DefinitionLoader<AgentDefinition>>;
5699
- /** Prompt definitions, keyed by name */
5700
- prompts: Record<string, DefinitionLoader<PromptDefinition>>;
5701
- /** Tool definitions, keyed by name */
5702
- tools: Record<string, DefinitionLoader<ToolDefinition<unknown, any, any>>>;
5703
- /** Model definitions, keyed by name */
5704
- models: Record<string, DefinitionLoader<ModelDefinition>>;
5705
- /** Hook definitions, keyed by name */
5706
- hooks: Record<string, DefinitionLoader<HookSignatures[keyof HookSignatures]>>;
5707
- /**
5708
- * Effect definitions, keyed by name.
5709
- *
5710
- * Optional for backward compatibility with previously packed packages.
5711
- */
5712
- effects?: Record<string, DefinitionLoader<EffectDefinition<any, any>>>;
5713
- /**
5714
- * Thread endpoint handlers, keyed by packed route key (for example, `status.get`).
5715
- *
5716
- * Runtimes mount these beneath the same thread ID prefix as local thread
5717
- * endpoints, using the packed route key to derive the endpoint path.
5718
- * Requests beneath that prefix that do not match any local or packed thread
5719
- * endpoint route MUST return HTTP 404.
5720
- *
5721
- * Optional for backward compatibility with previously packed packages.
5722
- */
5723
- threadEndpoints?: Record<string, DefinitionLoader<MarkedThreadEndpoint | Controller>>;
5724
- /** Package metadata */
5725
- __meta: PackedMeta;
5726
- }
5805
+ declare function defineHook<K extends HookName>(options: HookDefinitionOptions<K>): HookDefinitionResult<K>;
5727
5806
 
5728
- export { type AgentDefinition, type AgentType, type AttachmentRef, type CodeExecution, type CodeExecutionError, type CodeExecutionLog, type CodeExecutionOptions, type CodeExecutionResult, type CodeExecutionStatus, type ContentPart, type Controller, type ControllerContext, type ControllerReturn, type CreateFetch, type CredentialGrant, type DefineToolOptions, type DefinitionLoader, ENDPOINT_ACCESS_SYMBOL, type Effect, type EffectDefinition, type EmptyUsesState, type EndpointAccess, type EndpointOptions, type EnvValueType, type ExecutionState, type FetchLike, type FileChunk, type FilePart, type FileRecord, type FileStats, type FileStorage, type FindResult, type GetMessagesOptions, type GlobalNamespaceContext, type GrepResult, type HookContext, type HookDefinition, type HookDefinitionOptions, type HookDefinitionResult, type HookMessage, type HookName, type HookSignatures, type HookToolCall, type HookToolResult, type ImageContent, type ImagePart, type ImageUrlPart, type InferProviderOptions, type InjectMessageInput, type InspectedRequest, type LLMMessage, type LLMProviderInterface, type ListUserThreadsOptions, type ListUserValuesOptions, type MarkedThreadEndpoint, type MarkedUserEndpoint, type Message, type MessageUpdates, type MessagesResult, type ModelCapabilities, type ModelDefinition, type ModelProvider, type NamespaceContext, type PackageSignature, type PackedExports, type PackedMeta, type PackedMetadata, type PackedNamespaceContext, type PromptContent, type PromptDefinition, type PromptEnvPart, type PromptIncludePart, type PromptInput, type PromptPart, type PromptTextPart, type PromptToolConfig, type ProviderAssistantMessage, type ProviderAttachment, type ProviderConfigSlot, type ProviderConfigSlots, type ProviderConfigValueSource, type ProviderDefinition, ProviderError, type ProviderErrorCode, type ProviderExecutedToolResult, type ProviderFactory, type ProviderFactoryConfig, type ProviderFactoryWithOptions, type ProviderFinishReason, type ProviderGeneratedImage, type ProviderInstance, type ProviderMessage, type ProviderMessageContent, type ProviderMethodOverrides, type ProviderModelInfo, type ProviderModelsPage, type ProviderModelsQuery, type ProviderOverride, type ProviderOverrideContext, type ProviderReasoningDetail, type ProviderRequest, type ProviderResponse, type ProviderStreamChunk, type ProviderSyncOverride, type ProviderSystemMessage, type ProviderTool, type ProviderToolCallPart, type ProviderToolMessage, type ProviderToolResultContent, type ProviderUsage, type ProviderUsagePricing, type ProviderUserMessage, type ProviderWebSearchResult, type QueueMessageInput, type ReadFileStreamOptions, type ReaddirResult, type ReasoningConfig, type ResolveUsesState, type ResponseSummary, type RuntimeByteStream, type RuntimeStreamErrorCode, type RuntimeStreamRequest, type RuntimeStreamResult, type RuntimeStreams, type ScheduledEffect, type SessionToolBinding, type SessionToolConfig, type SetEnvOptions, type SideConfig, type SkillFileInput, type SkillMetadata, type SkillsNamespace, type StopSessionOptions, type StructuredPrompt, type SubagentRegistryEntry, type SubagentToolConfig, type SubpromptConfig, THREAD_ENDPOINT_SYMBOL, THREAD_STREAM_REASONING_PARAM, type TenvRawShape, type TextContent, type TextPart, type ThreadEndpointAuth, type ThreadEndpointHandler, type ThreadEndpointRouteParams, type ThreadMessageChunkEvent, type ThreadMetadata, type ThreadReasoningChunkEvent, type ThreadState, type ThreadUser, type Tool, type ToolArgs, type ToolArgsNode, type ToolArgsRawShape, type ToolAttachment, type ToolConfig, type ToolContent, type ToolDefinition, type ToolResult, type ToolTenvs, type ToolVariables, USER_ENDPOINT_SYMBOL, type UserClientInfo, type UserEndpointHandler, type UserEndpointRouteParams, type UserHandle, type UserIdentity, type UserState, type UserThreadSummary, type UserValueEntry, type UsesAwareExecute, type UsesConstrainedState, type VariableDefinition, type VariableType, type VirtualModuleLoader, type VirtualModuleRegistry, type WriteFileOptions, belongsToPackage, defineAgent, defineController, defineEffect, defineHook, defineModel, definePrompt, defineProvider, defineThreadEndpoint, defineTool, defineUserEndpoint, getEndpointAccess, isPacked, isThreadEndpoint, isUserEndpoint, isVisibleInNamespace, mapReasoningLevel, providerConst, providerEnv, providerValue, validateToolName };
5807
+ export { type AgentDefinition, type AgentType, type AttachmentRef, type CodeExecution, type CodeExecutionError, type CodeExecutionLog, type CodeExecutionOptions, type CodeExecutionResult, type CodeExecutionStatus, type ContentPart, type Controller, type ControllerContext, type ControllerReturn, type CreateFetch, type CredentialGrant, type DefineToolOptions, type DefinitionLoader, ENDPOINT_ACCESS_SYMBOL, type Effect, type EffectDefinition, type EmptyUsesState, type EndpointAccess, type EndpointOptions, type EnvValueType, type ExecutionState, type FetchLike, type FileChunk, type FilePart, type FileRecord, type FileStats, type FileStorage, type FindResult, type GetMessagesOptions, type GlobalNamespaceContext, type GrepResult, type HookContext, type HookDefinition, type HookDefinitionOptions, type HookDefinitionResult, type HookExecutionContext, type HookExecutionSource, type HookMessage, type HookName, type HookSignatures, type HookToolCall, type HookToolResult, type ImageContent, type ImagePart, type ImageUrlPart, type InferProviderOptions, type InjectMessageInput, type InspectedRequest, type LLMMessage, type LLMProviderInterface, type ListUserThreadsOptions, type ListUserValuesOptions, type MarkedThreadEndpoint, type MarkedUserEndpoint, type Message, type MessageUpdates, type MessagesResult, type ModelCapabilities, type ModelDefinition, type ModelProvider, type NamespaceContext, type PackageSignature, type PackedExports, type PackedMeta, type PackedMetadata, type PackedNamespaceContext, type ParentHookRequirement, type ParentHookStage, type PromptContent, type PromptDefinition, type PromptEnvPart, type PromptIncludePart, type PromptInput, type PromptPart, type PromptTextPart, type PromptToolConfig, type ProviderAssistantMessage, type ProviderAttachment, type ProviderConfigSlot, type ProviderConfigSlots, type ProviderConfigValueSource, type ProviderDefinition, ProviderError, type ProviderErrorCode, type ProviderExecutedToolResult, type ProviderFactory, type ProviderFactoryConfig, type ProviderFactoryWithOptions, type ProviderFinishReason, type ProviderGeneratedImage, type ProviderInstance, type ProviderMessage, type ProviderMessageContent, type ProviderMethodOverrides, type ProviderModelInfo, type ProviderModelsPage, type ProviderModelsQuery, type ProviderOverride, type ProviderOverrideContext, type ProviderReasoningDetail, type ProviderRequest, type ProviderResponse, type ProviderStreamChunk, type ProviderSyncOverride, type ProviderSystemMessage, type ProviderTool, type ProviderToolCallPart, type ProviderToolMessage, type ProviderToolResultContent, type ProviderUsage, type ProviderUsagePricing, type ProviderUserMessage, type ProviderWebSearchResult, type QueueMessageInput, type ReadFileStreamOptions, type ReaddirResult, type ReasoningConfig, type ResolveUsesState, type ResponseSummary, type RuntimeByteStream, type RuntimeStreamErrorCode, type RuntimeStreamRequest, type RuntimeStreamResult, type RuntimeStreams, type ScheduledEffect, type SessionToolBinding, type SessionToolConfig, type SetEnvOptions, type SideConfig, type SkillFileInput, type SkillMetadata, type SkillsNamespace, type StopSessionOptions, type StructuredPrompt, type SubagentRegistryEntry, type SubagentToolConfig, type SubpromptConfig, THREAD_ENDPOINT_SYMBOL, THREAD_STREAM_REASONING_PARAM, type TenvRawShape, type TextContent, type TextPart, type ThreadEndpointAuth, type ThreadEndpointHandler, type ThreadEndpointRouteParams, type ThreadHookName, type ThreadMessageChunkEvent, type ThreadMetadata, type ThreadReasoningChunkEvent, type ThreadState, type ThreadUser, type Tool, type ToolArgs, type ToolArgsNode, type ToolArgsRawShape, type ToolAttachment, type ToolConfig, type ToolContent, type ToolDefinition, type ToolResult, type ToolTenvs, type ToolVariables, USER_ENDPOINT_SYMBOL, type UserClientInfo, type UserEndpointHandler, type UserEndpointRouteParams, type UserHandle, type UserIdentity, type UserState, type UserThreadSummary, type UserValueEntry, type UsesAwareExecute, type UsesConstrainedState, type VariableDefinition, type VariableType, type VirtualModuleLoader, type VirtualModuleRegistry, type WriteFileOptions, belongsToPackage, defineAgent, defineController, defineEffect, defineHook, defineModel, definePrompt, defineProvider, defineThreadEndpoint, defineTool, defineUserEndpoint, getEndpointAccess, isPacked, isThreadEndpoint, isUserEndpoint, isVisibleInNamespace, mapReasoningLevel, providerConst, providerEnv, providerValue, validateToolName };