Copy page
View as Markdown View this page as plain text

Threads

A thread is an instance of an agent — the runtime execution context where conversations happen. Each thread has isolated storage for messages and files, maintains its own conversation history, and can be handed off between agents while preserving full context.

This page covers the core ideas: what a thread is, its identity, the ThreadState interface, and the small primitives that live directly on it (resource loading, tool invocation, effects, events, context, runtime context, subagent hierarchy, lifecycle). Larger surfaces have their own pages:

1. Thread Definition

A thread is an instance of an agent with isolated storage for messages and files.

1.1 Agent Assignment

The agent assigned to a thread can change over time while preserving all history and context. This is called a handoff.

ScenarioBehavior
Tool handoffCalling an ai_human agent as a tool transfers control to that agent
Manual handoffQueue a tool call to an agent to trigger handoff programmatically
dual_ai executionCreates its own isolated thread instance (no handoff)

Handoffs enable workflows where specialized agents handle different parts of a conversation — a triage agent routes to support, support escalates to billing — all within the same thread with full context preserved.

1.2 ThreadState Interface

ThreadState is the unified interface for all thread operations. It provides a consistent API for interacting with threads regardless of context — whether from tools during execution, hooks, or HTTP endpoints.

1.2.1 Properties

PropertyTypeDescription
threadIdstringUnique thread identifier (readonly)
agentIdstringOwning agent name (readonly)
userIdstring | nullAssociated user (readonly)
createdAtnumberCreation timestamp in microseconds (readonly)
childrenSubagentRegistryEntry[]Resumable child subagent registry (readonly)
terminatednumber | nullTermination timestamp in microseconds, or null when active (readonly)
contextRecord<string, unknown>Per-execution key-value storage. See Context Storage
executionExecutionState | nullExecution state during active execution, null at rest. See Execution State
_notPackableRuntimeContextRecord<string, unknown> | undefinedRuntime-specific context (non-portable). See Runtime Context

1.2.2 Methods

The methods directory below is grouped by topic. Topics with their own page link out; the rest are documented on this page.

MessagesMessages →

MethodReturnsDescription
getMessages(options?)Promise<MessagesResult>Get messages with pagination
getMessage(messageId)Promise<Message | null>Get single message by ID
injectMessage(input)Promise<Message>Inject a new message
queueMessage(input)Promise<void>Queue a message for delivery on next step
updateMessage(messageId, updates)Promise<Message>Update existing message
deleteMessage(messageId)Promise<boolean>Delete a message

File SystemFile System →

MethodReturnsDescription
writeFile(path, data, mimeType, options?)Promise<FileRecord>Write file
readFile(path)Promise<ArrayBuffer | null>Read file content
readFileStream(path, options?)Promise<AsyncIterable<FileChunk> | null>Stream large files
statFile(path)Promise<FileRecord | null>Get file metadata
readdirFile(path)Promise<ReaddirResult>List directory
unlinkFile(path)Promise<void>Delete file
mkdirFile(path)Promise<FileRecord>Create directory
rmdirFile(path)Promise<void>Remove directory
getFileStats()Promise<FileStats>Get filesystem stats
grepFiles(pattern)Promise<GrepResult[]>Search file contents
findFiles(pattern)Promise<FindResult>Find files by glob
getFileThumbnail(path)Promise<ArrayBuffer | null>Get image thumbnail

Code ExecutionCode Execution →

MethodReturnsDescription
runCode(source, options?)CodeExecutionExecute JS/TS source in an isolated sandbox; returns a thenable handle with terminate()

Subagent Hierarchy — see §9 Subagent Hierarchy

MethodReturnsDescription
getChildThread(referenceId)Promise<ThreadState | null>Resolve child thread by subagent reference
getParentThread()Promise<ThreadState | null>Resolve parent thread for this thread
notifyParent(content)Promise<void>Queue a silent message to this thread’s parent
setStatus(status)Promise<void>Update this thread’s status in the parent registry

Resource Loading — see §2 Resource Loading

