goatchain 0.0.29 → 0.0.31

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/README.md CHANGED
@@ -28,6 +28,7 @@ hooks: {
28
28
  ```
29
29
 
30
30
  If you need both, use:
31
+
31
32
  - `preToolUse` to rewrite tool arguments/tool call
32
33
  - `permissionRequest` to allow or block execution
33
34
 
@@ -115,19 +116,25 @@ const session = await agent.createSession({
115
116
  interface CreateSessionOptions {
116
117
  sessionId?: string // Custom session ID
117
118
  model?: ModelRef // Optional model override
119
+ variants?: ModelRef[] // Optional fallback model refs
118
120
  maxIterations?: number // Max agent loop iterations (default: 1000)
119
121
  cwd?: string // Working directory for file operations
122
+ messageQueueConfig?: {
123
+ autoProcessQueue?: boolean // Auto-process queued messages (default: true)
124
+ maxQueueSize?: number // Optional queue length limit
125
+ }
120
126
  requestParams?: {
121
127
  temperature?: number // Model temperature
122
128
  maxTokens?: number // Max output tokens
123
- topP?: number // Nucleus sampling parameter
129
+ stop?: string[] // Optional stop sequences
130
+ [key: string]: unknown // Provider-specific request params
124
131
  }
125
132
  }
126
133
  ```
127
134
 
128
135
  ### Working Directory (CWD) Configuration
129
136
 
130
- You can set a working directory for the session, which will be automatically applied to all file operation tools (Read, Write, Edit, Glob, Grep, Bash, AstGrepSearch, AstGrepReplace):
137
+ You can set a working directory for the session, which will be automatically applied to tools that support `setCwd()` (for example `Read`, `Write`, `Edit`, `Glob`, `Grep`, and `Bash`):
131
138
 
132
139
  ```typescript
133
140
  // Set CWD when creating a session
@@ -161,13 +168,70 @@ session.send('What is the weather today?')
161
168
 
162
169
  // Multiple messages in sequence
163
170
  session.send('First question')
164
- // Wait for response...
165
171
  session.send('Follow-up question')
166
172
  ```
167
173
 
168
174
  #### Send Options
169
175
 
170
- You can pass options to `send()` to control tool execution, approval, and more:
176
+ You can pass options to `send()` to control priority, tool execution, approval, and more:
177
+
178
+ ```typescript
179
+ // Higher priority messages are processed first (smaller number = higher priority)
180
+ session.send('Low priority', { priority: 10 })
181
+ session.send('High priority', { priority: 1 })
182
+ ```
183
+
184
+ `send()` returns a message ID that can be used for queue management:
185
+
186
+ ```typescript
187
+ const messageId = session.send('Can be cancelled later')
188
+ session.cancelQueuedMessage(messageId)
189
+ ```
190
+
191
+ #### Message Queue
192
+
193
+ Session now supports queue-based messaging by default. You can enqueue multiple messages safely even while a previous `receive()` is running.
194
+
195
+ ```typescript
196
+ const session = await agent.createSession()
197
+
198
+ session.send('First message')
199
+ session.send('Second message')
200
+
201
+ // Batch enqueue with priority
202
+ session.sendBatch([
203
+ { input: 'Task A', priority: 2 },
204
+ { input: 'Task B', priority: 1 },
205
+ ])
206
+
207
+ // Inspect queue state
208
+ const queue = session.getQueueStatus()
209
+ console.log(queue.length, queue.isProcessing)
210
+
211
+ // Remove queued messages
212
+ session.clearQueue()
213
+ ```
214
+
215
+ Manual queue mode:
216
+
217
+ ```typescript
218
+ const session = await agent.createSession({
219
+ messageQueueConfig: { autoProcessQueue: false },
220
+ })
221
+
222
+ session.send('Message 1')
223
+ session.send('Message 2')
224
+
225
+ for await (const event of session.receive()) {
226
+ // only Message 1
227
+ }
228
+
229
+ for await (const event of session.receive()) {
230
+ // Message 2
231
+ }
232
+ ```
233
+
234
+ Tool context and approval options still work as before:
171
235
 
172
236
  ```typescript
173
237
  // Auto-approve all tools for this request
@@ -212,7 +276,7 @@ for await (const event of session.receive()) {
212
276
  break
213
277
 
214
278
  case 'tool_call_start':
215
- console.log(`\nCalling tool: ${event.name}`)
279
+ console.log(`\nCalling tool: ${event.toolName ?? event.callId}`)
216
280
  break
217
281
 
218
282
  case 'tool_result':
@@ -228,30 +292,28 @@ for await (const event of session.receive()) {
228
292
  console.log(`\nConversation done: ${event.stopReason}`)
229
293
  console.log(`Total tokens: ${event.usage?.totalTokens}`)
230
294
  break
231
-
232
- case 'error':
233
- console.error(`Error: ${event.error}`)
234
- break
235
295
  }
236
296
  }
237
297
  ```
238
298
 
239
299
  ### Session Event Types
240
300
 
241
- | Event Type | Description | Key Fields |
242
- | ----------------- | --------------------------- | --------------------- |
243
- | `iteration_start` | Agent loop iteration begins | `iteration` |
244
- | `text_delta` | Partial text response | `delta` |
245
- | `thinking_start` | Reasoning phase begins | - |
246
- | `thinking_delta` | Reasoning content | `delta` |
247
- | `thinking_end` | Reasoning phase ends | - |
248
- | `tool_call_start` | Tool invocation begins | `name`, `id` |
249
- | `tool_call_delta` | Tool arguments stream | `delta` |
250
- | `tool_call_end` | Tool call complete | `name`, `args` |
251
- | `tool_result` | Tool execution result | `result`, `error` |
252
- | `iteration_end` | Iteration complete | `usage`, `iteration` |
253
- | `done` | Stream finished | `stopReason`, `usage` |
254
- | `error` | Error occurred | `error` |
301
+ | Event Type | Description | Key Fields |
302
+ | ------------------------------------------------------- | ------------------------------------------------ | -------------------------------------------------- |
303
+ | `session_created` | New session stream starts | `sessionId` |
304
+ | `text_start` / `text_delta` / `text_end` | Assistant text stream | `delta`, `content` |
305
+ | `thinking_start` / `thinking_delta` / `thinking_end` | Model reasoning stream | `delta`, `content` |
306
+ | `tool_call_start` / `tool_call_delta` / `tool_call_end` | Tool call lifecycle | `callId`, `toolName`, `toolCall` |
307
+ | `tool_output_start` / `tool_output_delta` | Live tool stdout/stderr stream | `tool_call_id`, `delta`, `isStderr` |
308
+ | `tool_result` | Tool execution result | `tool_call_id`, `result`, `isError` |
309
+ | `tool_approval_requested` | High-risk tool needs approval | `tool_call_id`, `toolName`, `riskLevel`, `args` |
310
+ | `requires_action` | Execution paused for approval or AskUser answers | `kind`, `checkpoint`, `checkpointRef`, `questions` |
311
+ | `tool_skipped` | Tool execution skipped | `tool_call_id`, `toolName`, `reason` |
312
+ | `iteration_start` / `iteration_end` | Agent loop iteration lifecycle | `iteration`, `usage` |
313
+ | `subagent_event` | Forwarded subagent status | `subagentId`, `subagentType`, `phase` |
314
+ | `compression_start` / `compression_end` | Context compression lifecycle | `tokensBefore`, `tokensAfter`, `strategy` |
315
+ | `hook_evaluation` | Prompt-hook evaluation lifecycle | `hookName`, `phase`, `status` |
316
+ | `done` | Stream finished | `stopReason`, `modelStopReason`, `error`, `usage` |
255
317
 
256
318
  ### Session State Management
257
319
 
@@ -260,7 +322,7 @@ for await (const event of session.receive()) {
260
322
  console.log(session.messages) // Message[]
261
323
 
262
324
  // Check session status
263
- console.log(session.status) // 'idle' | 'running' | 'completed' | 'error'
325
+ console.log(session.status) // 'active' | 'paused' | 'completed' | 'error' | 'archived'
264
326
 
265
327
  // Get token usage
266
328
  console.log(session.usage)
@@ -320,8 +382,8 @@ for await (const event of session.receive()) {
320
382
  }
321
383
  }
322
384
 
323
- // Session now contains full conversation history
324
- console.log(session.messages.length) // 4 (2 user, 2 assistant)
385
+ // Session now contains the full conversation history plus the system prompt
386
+ console.log(session.messages.length) // 5 (system + 2 user + 2 assistant)
325
387
  ```
326
388
 
327
389
  ### Resuming Sessions
@@ -358,7 +420,7 @@ for await (const event of resumed.receive()) {
358
420
  }
359
421
  ```
360
422
 
361
- ### Session Lifecycle Hooks
423
+ ### Session Utilities
362
424
 
363
425
  ```typescript
364
426
  // Add message manually
@@ -370,8 +432,9 @@ session.addMessage({
370
432
  // Save session state manually
371
433
  await session.save()
372
434
 
373
- // Clear session history
374
- session.messages = []
435
+ // Advanced: direct message mutation is allowed, but prefer snapshots/checkpoints
436
+ // when you need reproducible restore flows.
437
+ session.messages.push({ role: 'assistant', content: 'Synthetic entry' })
375
438
  ```
376
439
 
377
440
  ## 🤖 Agent Configuration
@@ -416,7 +479,9 @@ interface AgentOptions {
416
479
  agent.setModel({ provider: 'openai', modelId: 'gpt-4o-mini' })
417
480
 
418
481
  // Switch to another concrete model client instance
419
- const otherModelClient = createModel({ adapter: createOpenAIAdapter({ defaultModelId: 'gpt-4o' }) })
482
+ const otherModelClient = createModel({
483
+ adapter: createOpenAIAdapter({ defaultModelId: 'gpt-4o' }),
484
+ })
420
485
  agent.setModel(otherModelClient)
421
486
  ```
422
487
 
@@ -443,21 +508,22 @@ await sessionManager.destroy('session-id')
443
508
 
444
509
  GoatChain SDK exports the following built-in tools:
445
510
 
446
- | Tool Class | Runtime Name | Category | Purpose |
447
- | --- | --- | --- | --- |
448
- | `ReadTool` | `Read` | File | Read file content (text, binary metadata, and selected converted formats) |
449
- | `WriteTool` | `Write` | File | Create or overwrite files |
450
- | `EditTool` | `Edit` | File | In-place text replacement edits |
451
- | `GlobTool` | `Glob` | File/Search | Find files by glob pattern |
452
- | `GrepTool` | `Grep` | File/Search | Search file contents by pattern |
453
- | `BashTool` | `Bash` | Command | Execute shell commands |
454
- | `WebSearchTool` | `WebSearch` | Web | Search the web (e.g. via Serper API) |
455
- | `WebFetchTool` | `WebFetch` | Web | Fetch and extract content from a specific URL |
456
- | `TodoWriteTool` | `TodoWrite` | Planning | Manage structured todo lists |
457
- | `TodoPlanTool` | `TodoPlan` | Planning | Create/update planning todos for plan flows |
458
- | `AskUserTool` | `AskUserQuestion` | Interaction | Ask the user structured follow-up questions |
459
- | `EnterPlanModeTool` | `EnterPlanMode` | Mode | Enter plan mode |
460
- | `ExitPlanModeTool` | `ExitPlanMode` | Mode | Exit plan mode |
511
+ | Tool Class | Runtime Name | Category | Purpose |
512
+ | ------------------- | ----------------- | ----------- | ------------------------------------------------------------------------- |
513
+ | `ReadTool` | `Read` | File | Read file content (text, binary metadata, and selected converted formats) |
514
+ | `WriteTool` | `Write` | File | Create or overwrite files |
515
+ | `EditTool` | `Edit` | File | In-place text replacement edits |
516
+ | `GlobTool` | `Glob` | File/Search | Find files by glob pattern |
517
+ | `GrepTool` | `Grep` | File/Search | Search file contents by pattern |
518
+ | `BashTool` | `Bash` | Command | Execute shell commands |
519
+ | `WebSearchTool` | `WebSearch` | Web | Search the web (e.g. via Serper API) |
520
+ | `WebFetchTool` | `WebFetch` | Web | Fetch and extract content from a specific URL |
521
+ | `TaskTool` | `Task` | Subagent | Run a registered subagent task (for example Explore) |
522
+ | `TodoWriteTool` | `TodoWrite` | Planning | Manage structured todo lists |
523
+ | `TodoPlanTool` | `TodoPlan` | Planning | Create/update planning todos for plan flows |
524
+ | `AskUserTool` | `AskUserQuestion` | Interaction | Ask the user structured follow-up questions |
525
+ | `EnterPlanModeTool` | `EnterPlanMode` | Mode | Enter plan mode |
526
+ | `ExitPlanModeTool` | `ExitPlanMode` | Mode | Exit plan mode |
461
527
 
462
528
  ```typescript
463
529
  import {
@@ -560,14 +626,14 @@ tools.register(
560
626
 
561
627
  **How each file tool uses `cwd`:**
562
628
 
563
- | Tool | What it does | How `cwd` is applied | Per-call override | Extra sandbox options |
564
- | --- | --- | --- | --- | --- |
565
- | `ReadTool` | Reads files (and some converted formats) | Relative `file_path` resolves from `cwd` | `file_path` can be absolute | `allowedDirectory`, `fileBlacklist`, `disableBlacklist` |
566
- | `WriteTool` | Writes/overwrites files | Relative `file_path` resolves from `cwd` | `file_path` can be absolute | `allowedDirectory`, `fileBlacklist`, `disableBlacklist` |
567
- | `EditTool` | Replaces `old_string` with `new_string` in a file | Relative `file_path` resolves from `cwd` | `file_path` can be absolute | `fileBlacklist`, `disableBlacklist` |
568
- | `GlobTool` | Finds files by pattern | Search root defaults to `cwd` | `path` argument can change search root | `fileBlacklist`, `disableBlacklist` |
569
- | `GrepTool` | Searches text content in files | Search runs under `cwd` | `path` argument narrows search scope | `fileBlacklist`, `disableBlacklist` |
570
- | `BashTool` | Runs shell commands | Commands execute in `cwd` | `workdir` argument overrides per call | None |
629
+ | Tool | What it does | How `cwd` is applied | Per-call override | Extra sandbox options |
630
+ | ----------- | ------------------------------------------------- | ---------------------------------------- | -------------------------------------- | ------------------------------------------------------- |
631
+ | `ReadTool` | Reads files (and some converted formats) | Relative `file_path` resolves from `cwd` | `file_path` can be absolute | `allowedDirectory`, `fileBlacklist`, `disableBlacklist` |
632
+ | `WriteTool` | Writes/overwrites files | Relative `file_path` resolves from `cwd` | `file_path` can be absolute | `allowedDirectory`, `fileBlacklist`, `disableBlacklist` |
633
+ | `EditTool` | Replaces `old_string` with `new_string` in a file | Relative `file_path` resolves from `cwd` | `file_path` can be absolute | `fileBlacklist`, `disableBlacklist` |
634
+ | `GlobTool` | Finds files by pattern | Search root defaults to `cwd` | `path` argument can change search root | `fileBlacklist`, `disableBlacklist` |
635
+ | `GrepTool` | Searches text content in files | Search runs under `cwd` | `path` argument narrows search scope | `fileBlacklist`, `disableBlacklist` |
636
+ | `BashTool` | Runs shell commands | Commands execute in `cwd` | `workdir` argument overrides per call | None |
571
637
 
572
638
  **Directory & Protection Options:**
573
639
 
@@ -677,23 +743,63 @@ interface AgentHooks {
677
743
  sessionStart?: (ctx: SessionStartContext) => Promise<void>
678
744
  sessionEnd?: (ctx: SessionEndContext) => Promise<void>
679
745
  stop?: (ctx: StopContext) => Promise<void>
680
- userPromptSubmit?: (ctx: UserPromptSubmitContext) => Promise<UserPromptSubmitResult>
746
+ userPromptSubmit?:
747
+ | ((ctx: UserPromptSubmitContext) => Promise<UserPromptSubmitResult>)
748
+ | PromptHookEntry
749
+ | Array<
750
+ | ((ctx: UserPromptSubmitContext) => Promise<UserPromptSubmitResult>)
751
+ | PromptHookEntry
752
+ >
681
753
 
682
754
  // Tool lifecycle
683
755
  // - Can modify tool call with modifiedToolCall
684
- preToolUse?: (ctx: ToolHookContext) => Promise<PreToolUseResult | void>
685
- permissionRequest?: (ctx: ToolHookContext) => Promise<PermissionRequestResult>
686
- postToolUse?: (ctx: ToolHookContext, result: unknown) => Promise<void>
687
- postToolUseFailure?: (ctx: ToolHookContext, error: Error) => Promise<void>
756
+ preToolUse?:
757
+ | ((ctx: ToolHookContext) => Promise<PreToolUseResult | void>)
758
+ | PromptHookEntry
759
+ | Array<
760
+ | ((ctx: ToolHookContext) => Promise<PreToolUseResult | void>)
761
+ | PromptHookEntry
762
+ >
763
+ permissionRequest?:
764
+ | ((ctx: ToolHookContext) => Promise<PermissionRequestResult>)
765
+ | PromptHookEntry
766
+ | Array<
767
+ | ((ctx: ToolHookContext) => Promise<PermissionRequestResult>)
768
+ | PromptHookEntry
769
+ >
770
+ postToolUse?:
771
+ | ((ctx: ToolHookContext, result: unknown) => Promise<void>)
772
+ | PromptHookEntry
773
+ | Array<
774
+ | ((ctx: ToolHookContext, result: unknown) => Promise<void>)
775
+ | PromptHookEntry
776
+ >
777
+ postToolUseFailure?:
778
+ | ((ctx: ToolHookContext, error: Error) => Promise<void>)
779
+ | PromptHookEntry
780
+ | Array<
781
+ | ((ctx: ToolHookContext, error: Error) => Promise<void>)
782
+ | PromptHookEntry
783
+ >
688
784
 
689
785
  // Subagent lifecycle (used by parallel task/subagent middleware)
690
786
  subagentStart?: (ctx: SubagentStartContext) => Promise<void>
691
- subagentStop?: (ctx: SubagentStopContext) => Promise<void>
787
+ subagentStop?:
788
+ | ((ctx: SubagentStopContext) => Promise<void>)
789
+ | PromptHookEntry
790
+ | Array<((ctx: SubagentStopContext) => Promise<void>) | PromptHookEntry>
692
791
  }
693
792
 
694
793
  // Backward-compatible alias
695
794
  type ToolHooks = AgentHooks
696
795
 
796
+ interface PromptHookEntry {
797
+ type: 'prompt'
798
+ prompt: string // use $ARGUMENTS to inject serialized input
799
+ model?: { provider: string; modelId: string }
800
+ timeoutMs?: number // default: 30000
801
+ }
802
+
697
803
  interface BaseHookContext {
698
804
  sessionId: string
699
805
  }
@@ -782,6 +888,113 @@ interface SubagentStopContext extends BaseHookContext {
782
888
  }
783
889
  ```
784
890
 
891
+ ### Prompt Hook Evaluation
892
+
893
+ Prompt hooks are evaluation-only in current SDK behavior:
894
+
895
+ - Prompt evaluation does not change execution decisions or mutate input/tool calls
896
+ - Supported prompt hooks: `userPromptSubmit`, `preToolUse`, `permissionRequest`, `postToolUse`, `postToolUseFailure`, `subagentStop`
897
+ - `permissionRequest` prompt evaluation only runs when the approval path is entered
898
+ - Each evaluation is persisted in `session.metadata._hookEvaluations`
899
+
900
+ Prompt evaluation emits `hook_evaluation` stream events with `phase`:
901
+
902
+ - `start`
903
+ - `stream` (text delta)
904
+ - `end` (status/result/error)
905
+
906
+ `hook_evaluation` event shape:
907
+
908
+ ```typescript
909
+ interface HookEvaluationEvent extends BaseEvent {
910
+ type: 'hook_evaluation'
911
+ evaluationId: string
912
+ hookName:
913
+ | 'permissionRequest'
914
+ | 'preToolUse'
915
+ | 'postToolUse'
916
+ | 'postToolUseFailure'
917
+ | 'subagentStop'
918
+ | 'userPromptSubmit'
919
+ phase: 'start' | 'stream' | 'end'
920
+ prompt?: string
921
+ input?: unknown
922
+ delta?: string
923
+ rawResponse?: string
924
+ result?: unknown
925
+ usage?: Usage
926
+ durationMs?: number
927
+ status?: 'success' | 'error' | 'timeout'
928
+ error?: { code?: string; message: string }
929
+ toolCallId?: string
930
+ }
931
+ ```
932
+
933
+ Metadata persistence shape:
934
+
935
+ ```typescript
936
+ session.metadata._hookEvaluations = {
937
+ preToolUse: [
938
+ {
939
+ evaluationId: '...',
940
+ timestamp: 1730000000000,
941
+ hookName: 'preToolUse',
942
+ prompt: '...',
943
+ input: { ... },
944
+ status: 'success',
945
+ durationMs: 120,
946
+ rawResponse: '{"ok":true}',
947
+ result: { ok: true },
948
+ usage: { promptTokens: 100, completionTokens: 20, totalTokens: 120 },
949
+ toolCallId: 'call_123',
950
+ },
951
+ ],
952
+ }
953
+ ```
954
+
955
+ Complete prompt hook example (function hook + prompt hook + event handling):
956
+
957
+ ```typescript
958
+ import { Agent } from 'goatchain'
959
+ import type { HookEvaluationEvent } from 'goatchain'
960
+
961
+ const session = await agent.createSession({
962
+ hooks: {
963
+ preToolUse: [
964
+ async (ctx) => {
965
+ // normal behavior hook
966
+ return undefined
967
+ },
968
+ {
969
+ type: 'prompt',
970
+ prompt: 'Analyze this tool call: $ARGUMENTS',
971
+ },
972
+ ],
973
+ permissionRequest: {
974
+ type: 'prompt',
975
+ prompt: 'Review approval context: $ARGUMENTS',
976
+ },
977
+ },
978
+ })
979
+
980
+ session.send('Do the task', {
981
+ toolContext: {
982
+ approval: { strategy: 'high_risk' },
983
+ },
984
+ })
985
+
986
+ for await (const event of session.receive()) {
987
+ if (event.type === 'hook_evaluation') {
988
+ const ev = event as HookEvaluationEvent
989
+ if (ev.phase === 'end') {
990
+ console.log('hook evaluation done:', ev.hookName, ev.status, ev.result)
991
+ }
992
+ }
993
+ }
994
+
995
+ console.log(session.metadata?._hookEvaluations)
996
+ ```
997
+
785
998
  ### Hook Execution Order
786
999
 
787
1000
  Typical order in one run:
@@ -804,7 +1017,12 @@ const agent = new Agent({
804
1017
  name: 'MyAgent',
805
1018
  systemPrompt: 'You are helpful.',
806
1019
  model,
807
- tools: new ToolRegistry().register(new ReadTool()).register(new WriteTool()),
1020
+ tools: (() => {
1021
+ const tools = new ToolRegistry()
1022
+ tools.register(new ReadTool())
1023
+ tools.register(new WriteTool())
1024
+ return tools
1025
+ })(),
808
1026
  })
809
1027
 
810
1028
  // Create session with hooks
@@ -1039,7 +1257,7 @@ const session = await agent.createSession({
1039
1257
 
1040
1258
  ### Tool Context
1041
1259
 
1042
- The `toolContext` parameter in `send()` and `receive()` allows passing additional context:
1260
+ The `toolContext` parameter in `send()` and `receive()` is used for approval state and AskUser resume data:
1043
1261
 
1044
1262
  ```typescript
1045
1263
  session.send('Do something risky', {
@@ -1052,8 +1270,14 @@ session.send('Do something risky', {
1052
1270
  tool_call_id_456: { approved: false, reason: 'Too dangerous' },
1053
1271
  },
1054
1272
  },
1055
- // Your custom context
1056
- custom: { userId: '123', environment: 'production' },
1273
+ askUser: {
1274
+ answers: {
1275
+ tool_call_id_789: {
1276
+ framework: 'React',
1277
+ styling: 'Tailwind CSS',
1278
+ },
1279
+ },
1280
+ },
1057
1281
  },
1058
1282
  })
1059
1283
  ```
@@ -1070,7 +1294,7 @@ outer:before → inner:before → exec (model.stream) → inner:after → outer:
1070
1294
 
1071
1295
  ```typescript
1072
1296
  // Add named middleware (recommended)
1073
- agent.use(async (state, next) => {
1297
+ await agent.use(async (state, next) => {
1074
1298
  const start = Date.now()
1075
1299
  console.log(`[${state.iteration}] Before model call`)
1076
1300
 
@@ -1087,7 +1311,7 @@ agent.removeMiddleware('logging')
1087
1311
  console.log(agent.middlewareNames) // ['logging', 'compression', ...]
1088
1312
 
1089
1313
  // Use unsubscribe function
1090
- const unsubscribe = agent.use(middleware, 'temp')
1314
+ const unsubscribe = await agent.use(middleware, 'temp')
1091
1315
  unsubscribe() // Remove middleware
1092
1316
  ```
1093
1317
 
@@ -1101,10 +1325,10 @@ Adds planning phase before execution:
1101
1325
  import { createPlanModeMiddleware } from 'goatchain'
1102
1326
 
1103
1327
  // Automatically named 'plan_mode'
1104
- agent.use(createPlanModeMiddleware())
1328
+ await agent.use(createPlanModeMiddleware())
1105
1329
 
1106
1330
  // With custom configuration
1107
- agent.use(
1331
+ await agent.use(
1108
1332
  createPlanModeMiddleware({
1109
1333
  name: 'my-plan', // Custom name
1110
1334
  planPrompt: 'Create a detailed plan...', // Custom prompt
@@ -1114,35 +1338,36 @@ agent.use(
1114
1338
 
1115
1339
  #### Context Compression Middleware
1116
1340
 
1117
- Automatically compresses context when token limit is reached using a two-stage strategy:
1341
+ Automatically compresses context from the full raw transcript when the prompt approaches the model context window. The middleware now:
1342
+
1343
+ - reuses any persisted rolling summary first
1344
+ - removes old `tool` messages before touching the current round
1345
+ - preserves the last user round
1346
+ - performs at most one AI summary pass per overflow event
1347
+ - guarantees the final prompt stays within `contextLength` or fails locally before the model call
1348
+ - can emit per-stage snapshots in the large E2E example for inspection
1118
1349
 
1119
1350
  ```typescript
1120
1351
  import { createContextCompressionMiddleware } from 'goatchain'
1121
1352
 
1122
1353
  // Automatically named 'context_compression'
1123
- agent.use(
1354
+ await agent.use(
1124
1355
  createContextCompressionMiddleware({
1125
- maxTokens: 128000,
1126
- protectedTurns: 2, // Keep last 2 conversation turns
1127
- model: model,
1128
- stateStore: agent.stateStore,
1129
- toolCompressionTarget: 0.45, // Compress to 45% of maxTokens
1130
- minKeepToolResults: 5, // Keep last 5 tool results
1131
- // Optional: Enable detailed logging
1132
- enableLogging: true,
1133
- logFilePath: 'compression-logs.jsonl',
1356
+ contextLength: 128000,
1134
1357
  }),
1135
1358
  )
1136
1359
  ```
1137
1360
 
1138
- See [Context Compression Logging Guide](./docs/context-compression-logging.md) for details on monitoring compression behavior.
1361
+ The large E2E example writes `round-N/stage1.json` through `stage4.json` under `examples/output/context-compression-large-e2e/` by default, so you can inspect each compression step directly.
1362
+
1363
+ See [`src/spec/middleware.md`](./src/spec/middleware.md) for the full middleware and compression spec.
1139
1364
 
1140
1365
  ### Custom Middleware Examples
1141
1366
 
1142
1367
  #### Logging Middleware
1143
1368
 
1144
1369
  ```typescript
1145
- agent.use(async (state, next) => {
1370
+ await agent.use(async (state, next) => {
1146
1371
  console.log(`Iteration ${state.iteration}:`, {
1147
1372
  messages: state.messages.length,
1148
1373
  pendingTools: state.pendingToolCalls.length,
@@ -1162,7 +1387,7 @@ agent.use(async (state, next) => {
1162
1387
  #### Error Handling Middleware
1163
1388
 
1164
1389
  ```typescript
1165
- agent.use(async (state, next) => {
1390
+ await agent.use(async (state, next) => {
1166
1391
  try {
1167
1392
  return await next(state)
1168
1393
  } catch (error) {
@@ -1182,7 +1407,7 @@ import { RateLimiter } from 'some-rate-limiter'
1182
1407
 
1183
1408
  const limiter = new RateLimiter({ requestsPerMinute: 60 })
1184
1409
 
1185
- agent.use(async (state, next) => {
1410
+ await agent.use(async (state, next) => {
1186
1411
  await limiter.acquire()
1187
1412
  return next(state)
1188
1413
  }, 'rate-limiter')
@@ -1191,7 +1416,7 @@ agent.use(async (state, next) => {
1191
1416
  #### Custom Retry Middleware
1192
1417
 
1193
1418
  ```typescript
1194
- agent.use(async (state, next) => {
1419
+ await agent.use(async (state, next) => {
1195
1420
  let retries = 3
1196
1421
 
1197
1422
  while (retries > 0) {
@@ -1221,10 +1446,12 @@ interface AgentLoopState {
1221
1446
  iteration: number // Current iteration number
1222
1447
  pendingToolCalls: ToolCallWithResult[] // Pending tool executions
1223
1448
  currentResponse: string // Current LLM response
1449
+ currentThinking?: string // Current reasoning content, if the model emits it
1224
1450
  shouldContinue: boolean // Whether to continue loop
1225
1451
  stopReason?: string // Reason for stopping
1226
- usage?: Usage // Token usage
1452
+ usage: Usage // Cumulative token usage
1227
1453
  error?: Error // Error if any
1454
+ metadata: Record<string, unknown> // Middleware/hook shared runtime data
1228
1455
  }
1229
1456
  ```
1230
1457
 
@@ -1347,8 +1574,15 @@ Implement custom state stores:
1347
1574
  interface StateStore {
1348
1575
  deleteOnComplete: boolean
1349
1576
 
1577
+ save<T>(sessionId: string, key: string, data: T): Promise<void>
1578
+ load<T>(sessionId: string, key: string): Promise<T | undefined>
1579
+ delete(sessionId: string, key: string): Promise<void>
1580
+ listKeys(sessionId: string): Promise<string[]>
1581
+ listSessions(): Promise<string[]>
1582
+
1583
+ // Checkpoint helpers
1350
1584
  saveCheckpoint(checkpoint: AgentLoopCheckpoint): Promise<void>
1351
- loadCheckpoint(sessionId: string): Promise<AgentLoopCheckpoint | null>
1585
+ loadCheckpoint(sessionId: string): Promise<AgentLoopCheckpoint | undefined>
1352
1586
  deleteCheckpoint(sessionId: string): Promise<void>
1353
1587
  listCheckpoints(): Promise<AgentLoopCheckpoint[]>
1354
1588
  }
@@ -1366,17 +1600,8 @@ interface AgentLoopCheckpoint {
1366
1600
  ### Manual Checkpoint Management
1367
1601
 
1368
1602
  ```typescript
1369
- // Save checkpoint manually
1370
- await stateStore.saveCheckpoint({
1371
- sessionId: session.id,
1372
- messages: session.messages,
1373
- iteration: 3,
1374
- usage: session.usage,
1375
- createdAt: Date.now(),
1376
- updatedAt: Date.now(),
1377
- })
1378
-
1379
- // Load checkpoint
1603
+ // Prefer SDK-managed checkpoints during session.receive().
1604
+ // You can still inspect or clean them up manually:
1380
1605
  const checkpoint = await stateStore.loadCheckpoint('session-id')
1381
1606
 
1382
1607
  // List all checkpoints
@@ -1456,7 +1681,7 @@ for await (const event of session.receive()) {
1456
1681
  if (event.type === 'text_delta') {
1457
1682
  process.stdout.write(event.delta)
1458
1683
  } else if (event.type === 'tool_call_start') {
1459
- console.log(`\nCalling: ${event.name}`)
1684
+ console.log(`\nCalling: ${event.toolName}`)
1460
1685
  } else if (event.type === 'tool_result') {
1461
1686
  console.log(`Result: ${JSON.stringify(event.result).slice(0, 100)}...`)
1462
1687
  }
@@ -1537,7 +1762,7 @@ const agent = new Agent({
1537
1762
  })
1538
1763
 
1539
1764
  // Add logging middleware
1540
- agent.use(async (state, next) => {
1765
+ await agent.use(async (state, next) => {
1541
1766
  console.log(`\n=== Iteration ${state.iteration} ===`)
1542
1767
  const result = await next(state)
1543
1768
  console.log(`Tokens used: ${result.usage?.totalTokens || 0}`)
@@ -1545,7 +1770,7 @@ agent.use(async (state, next) => {
1545
1770
  }, 'logger')
1546
1771
 
1547
1772
  // Add plan mode
1548
- agent.use(createPlanModeMiddleware())
1773
+ await agent.use(createPlanModeMiddleware())
1549
1774
 
1550
1775
  const session = await agent.createSession()
1551
1776
  session.send('Create a todo list app with React and TypeScript')
@@ -1611,7 +1836,12 @@ import {
1611
1836
  Agent,
1612
1837
  createModel,
1613
1838
  createOpenAIAdapter,
1614
- createBuiltinTools,
1839
+ ToolRegistry,
1840
+ ReadTool,
1841
+ WriteTool,
1842
+ EditTool,
1843
+ GlobTool,
1844
+ GrepTool,
1615
1845
  } from 'goatchain'
1616
1846
 
1617
1847
  const model = createModel({
@@ -1621,11 +1851,18 @@ const model = createModel({
1621
1851
  }),
1622
1852
  })
1623
1853
 
1854
+ const tools = new ToolRegistry()
1855
+ tools.register(new ReadTool())
1856
+ tools.register(new WriteTool())
1857
+ tools.register(new EditTool())
1858
+ tools.register(new GlobTool())
1859
+ tools.register(new GrepTool())
1860
+
1624
1861
  const agent = new Agent({
1625
1862
  name: 'File Agent',
1626
1863
  systemPrompt: 'You are a file management assistant.',
1627
1864
  model,
1628
- tools: createBuiltinTools(), // All file tools included
1865
+ tools,
1629
1866
  })
1630
1867
 
1631
1868
  // Set working directory at session creation
@@ -1732,7 +1969,7 @@ for await (const event of session.receive()) {
1732
1969
  if (event.type === 'text_delta') {
1733
1970
  process.stdout.write(event.delta)
1734
1971
  } else if (event.type === 'tool_call_start') {
1735
- console.log(`\n[Tool] ${event.name}`)
1972
+ console.log(`\n[Tool] ${event.toolName}`)
1736
1973
  }
1737
1974
  }
1738
1975
  ```
@@ -1824,12 +2061,39 @@ agent.setModel({ provider: 'openai', modelId: 'gpt-4o-mini' })
1824
2061
 
1825
2062
  #### Methods
1826
2063
 
1827
- **`send(input): void`**
2064
+ **`send(input, options?): string`**
1828
2065
 
1829
- Send a message to the session.
2066
+ Enqueue a message and return its queue message ID.
1830
2067
 
1831
2068
  ```typescript
1832
- session.send('Hello!')
2069
+ const id = session.send('Hello!')
2070
+ ```
2071
+
2072
+ **`sendBatch(messages): string[]`**
2073
+
2074
+ Batch enqueue messages and return queue message IDs.
2075
+
2076
+ ```typescript
2077
+ const ids = session.sendBatch([
2078
+ { input: 'task-1', priority: 1 },
2079
+ { input: 'task-2', priority: 2 },
2080
+ ])
2081
+ ```
2082
+
2083
+ **`cancelQueuedMessage(messageId): boolean`**
2084
+
2085
+ Cancel a queued message by ID.
2086
+
2087
+ ```typescript
2088
+ session.cancelQueuedMessage(id)
2089
+ ```
2090
+
2091
+ **`getQueueStatus(): MessageQueueStatus`**
2092
+
2093
+ Query queue length, preview list, processing status, and config.
2094
+
2095
+ ```typescript
2096
+ console.log(session.getQueueStatus())
1833
2097
  ```
1834
2098
 
1835
2099
  **`receive(options?): AsyncGenerator<AgentEvent>`**
@@ -1894,7 +2158,7 @@ session.setCwd('/path/to/project')
1894
2158
  #### Properties
1895
2159
 
1896
2160
  - `id: string` - Session ID
1897
- - `status: SessionStatus` - Session status ('idle' | 'running' | 'completed' | 'error')
2161
+ - `status: SessionStatus` - Session status (`'active' | 'paused' | 'completed' | 'error' | 'archived'`)
1898
2162
  - `messages: Message[]` - Message history
1899
2163
  - `usage: Usage` - Token usage statistics
1900
2164
  - `createdAt: number` - Creation timestamp
@@ -1905,9 +2169,12 @@ session.setCwd('/path/to/project')
1905
2169
  ```typescript
1906
2170
  interface Message {
1907
2171
  role: 'system' | 'user' | 'assistant' | 'tool'
1908
- content: string | ToolCall[] | ToolResult[]
1909
- name?: string // For tool messages
1910
- toolCallId?: string // For tool messages
2172
+ content: MessageContent
2173
+ reasoning_content?: string // Assistant messages
2174
+ tool_calls?: ToolCall[] // Assistant messages
2175
+ tool_call_id?: string // Tool messages
2176
+ name?: string
2177
+ isError?: boolean // Tool messages
1911
2178
  }
1912
2179
  ```
1913
2180
 
@@ -1925,10 +2192,17 @@ interface Usage {
1925
2192
 
1926
2193
  ```typescript
1927
2194
  type AgentEvent =
2195
+ | TextStartEvent
1928
2196
  | TextDeltaEvent
2197
+ | TextEndEvent
1929
2198
  | ToolCallStartEvent
1930
2199
  | ToolCallDeltaEvent
1931
2200
  | ToolCallEndEvent
2201
+ | ToolOutputStartEvent
2202
+ | ToolOutputDeltaEvent
2203
+ | ToolApprovalRequestedEvent
2204
+ | RequiresActionEvent
2205
+ | ToolSkippedEvent
1932
2206
  | ToolResultEvent
1933
2207
  | ThinkingStartEvent
1934
2208
  | ThinkingDeltaEvent
@@ -1936,7 +2210,6 @@ type AgentEvent =
1936
2210
  | IterationStartEvent
1937
2211
  | IterationEndEvent
1938
2212
  | DoneEvent
1939
- | ErrorEvent
1940
2213
 
1941
2214
  interface TextDeltaEvent {
1942
2215
  type: 'text_delta'
@@ -1945,8 +2218,15 @@ interface TextDeltaEvent {
1945
2218
 
1946
2219
  interface ToolCallStartEvent {
1947
2220
  type: 'tool_call_start'
1948
- id: string
1949
- name: string
2221
+ callId: string
2222
+ toolName?: string
2223
+ }
2224
+
2225
+ interface RequiresActionEvent {
2226
+ type: 'requires_action'
2227
+ kind: 'tool_approval' | 'ask_user'
2228
+ checkpoint?: AgentLoopCheckpoint
2229
+ checkpointRef?: { sessionId: string }
1950
2230
  }
1951
2231
 
1952
2232
  interface ToolResultEvent {
@@ -1958,6 +2238,7 @@ interface ToolResultEvent {
1958
2238
 
1959
2239
  interface DoneEvent {
1960
2240
  type: 'done'
2241
+ finalResponse?: string
1961
2242
  stopReason:
1962
2243
  | 'max_iterations'
1963
2244
  | 'final_response'
@@ -1965,6 +2246,13 @@ interface DoneEvent {
1965
2246
  | 'cancelled'
1966
2247
  | 'approval_required'
1967
2248
  | 'max_follow_ups'
2249
+ modelStopReason?: 'tool_call' | 'final' | 'length' | 'error' | 'cancelled'
2250
+ error?: {
2251
+ code?: string
2252
+ message: string
2253
+ status?: number
2254
+ retryable?: boolean
2255
+ }
1968
2256
  usage?: Usage
1969
2257
  }
1970
2258
  ```
@@ -2023,7 +2311,7 @@ classDiagram
2023
2311
  +status: SessionStatus
2024
2312
  +messages: Message[]
2025
2313
  +usage: Usage
2026
- +send(input): void
2314
+ +send(input, options?): string
2027
2315
  +receive(): AsyncGenerator~AgentEvent~
2028
2316
  +save(): Promise~void~
2029
2317
  }
@@ -2075,7 +2363,13 @@ See [docs/cli.md](./docs/cli.md) and [docs/server.md](./docs/server.md) for deta
2075
2363
  Expose DimCode as an Agent Client Protocol server for editor integrations:
2076
2364
 
2077
2365
  ```bash
2078
- bun run acp-server
2366
+ dim acp
2367
+ ```
2368
+
2369
+ For source checkouts, use a cwd-independent command:
2370
+
2371
+ ```bash
2372
+ node /absolute/path/to/GoatChain/scripts/acpx-agent.mjs
2079
2373
  ```
2080
2374
 
2081
2375
  **Configuration for Zed** (`settings.json`):
@@ -2084,13 +2378,16 @@ bun run acp-server
2084
2378
  {
2085
2379
  "agent_servers": {
2086
2380
  "dimcode": {
2087
- "command": "pnpm",
2088
- "args": ["--dir", "/path/to/DimCode", "acp-server"]
2381
+ "command": "/absolute/path/to/dim",
2382
+ "args": ["acp"]
2089
2383
  }
2090
2384
  }
2091
2385
  }
2092
2386
  ```
2093
2387
 
2388
+ For OpenClaw `acpx`, use either `/absolute/path/to/dim acp` or `node /absolute/path/to/GoatChain/scripts/acpx-agent.mjs`.
2389
+ Do not use `bun run acp-server` there; it depends on the launcher cwd being the GoatChain repo root.
2390
+
2094
2391
  See [docs/acp-server.md](./docs/acp-server.md) for details.
2095
2392
 
2096
2393
  ## 📚 Documentation