MethodReturnsDescription
loadModel(name)Promise<T>Load model definition
loadPrompt(name)Promise<T>Load prompt definition
loadAgent(name)Promise<T>Load agent definition
getPromptNames()string[]List available prompts
getAgentNames()string[]List available agents
getModelNames()string[]List available models
env(propertyName)Promise<string>Resolve environment variable using thread/user/instance/agent/prompt precedence
envType(propertyName)Promise<'text' | 'secret'>Resolve display/redaction classification for an environment value
setEnv(propertyName, value, options?)Promise<void>Set a thread environment variable, optionally choosing text or secret, and propagate to active descendants

Tool Invocation — see §3 Tool Invocation

MethodReturnsDescription
queueTool(toolName, args)voidQueue tool for async execution
invokeTool(toolName, args)Promise<ToolResult>Invoke tool and wait for result

Effect Scheduling — see §4 Effect Scheduling

MethodReturnsDescription
scheduleEffect(name, args, delay?)Promise<string>Schedule effect, returns effect ID
getScheduledEffects(name?)Promise<ScheduledEffect[]>Get pending effects
removeScheduledEffect(id)Promise<boolean>Cancel scheduled effect

Events — see §5 Events

MethodReturnsDescription
emit(event, data)voidEmit event to connected clients

Durable Key-Value Store — see §7 Durable Key-Value Store

MethodReturnsDescription
getValue(key)Promise<T | null>Read a value from the thread’s durable key-value store
setValue(key, value)Promise<void>Write a JSON-serializable value; null/undefined deletes the key

Lifecycle — see §8 Lifecycle

MethodReturnsDescription
terminate()Promise<void>Soft-terminate the thread

1.3 Access Contexts

ThreadState is provided in different contexts:

ContextExecution StateUse Case
ToolsPresentDuring agent execution
HooksPresentLifecycle interception
EndpointsNullHTTP API access

2. Resource Loading

2.1 Loading Definitions

ThreadState provides access to registered definitions:

const model = await state.loadModel('gpt-4o');
const prompt = await state.loadPrompt('support-prompt');
const agent = await state.loadAgent('customer-support');

2.2 Listing Resources

const prompts = state.getPromptNames();
const agents = state.getAgentNames();
const models = state.getModelNames();

2.3 Environment Variable Resolution

const apiKey = await state.env('GOOGLE_API_KEY');
await state.setEnv('TOPDOWN_APPROVAL_GATE', 'open');
await state.setEnv('PUBLIC_LABEL', 'QA', { type: 'text' });
await state.setEnv('API_TOKEN', 'secret-token', { type: 'secret' });

env(propertyName) resolves in this order:

  1. Thread environment variables
  2. User-account environment variables
  3. Runtime instance environment variables
  4. Agent definition defaults (agent.env)
  5. Prompt definition defaults (prompt.env)

envType(propertyName) returns the display/redaction classification for a resolved environment value. Unknown or non-thread sources default to secret. Values marked secret should be redacted from tool output and logs; values marked text may be displayed.

setEnv(propertyName, value, options?) writes to thread storage and propagates recursively to active descendants. Pass { type: 'text' } or { type: 'secret' } to choose the display/redaction classification. Omitting type preserves any existing classification for that key, or defaults a new key to secret. During propagation, descendants must filter out inherited values for variable names marked scoped in that descendant’s agent graph. Terminated descendants are skipped.

Thread, user, and instance environment values should be encrypted at rest when the runtime supports encrypted storage.

For resumable subagent creation, runtimes may require missing scoped variables to be provided through a pending request flow before boot can continue. See Agents §7.8.

3. Tool Invocation

3.1 Queuing Tools

Queue a tool for asynchronous execution:

state.queueTool('send_email', {
  to: '[email protected]',
  subject: 'Hello',
  body: 'Message content',
});

If the thread is currently executing, the tool is added to the queue. Otherwise, a new execution is started.

3.2 Direct Invocation

Invoke a tool and wait for the result:

const result = await state.invokeTool('get_weather', {
  location: 'San Francisco',
});

if (result.status === 'success') {
  console.log(result.result);
}

4. Effect Scheduling

Effects are scheduled operations that run outside the normal tool execution context. Unlike tools which execute immediately during conversation, effects:

  • Run independently of the conversation flow
  • Can be scheduled with delays
  • Execute with their own ThreadState context
  • Are ideal for side effects that don’t need immediate results

Effect definition syntax is specified in Effects. Packed effects may use qualified names ({packageId}/{effectName}) to avoid collisions.

4.1 Scheduling Effects

const effectId = await state.scheduleEffect(
  'send_reminder_email',
  { to: '[email protected]', subject: 'Reminder' },
  30 * 60 * 1000  // delay in milliseconds
);
ParameterTypeDescription
namestringEffect name (file in agents/effects/)
argsRecord<string, unknown>Arguments passed to effect handler
delaynumberDelay in milliseconds (default: 0 for immediate)

Returns a unique effect ID (UUID) that can be used to cancel the effect.

4.2 ScheduledEffect Record

PropertyTypeDescription
idstringUnique effect ID (UUID)
namestringEffect name
argsRecord<string, unknown>Arguments to be passed
scheduledAtnumberScheduled execution time (microseconds)
createdAtnumberWhen effect was scheduled (microseconds)

4.3 Managing Scheduled Effects

const effects = await state.getScheduledEffects();
const reminders = await state.getScheduledEffects('send_reminder_email');

const removed = await state.removeScheduledEffect(effectId);
if (removed) {
  console.log('Effect cancelled');
}

5. Events

5.1 Emitting Events

Send custom events to connected clients:

state.emit('progress', {
  step: 3,
  total: 10,
  message: 'Processing data...',
});

Events are delivered via WebSocket to any connected clients.

5.2 Event Guidelines

  • Event names SHOULD be lowercase with underscores
  • Event data MUST be JSON-serializable
  • Events SHOULD NOT contain sensitive information

6. Context Storage

6.1 Per-Execution Context

The context property provides temporary storage:

state.context.userData = { id: 123, name: 'Alice' };

// Retrieve in another tool call
const user = state.context.userData;

6.2 Context Limitations

AspectBehavior
ScopeSingle execution only
PersistenceLost after execution ends
TypeRecord<string, unknown>
SizeNo enforced limit

For persistent storage across executions, use the durable key-value store, the file system, or external storage.

7. Durable Key-Value Store

getValue and setValue provide a simple durable key-value store scoped to the thread. Values persist across executions, tool calls, hook invocations, and endpoint requests for the lifetime of the thread.

It is one of two ways to store persistent data on a thread, alongside the file system.

7.1 Reading and Writing Values

const count = (await state.getValue<number>('invocation_count')) ?? 0;
await state.setValue('invocation_count', count + 1);

await state.setValue('preferences', { theme: 'dark', locale: 'en-US' });
const prefs = await state.getValue<{ theme: string; locale: string }>('preferences');

7.2 Semantics

AspectBehavior
ScopePer-thread; not shared with parent, child, or sibling threads
PersistenceDurable across executions for the lifetime of the thread
Value typeAny JSON-serializable value (objects, arrays, strings, numbers, booleans, null)
Missing keysgetValue returns null
DeletionsetValue(key, null) or setValue(key, undefined) deletes the key
SubagentsValues are not automatically copied to spawned child threads

7.3 Comparison With Other Storage

SurfaceScopePersistenceIntended Use
contextSingle executionEphemeralSharing state between tool calls in one execution
getValue / setValueThreadDurableSmall structured state scoped to the thread
env / envType / setEnvThread → user → instance → agent → promptDurable (encrypted at rest when supported)Configuration and secrets with layered resolution and display/redaction metadata
File systemThreadDurableLarge or binary payloads

Runtime semantics and conformance requirements for the durable key-value store live in Runtime §15.

8. Lifecycle

terminate() is a soft shutdown:

  • Marks the thread inactive and sets terminated
  • Aborts in-flight execution
  • Rejects future queueMessage() and execution attempts
  • Parent registries should reflect 'terminated' status
await state.terminate();

9. Subagent Hierarchy, Communication, and Termination

Subagent-aware runtimes expose parent/child thread relationships through ThreadState:

const child = await state.getChildThread(referenceId);
const parent = await state.getParentThread();

children entries track resumable child references and status/projection metadata:

interface SubagentRegistryEntry {
  reference: string;
  name: string;
  title?: string;
  description: string;
  resumable?: boolean;
  blocking?: boolean;
  threadName?: string;
  spawnGroupId?: string;
  createdAt?: number;
  status: string;
  parentCommunication?: 'implicit' | 'explicit';
}

See Agents §7 for the full lifecycle, communication modes, and cross-thread semantics.

9.1 Cross-Thread Attachment Copying

When communication crosses thread boundaries, filesystem references must be copied:

  • parent → child for invocation/messaging attachments
  • child → parent for completion/failure payload attachments

Returned attachment paths must be valid in the receiving thread filesystem.

9.2 Lifecycle Status Messages

Runtimes may persist synthesized status messages (for example, system messages tagged with metadata.status_kind) to make cross-thread lifecycle events observable in message feeds.

9.3 Parent Messaging

Children can push information up the hierarchy without waiting for completion:

await state.notifyParent('halfway through the batch');
await state.setStatus('running');

10. Runtime Context (Non-Portable)

The _notPackableRuntimeContext property allows runtime implementations to inject platform-specific context.

PropertyTypeDescription
_notPackableRuntimeContextRecord<string, unknown> | undefinedRuntime-specific context injected by the implementation

10.1 Portability

Tools that access _notPackableRuntimeContext cannot be packed, shared, or published to tool registries.

10.2 Example: Accessing a Host-Provided Binding

import { defineTool } from '@standardagents/builder';
import { z } from 'zod';

export default defineTool({
  description: 'Query internal database',
  args: z.object({ userId: z.string() }),
  execute: async (state, args) => {
    const env = state._notPackableRuntimeContext?.env as HostEnv | undefined;

    if (!env?.DATABASE) {
      return { status: 'error', error: 'Database not available' };
    }

    const result = await env.DATABASE
      .prepare('SELECT * FROM users WHERE id = ?')
      .bind(args.userId)
      .first();

    return { status: 'success', result: JSON.stringify(result) };
  },
});

Tip: The shape of _notPackableRuntimeContext is runtime-defined. Typical implementations expose either a handle to host bindings (databases, key-value stores, object storage) under env, or a process/environment handle for server-style runtimes. Consult your runtime’s documentation for the specific contract.

11. Conformance Requirements

11.1 Thread Lifecycle

  1. Creation: Thread created with metadata
  2. Execution: Agent processes messages
  3. At Rest: Thread accessible via endpoints
  4. Resumption: New execution on next message

11.2 Platform Independence

ThreadState is an abstract interface. Implementations MUST NOT expose:

  • Storage implementation details
  • Platform-specific bindings
  • Internal state management

11.3 Error Handling

Methods that return promises SHOULD throw on:

  • Resource not found (loadModel, loadPrompt, loadAgent)
  • Invalid parameters
  • Storage errors

Methods SHOULD NOT throw on:

  • Empty results (return empty arrays/null instead)
  • Missing optional data

12. TypeScript Reference

/**
 * Thread state interface - the unified API for thread interactions.
 * Available to tools, hooks, and endpoints.
 */
interface ThreadState {
  // ─────────────────────────────────────────────────────────────────────────
  // Identity (readonly)
  // ─────────────────────────────────────────────────────────────────────────
  readonly threadId: string;
  readonly agentId: string;
  readonly userId: string | null;
  readonly createdAt: number;
  readonly children: SubagentRegistryEntry[];
  readonly terminated: number | null;

  // ─────────────────────────────────────────────────────────────────────────
  // Messages — see /threads/messages
  // ─────────────────────────────────────────────────────────────────────────
  getMessages(options?: GetMessagesOptions): Promise<MessagesResult>;
  getMessage(messageId: string): Promise<Message | null>;
  injectMessage(input: InjectMessageInput): Promise<Message>;
  queueMessage(input: QueueMessageInput): Promise<void>;
  updateMessage(messageId: string, updates: MessageUpdates): Promise<Message>;
  deleteMessage(messageId: string): Promise<boolean>;

  // ─────────────────────────────────────────────────────────────────────────
  // Resource Loading
  // ─────────────────────────────────────────────────────────────────────────
  loadModel<T = unknown>(name: string): Promise<T>;
  loadPrompt<T = unknown>(name: string): Promise<T>;
  loadAgent<T = unknown>(name: string): Promise<T>;
  getChildThread(referenceId: string): Promise<ThreadState | null>;
  getParentThread(): Promise<ThreadState | null>;
  getPromptNames(): string[];
  getAgentNames(): string[];
  getModelNames(): string[];
  env(propertyName: string): Promise<string>;
  envType(propertyName: string): Promise<'text' | 'secret'>;
  setEnv(
    propertyName: string,
    value: string,
    options?: { type?: 'text' | 'secret' }
  ): Promise<void>;
  notifyParent(content: string): Promise<void>;
  setStatus(status: string): Promise<void>;

  // ─────────────────────────────────────────────────────────────────────────
  // Tool Invocation
  // ─────────────────────────────────────────────────────────────────────────
  queueTool(toolName: string, args: Record<string, unknown>): void;
  invokeTool(toolName: string, args: Record<string, unknown>): Promise<ToolResult>;

  // ─────────────────────────────────────────────────────────────────────────
  // Effect Scheduling
  // ─────────────────────────────────────────────────────────────────────────
  scheduleEffect(name: string, args: Record<string, unknown>, delay?: number): Promise<string>;
  getScheduledEffects(name?: string): Promise<ScheduledEffect[]>;
  removeScheduledEffect(id: string): Promise<boolean>;

  // ─────────────────────────────────────────────────────────────────────────
  // Events
  // ─────────────────────────────────────────────────────────────────────────
  emit(event: string, data: unknown): void;

  // ─────────────────────────────────────────────────────────────────────────
  // Context Storage
  // ─────────────────────────────────────────────────────────────────────────
  context: Record<string, unknown>;

  // ─────────────────────────────────────────────────────────────────────────
  // Durable Key-Value Store
  // ─────────────────────────────────────────────────────────────────────────
  getValue<T = unknown>(key: string): Promise<T | null>;
  setValue(key: string, value: unknown): Promise<void>;

  // ─────────────────────────────────────────────────────────────────────────
  // File System — see /threads/filesystem
  // ─────────────────────────────────────────────────────────────────────────
  writeFile(path: string, data: ArrayBuffer | string, mimeType: string, options?: WriteFileOptions): Promise<FileRecord>;
  readFile(path: string): Promise<ArrayBuffer | null>;
  readFileStream(path: string, options?: ReadFileStreamOptions): Promise<AsyncIterable<FileChunk> | null>;
  statFile(path: string): Promise<FileRecord | null>;
  readdirFile(path: string): Promise<ReaddirResult>;
  unlinkFile(path: string): Promise<void>;
  mkdirFile(path: string): Promise<FileRecord>;
  rmdirFile(path: string): Promise<void>;
  getFileStats(): Promise<FileStats>;
  grepFiles(pattern: string): Promise<GrepResult[]>;
  findFiles(pattern: string): Promise<FindResult>;
  getFileThumbnail(path: string): Promise<ArrayBuffer | null>;

  // ─────────────────────────────────────────────────────────────────────────
  // Code Execution — see /threads/code-execution
  // ─────────────────────────────────────────────────────────────────────────
  runCode(source: string, options?: CodeExecutionOptions): CodeExecution;

  // ─────────────────────────────────────────────────────────────────────────
  // Execution State — see /threads/execution-state
  // ─────────────────────────────────────────────────────────────────────────
  execution: ExecutionState | null;
  terminate(): Promise<void>;

  // ─────────────────────────────────────────────────────────────────────────
  // Runtime Context (Non-Portable)
  // ─────────────────────────────────────────────────────────────────────────
  readonly _notPackableRuntimeContext?: Record<string, unknown>;
}

/**
 * A scheduled effect pending execution.
 */
interface ScheduledEffect {
  id: string;
  name: string;
  args: Record<string, unknown>;
  scheduledAt: number;  // microseconds
  createdAt: number;    // microseconds
}