qlogicagent 2.20.1 → 2.20.3

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.
Files changed (191) hide show
  1. package/dist/agent-contract.js +1 -1
  2. package/dist/agent.js +39 -35
  3. package/dist/cli.js +1 -1
  4. package/dist/host-contract.js +1 -1
  5. package/dist/host-session-collection-contract.js +1 -1
  6. package/dist/index.js +624 -549
  7. package/dist/memory-category-contract.js +1 -0
  8. package/dist/orchestration.js +1 -1
  9. package/dist/project-memory-host.js +21 -21
  10. package/dist/protocol.js +1 -1
  11. package/dist/skill-category-contract.js +1 -0
  12. package/dist/skills/builtin/web-research/SKILL.md +37 -0
  13. package/dist/skills/mcp/astraclaw-native-mcp-server.js +1 -1
  14. package/dist/tunables.js +1 -1
  15. package/dist/types/agent/memory-recall-context.d.ts +6 -9
  16. package/dist/types/agent/memory-recall-injection.d.ts +1 -6
  17. package/dist/types/agent/tool-loop/agent-argument-normalizer.d.ts +12 -0
  18. package/dist/types/agent/tool-loop/artifact-final-contract.d.ts +8 -0
  19. package/dist/types/agent/tool-loop.d.ts +5 -0
  20. package/dist/types/agent/types.d.ts +5 -0
  21. package/dist/types/cli/acp-extended-handlers.d.ts +11 -9
  22. package/dist/types/cli/acp-session-handlers.d.ts +1 -1
  23. package/dist/types/cli/acp-session-host.d.ts +8 -0
  24. package/dist/types/cli/agent-runtime-bootstrap.d.ts +7 -1
  25. package/dist/types/cli/agent-runtime-session-state.d.ts +23 -6
  26. package/dist/types/cli/atomic-text-file.d.ts +31 -0
  27. package/dist/types/cli/background-context-capsule.d.ts +12 -0
  28. package/dist/types/cli/base-tool-bootstrap.d.ts +1 -0
  29. package/dist/types/cli/cli-acp-request-handler.d.ts +3 -1
  30. package/dist/types/cli/community-workflow-installer.d.ts +1 -1
  31. package/dist/types/cli/core-tool-coordinator.d.ts +1 -0
  32. package/dist/types/cli/core-tools/agent-tool-service.d.ts +17 -1
  33. package/dist/types/cli/dev-tool-bootstrap.d.ts +3 -2
  34. package/dist/types/cli/dev-tools/dev-tool-bootstrap-service.d.ts +1 -0
  35. package/dist/types/cli/dev-tools/dev-tool-registry.d.ts +3 -1
  36. package/dist/types/cli/dev-tools/incremental-repo-map.d.ts +81 -0
  37. package/dist/types/cli/dev-tools/lsp-supervisor.d.ts +59 -0
  38. package/dist/types/cli/dev-tools/lsp-tool-service.d.ts +2 -0
  39. package/dist/types/cli/handlers/dream-handler.d.ts +1 -1
  40. package/dist/types/cli/handlers/memory-handler.d.ts +2 -14
  41. package/dist/types/cli/handlers/product-handler.d.ts +6 -24
  42. package/dist/types/cli/handlers/turn-handler.d.ts +1 -0
  43. package/dist/types/cli/idle-dream-coordinator.d.ts +9 -5
  44. package/dist/types/cli/memory-background-coordinator.d.ts +26 -0
  45. package/dist/types/cli/memory-candidate-service.d.ts +2 -3
  46. package/dist/types/cli/memory-coordinator.d.ts +12 -8
  47. package/dist/types/cli/permission-bootstrap.d.ts +10 -1
  48. package/dist/types/cli/product-acp-params.d.ts +29 -0
  49. package/dist/types/cli/resolved-agent-cache.d.ts +18 -7
  50. package/dist/types/cli/rpc-registry.d.ts +2 -1
  51. package/dist/types/cli/runtime-hook-bootstrap.d.ts +2 -0
  52. package/dist/types/cli/stdio-acp-request-host.d.ts +7 -4
  53. package/dist/types/cli/stdio-agent-session-bootstrap.d.ts +8 -2
  54. package/dist/types/cli/stdio-rpc-handler-hosts.d.ts +4 -2
  55. package/dist/types/cli/stdio-runtime-bootstrap.d.ts +1 -0
  56. package/dist/types/cli/stdio-server.d.ts +49 -16
  57. package/dist/types/cli/stdio-session-runtime-coordinator.d.ts +0 -2
  58. package/dist/types/cli/task-distillation-coordinator.d.ts +42 -14
  59. package/dist/types/cli/tool-bootstrap-core-registration.d.ts +14 -4
  60. package/dist/types/cli/tool-bootstrap.d.ts +2 -1
  61. package/dist/types/cli/turn-core.d.ts +4 -0
  62. package/dist/types/cli/turn-lifecycle.d.ts +1 -0
  63. package/dist/types/contracts/hooks.d.ts +3 -2
  64. package/dist/types/contracts/turn-event.d.ts +7 -0
  65. package/dist/types/host-contract/index.d.ts +111 -12
  66. package/dist/types/host-contract/memory-category.d.ts +8 -0
  67. package/dist/types/host-contract/skill-category.d.ts +5 -0
  68. package/dist/types/host-session-collection-contract.d.ts +1 -0
  69. package/dist/types/orchestration/agent-instance.d.ts +15 -0
  70. package/dist/types/orchestration/dag-scheduler.d.ts +18 -0
  71. package/dist/types/orchestration/delegate-approval-policy.d.ts +8 -0
  72. package/dist/types/orchestration/goal-acceptance.d.ts +2 -5
  73. package/dist/types/orchestration/goal-loop-coordinator.d.ts +4 -2
  74. package/dist/types/orchestration/goal-mode-adapters.d.ts +16 -0
  75. package/dist/types/orchestration/goal-run-types.d.ts +6 -1
  76. package/dist/types/orchestration/product-budget.d.ts +35 -16
  77. package/dist/types/orchestration/product-persistence.d.ts +5 -14
  78. package/dist/types/orchestration/product-planner.d.ts +38 -0
  79. package/dist/types/orchestration/product-worktree.d.ts +4 -2
  80. package/dist/types/orchestration/solo-evaluator.d.ts +42 -2
  81. package/dist/types/orchestration/solo-spec-builder.d.ts +3 -0
  82. package/dist/types/orchestration/testing/delegate-test-bridge.d.ts +6 -0
  83. package/dist/types/orchestration/testing/run-state-client-double.d.ts +13 -0
  84. package/dist/types/orchestration/workflow/builtin-capabilities.d.ts +3 -0
  85. package/dist/types/orchestration/workflow/capability-catalog.d.ts +31 -0
  86. package/dist/types/orchestration/workflow/cron-schedule.d.ts +3 -4
  87. package/dist/types/orchestration/workflow/expression.d.ts +2 -1
  88. package/dist/types/orchestration/workflow/n8n-import.d.ts +3 -0
  89. package/dist/types/orchestration/workflow/node-registry.d.ts +2 -2
  90. package/dist/types/orchestration/workflow/node-schema.d.ts +20 -4
  91. package/dist/types/orchestration/workflow/params-schema.d.ts +7 -6
  92. package/dist/types/orchestration/workflow/qla-executor-host.d.ts +4 -1
  93. package/dist/types/orchestration/workflow/run-history-store.d.ts +33 -3
  94. package/dist/types/orchestration/workflow/semantic-acceptance.d.ts +65 -0
  95. package/dist/types/orchestration/workflow/workflow-authoring.d.ts +74 -0
  96. package/dist/types/orchestration/workflow/workflow-bundle.d.ts +4 -0
  97. package/dist/types/orchestration/workflow/workflow-controller.d.ts +16 -4
  98. package/dist/types/orchestration/workflow/workflow-edit-context.d.ts +46 -0
  99. package/dist/types/orchestration/workflow/workflow-intent-contract.d.ts +56 -0
  100. package/dist/types/orchestration/workflow/workflow-patch.d.ts +7 -0
  101. package/dist/types/orchestration/workflow/workflow-runtime.d.ts +5 -0
  102. package/dist/types/orchestration/workflow/workflow-scheduler.d.ts +11 -0
  103. package/dist/types/orchestration/workflow/workflow-store.d.ts +5 -0
  104. package/dist/types/orchestration/workflow/workflow-template-setup.d.ts +65 -0
  105. package/dist/types/orchestration/workflow/workflow-trigger.d.ts +4 -0
  106. package/dist/types/orchestration/workflow-chat-builder.d.ts +105 -20
  107. package/dist/types/orchestration/worktree-task-prompt.d.ts +1 -0
  108. package/dist/types/protocol/agent-contract.d.ts +49 -8
  109. package/dist/types/protocol/notifications.d.ts +1 -1
  110. package/dist/types/protocol/wire/acp-agent-management.d.ts +97 -9
  111. package/dist/types/protocol/wire/acp-protocol.d.ts +7 -1
  112. package/dist/types/protocol/wire/agent-methods.d.ts +15 -1
  113. package/dist/types/protocol/wire/index.d.ts +2 -4
  114. package/dist/types/protocol/wire/memory-provider-lifecycle.d.ts +1 -39
  115. package/dist/types/protocol/wire/notification-payloads.d.ts +96 -1
  116. package/dist/types/protocol/wire/web-capability.d.ts +2 -2
  117. package/dist/types/runtime/config/tunable-defaults.d.ts +0 -6
  118. package/dist/types/runtime/execution/bounded-output-tail.d.ts +7 -0
  119. package/dist/types/runtime/execution/dream-agent.d.ts +8 -5
  120. package/dist/types/runtime/execution/dream-category-context.d.ts +3 -3
  121. package/dist/types/runtime/execution/structured-tool-progress.d.ts +21 -0
  122. package/dist/types/runtime/hooks/memory-hooks.d.ts +20 -8
  123. package/dist/types/runtime/infra/background-tasks.d.ts +5 -1
  124. package/dist/types/runtime/infra/subagent-turn-snapshot.d.ts +32 -0
  125. package/dist/types/runtime/infra/turn-telemetry-store.d.ts +22 -0
  126. package/dist/types/runtime/memory/implicit-extraction.d.ts +23 -7
  127. package/dist/types/runtime/ports/agent-runtime-ports.d.ts +0 -5
  128. package/dist/types/runtime/ports/index.d.ts +2 -3
  129. package/dist/types/runtime/ports/memory-provider.d.ts +31 -16
  130. package/dist/types/runtime/ports/memory-writer.d.ts +55 -6
  131. package/dist/types/runtime/ports/permission-contracts.d.ts +2 -0
  132. package/dist/types/runtime/ports/tool-contracts.d.ts +3 -1
  133. package/dist/types/runtime/prompt/identity-section.d.ts +2 -0
  134. package/dist/types/runtime/session/session-permission-store.d.ts +6 -0
  135. package/dist/types/runtime/tasks/task-types.d.ts +2 -0
  136. package/dist/types/skills/memory/host-memory-provider.d.ts +59 -9
  137. package/dist/types/skills/memory/memory-tool.d.ts +3 -1
  138. package/dist/types/skills/memory/task-distillation.d.ts +22 -3
  139. package/dist/types/skills/permissions/hook-runner.d.ts +8 -0
  140. package/dist/types/skills/permissions/operation-classifier.d.ts +7 -0
  141. package/dist/types/skills/skill-system/skill-category.d.ts +10 -0
  142. package/dist/types/skills/tools/agent-tool.d.ts +13 -21
  143. package/dist/types/skills/tools/edit-tool.d.ts +10 -2
  144. package/dist/types/skills/tools/exec-tool.d.ts +2 -2
  145. package/dist/types/skills/tools/file-text-snapshot.d.ts +24 -0
  146. package/dist/types/skills/tools/lsp-tool.d.ts +1 -1
  147. package/dist/types/skills/tools/patch-tool.d.ts +10 -3
  148. package/dist/types/skills/tools/read-tool.d.ts +23 -1
  149. package/dist/types/skills/tools/repo-map-tool.d.ts +52 -0
  150. package/dist/types/skills/tools/search-tool.d.ts +10 -1
  151. package/dist/types/skills/tools/shell/index.d.ts +1 -2
  152. package/dist/types/skills/tools/shell/shell-command.d.ts +1 -1
  153. package/dist/types/skills/tools/shell/shell-exec.d.ts +2 -2
  154. package/dist/types/skills/tools/shell/task-output.d.ts +12 -3
  155. package/dist/types/skills/tools/subagent-decision-tool.d.ts +15 -0
  156. package/dist/types/skills/tools/task-tool.d.ts +16 -2
  157. package/dist/types/skills/tools/write-tool.d.ts +2 -1
  158. package/dist/types/transport/acp-server.d.ts +1 -0
  159. package/dist/types/transport/host-run-state-client.d.ts +6 -0
  160. package/dist/types/workflow-host.d.ts +8 -1
  161. package/dist/workflow-host.js +9 -9
  162. package/package.json +25 -6
  163. package/dist/types/cli/tool-bootstrap-web-registration.d.ts +0 -14
  164. package/dist/types/orchestration/tool-cascade.d.ts +0 -49
  165. package/dist/types/protocol/wire/channel-ingress.d.ts +0 -29
  166. package/dist/types/protocol/wire/channel.d.ts +0 -89
  167. package/dist/types/runtime/infra/native-mcp-config-sync.d.ts +0 -16
  168. package/dist/types/runtime/memory/categories.d.ts +0 -5
  169. package/dist/types/runtime/memory/recall-category-filter.d.ts +0 -42
  170. package/dist/types/runtime/ports/source-provider.d.ts +0 -39
  171. package/dist/types/runtime/ports/web-search-contracts.d.ts +0 -21
  172. package/dist/types/server/search-svc.d.ts +0 -1
  173. package/dist/types/skills/memory/local-embedding.d.ts +0 -80
  174. package/dist/types/skills/memory/memory-config-resolver.d.ts +0 -4
  175. package/dist/types/skills/memory/memory-embedding-config.d.ts +0 -31
  176. package/dist/types/skills/tools/web-answer-tool.d.ts +0 -27
  177. package/dist/types/skills/tools/web-fetch-tool.d.ts +0 -80
  178. package/dist/types/skills/tools/web-research-tool.d.ts +0 -48
  179. package/dist/types/skills/tools/web-search-tool.d.ts +0 -46
  180. package/dist/types/skills/web-search/brave-source.d.ts +0 -3
  181. package/dist/types/skills/web-search/crawl4ai-extractor.d.ts +0 -16
  182. package/dist/types/skills/web-search/embedding-rerank.d.ts +0 -13
  183. package/dist/types/skills/web-search/exa-source.d.ts +0 -3
  184. package/dist/types/skills/web-search/multi-source-backend.d.ts +0 -23
  185. package/dist/types/skills/web-search/redis-source-cache.d.ts +0 -6
  186. package/dist/types/skills/web-search/search-svc-source.d.ts +0 -5
  187. package/dist/types/skills/web-search/searxng-source.d.ts +0 -4
  188. package/dist/types/skills/web-search/serper-source.d.ts +0 -3
  189. package/dist/types/skills/web-search/source-factory.d.ts +0 -6
  190. package/dist/types/skills/web-search/stability.d.ts +0 -26
  191. package/dist/types/skills/web-search/web-tunable-resolver.d.ts +0 -16
@@ -220,12 +220,6 @@ export interface TunableDefaults {
220
220
  dreamCooldownMs: number;
221
221
  dreamMaxDurationMs: number;
222
222
  teamBudgetTokens: number;
223
- braveApiKey: string;
224
- exaApiKey: string;
225
- serperApiKey: string;
226
- searxngBaseUrl: string;
227
- crawl4aiBaseUrl: string;
228
- searchSvcBaseUrl: string;
229
223
  }
230
224
  /**
231
225
  * Snapshot of all current tunable values.
@@ -0,0 +1,7 @@
1
+ export interface BoundedOutputTail {
2
+ output: string;
3
+ totalBytes: number;
4
+ truncated: boolean;
5
+ }
6
+ /** Read at most maxBytes from the end of an append-only UTF-8 output file. */
7
+ export declare function readBoundedOutputTail(path: string, maxBytes: number): Promise<BoundedOutputTail>;
@@ -205,8 +205,8 @@ export interface DreamRunDeps {
205
205
  search(query: string, userId: string, options?: {
206
206
  limit?: number;
207
207
  }): Promise<Array<{
208
- blockId: string;
209
- text: string;
208
+ id: string;
209
+ content: string;
210
210
  score: number;
211
211
  }>>;
212
212
  proposeExtracted?(items: Array<{
@@ -219,11 +219,14 @@ export interface DreamRunDeps {
219
219
  }): Promise<{
220
220
  proposalsAdded?: number;
221
221
  }>;
222
- feedback?(memoryIds: string[], signal: "useful" | "irrelevant" | "outdated" | "wrong"): Promise<{
222
+ feedback?(memoryIds: string[], signal: "useful" | "irrelevant" | "outdated" | "wrong", context?: {
223
+ sessionId?: string;
224
+ silent?: boolean;
225
+ }): Promise<{
223
226
  affected: number;
224
227
  }>;
225
228
  /** Recent activity summary for temporal context injection into Dream.
226
- * B3(X7)异步化:16 面 host-state proxy 方法(host 模式经 x/host.request 跨进程)。 */
229
+ * B3(X7)异步化:Host 契约方法(host 模式经 x/host.request 跨进程)。 */
227
230
  getActivitySummary?(userId: string, days?: number): Promise<{
228
231
  dailyCounts: Array<{
229
232
  date: string;
@@ -238,7 +241,7 @@ export interface DreamRunDeps {
238
241
  }>;
239
242
  /** Synthesize a timeline narrative for a time range. B3(X7)异步化。 */
240
243
  synthesizeTimeline?(userId: string, startMs: number, endMs: number): Promise<string>;
241
- /** Get/set user profile entries. B3(X7):setProfile/getAllProfiles 异步化(16 面);getProfile 面外保持同步。 */
244
+ /** Get/set user profile entries. B3(X7):setProfile/getAllProfiles 异步化;getProfile 为本地同步读取。 */
242
245
  getProfile?(userId: string, key: string): string | null;
243
246
  setProfile?(userId: string, key: string, value: string): Promise<void>;
244
247
  getAllProfiles?(userId: string): Promise<Record<string, string>>;
@@ -1,8 +1,8 @@
1
- import type { QMemoryCategory } from "../memory/categories.js";
1
+ import type { LongTermMemoryCategory } from "../../host-contract/memory-category.js";
2
2
  /** A memory file with its inferred category and metadata. */
3
3
  export interface CategorizedMemoryFile {
4
4
  filename: string;
5
- category: QMemoryCategory | "uncategorized";
5
+ category: LongTermMemoryCategory | "uncategorized";
6
6
  /** First line description. */
7
7
  description: string;
8
8
  /** File size bytes. */
@@ -36,7 +36,7 @@ export declare function scanCategoryManifest(memoryRoot: string): Promise<Catego
36
36
  * - patterns: merge (consolidate duplicates into one authoritative entry)
37
37
  * - decisions: update (add "update" section if decision changed)
38
38
  */
39
- export declare const CATEGORY_MERGE_RULES: Record<QMemoryCategory | "uncategorized", string>;
39
+ export declare const CATEGORY_MERGE_RULES: Record<LongTermMemoryCategory | "uncategorized", string>;
40
40
  /**
41
41
  * Format the category manifest and merge rules as an injection block
42
42
  * for the dream consolidation prompt.
@@ -0,0 +1,21 @@
1
+ export declare const STRUCTURED_TOOL_PROGRESS_PREFIX = "::xiaozhiclaw-progress-v1::";
2
+ export interface StructuredToolProgressEvent {
3
+ source: string;
4
+ stage: string;
5
+ elapsedMs: number;
6
+ heartbeat: boolean;
7
+ completed?: number;
8
+ total?: number;
9
+ unit?: string;
10
+ }
11
+ export interface ExtractedStructuredToolProgress {
12
+ cleanOutput: string;
13
+ events: StructuredToolProgressEvent[];
14
+ }
15
+ /**
16
+ * Extracts the shared, producer-neutral progress envelope from process output.
17
+ * Unknown sources and stages are accepted when they use bounded public tokens;
18
+ * malformed or extended records remain visible as ordinary command output.
19
+ */
20
+ export declare function extractStructuredToolProgress(output: string): ExtractedStructuredToolProgress;
21
+ export declare function formatStructuredToolProgress(event: StructuredToolProgressEvent): string;
@@ -1,6 +1,6 @@
1
1
  import type { HookRegistry } from "../../contracts/hooks.js";
2
2
  import type { MemoryProvider } from "../ports/memory-provider.js";
3
- import type { MemoryWriter } from "../ports/memory-writer.js";
3
+ import type { MemoryExtractionQueue, MemoryWriter } from "../ports/memory-writer.js";
4
4
  import type { MemoryRecallSource } from "../ports/memory-recall-source.js";
5
5
  export declare const MEMORY_PREFETCH_CONFIG: {
6
6
  readonly MAX_SESSION_BYTES: number;
@@ -12,12 +12,13 @@ export declare const MEMORY_PREFETCH_CONFIG: {
12
12
  * skills/memory/task-distillation PROCEDURE_TAG (runtime cannot import skills
13
13
  * per the architecture boundary; task-distillation.test.ts asserts equality).
14
14
  */
15
- export declare const PROCEDURE_TAG_FALLBACK = "distilled@v1";
16
15
  export interface MemoryHooksDeps {
17
16
  /** QMemory adapter (MemoryProvider with search/health). */
18
17
  memoryProvider?: MemoryProvider;
19
18
  /** Local memory provider for direct writes (auto-extract). */
20
19
  localMemoryProvider?: MemoryWriter;
20
+ /** Host-owned durable hand-off for completed turns awaiting semantic extraction. */
21
+ memoryExtractionQueue?: MemoryExtractionQueue;
21
22
  /** User ID for memory lookups. */
22
23
  userId: string;
23
24
  /** Logger. */
@@ -30,22 +31,33 @@ export interface MemoryHooksDeps {
30
31
  /** Access to last assistant response (for implicit extraction). Set externally per turn. */
31
32
  getLastAssistantMessage?: () => string | undefined;
32
33
  /** Background LLM for implicit extraction. If provided, enables Layer 2. */
33
- llmExtract?: (prompt: string) => Promise<string | null>;
34
+ llmExtract?: (prompt: string, signal?: AbortSignal) => Promise<string | null>;
35
+ /** Shared process lane: queues learning work and aborts it for foreground turns. */
36
+ scheduleBackgroundMemoryWork(label: string, work: (signal: AbortSignal) => Promise<void>): Promise<boolean>;
34
37
  /** Part B write visibility: forwards turn.memory_written on auto-extract commits. */
35
38
  sendNotification?: (method: string, params: Record<string, unknown>) => void;
36
39
  /** V3.2 usage 仪表:procedure 复用的 session 维度与负反馈计数落盘目录。 */
37
- /** V3.2: tag marking a distilled procedure memory (recall filter). cli injects so runtime never imports skills. */
38
- procedureTag?: string;
40
+ /** Tag marking a distilled procedure memory. CLI injects it to preserve the runtime/skills boundary. */
41
+ procedureTag: string;
39
42
  /** V3.2: record distilled procedures whose text reached the prompt (positive reuse signal). cli-injected.
40
43
  * [A7] Called from memory.after_recall with the post-cap injected subset — NOT from
41
44
  * before_recall: search hits the model never saw must not build promotion evidence. */
42
- recordProcedureRecalls?: (procedureIds: string[], sessionId: string) => void;
45
+ recordProcedureRecalls(procedureIds: string[], sessionId: string): void;
43
46
  /** V3.2: record negative feedback against distilled procedures (failed turn). cli-injected. */
44
- recordProcedureNegatives?: (procedureIds: string[]) => void;
47
+ recordProcedureNegatives(procedureIds: string[]): void;
45
48
  /** [A7] Host-side access ledger (memories.access_count/last_accessed_at): report the L2 ids
46
49
  * actually injected this turn. cli-injected (fire-and-forget; Host stays the single writer —
47
50
  * search() no longer counts hits, so skipping this call means the memory goes uncounted). */
48
- recordInjectedAccess?: (memoryIds: string[]) => void;
51
+ recordInjectedAccess(memoryIds: string[], context: {
52
+ sessionId: string;
53
+ turnId: string;
54
+ }): void;
55
+ /** Host-owned recall outcome ledger. Best-effort accounting must never block a turn. */
56
+ recordRecallOutcome(input: {
57
+ sessionId: string;
58
+ turnId: string;
59
+ event: "used" | "helped" | "harmed" | "unattributed";
60
+ }): void;
49
61
  }
50
62
  /** Session-scoped state for dedup and byte budget (CC parity). */
51
63
  export interface MemoryPrefetchState {
@@ -42,6 +42,8 @@ export interface StartAgentTaskOptions {
42
42
  * background work that session spawned). Keyed by session — turn ids don't
43
43
  * cross the client/agent boundary, session ids do. */
44
44
  launchSessionId?: string;
45
+ /** Parent/root turn that owns this background task. */
46
+ launchTurnId?: string;
45
47
  /**
46
48
  * Idle (no-progress) reclaim window (ms): the task is force-failed as `timeout`
47
49
  * only after `idleMs` with no progress signal — an actively-producing task is
@@ -58,6 +60,8 @@ export interface RegisterBashTaskOptions {
58
60
  label?: string;
59
61
  pid?: number;
60
62
  handle: BashTaskHandle;
63
+ launchSessionId?: string;
64
+ launchTurnId?: string;
61
65
  }
62
66
  export interface TaskOutputSnapshot {
63
67
  task: TaskState;
@@ -168,7 +172,7 @@ export declare class BackgroundTaskManager {
168
172
  * superseded-turn abort or a natural turn-end, which leave background tasks
169
173
  * running. Returns the ids actually cancelled.
170
174
  */
171
- cancelTasksForSession(sessionId: string): string[];
175
+ cancelTasksForSession(sessionId: string, parentTurnId?: string): string[];
172
176
  /** Cancel every non-terminal task (session teardown). Returns cancelled ids. */
173
177
  cancelAllRunning(): string[];
174
178
  }
@@ -0,0 +1,32 @@
1
+ import type { TurnSubagentActivityNotification, TurnSubagentCompletedNotification, TurnSubagentDecisionNotification, TurnSubagentStartedNotification } from "../../protocol/wire/notification-payloads.js";
2
+ export interface PersistedSubagentSnapshot {
3
+ subagentId: string;
4
+ agentType: string;
5
+ displayName?: string;
6
+ delegatedTask?: string;
7
+ mode?: "foreground" | "background";
8
+ status: "running" | "completed" | "failed" | "cancelled";
9
+ activities?: Array<{
10
+ activityId?: string;
11
+ toolName?: string;
12
+ conciseStatus: string;
13
+ status: "running" | "completed" | "failed" | "blocked";
14
+ observedAt: string;
15
+ }>;
16
+ toolsUsed?: string[];
17
+ resultSummary?: string;
18
+ evidence?: TurnSubagentCompletedNotification["evidence"];
19
+ durationMs?: number;
20
+ tokenUsage?: TurnSubagentCompletedNotification["tokenUsage"];
21
+ decision?: "pending" | "adopted" | "partially_adopted" | "rejected";
22
+ decisionSummary?: string;
23
+ error?: string;
24
+ startedAt?: string;
25
+ finishedAt?: string;
26
+ }
27
+ export declare function recordSubagentStarted(payload: TurnSubagentStartedNotification): PersistedSubagentSnapshot;
28
+ export declare function recordSubagentActivity(payload: TurnSubagentActivityNotification): PersistedSubagentSnapshot;
29
+ export declare function recordSubagentCompleted(payload: TurnSubagentCompletedNotification): PersistedSubagentSnapshot;
30
+ export declare function recordSubagentDecision(payload: TurnSubagentDecisionNotification): PersistedSubagentSnapshot | undefined;
31
+ export declare function subagentActivityBlock(snapshot: PersistedSubagentSnapshot): Record<string, unknown>;
32
+ export declare function clearSubagentTurnSnapshotsForTests(): void;
@@ -1,3 +1,4 @@
1
+ import type { HostDelegationTelemetrySummary } from "../../host-contract/index.js";
1
2
  export type AgentMode = "chat" | "solo" | "product" | "workflow" | "unknown";
2
3
  export interface TurnTelemetryUsage {
3
4
  inputTokens?: number;
@@ -27,6 +28,7 @@ export interface TurnTelemetrySnapshot {
27
28
  usage?: TurnTelemetryUsage;
28
29
  toolSummary?: TurnTelemetryToolSummary;
29
30
  fileContext?: TurnTelemetryFileContext;
31
+ delegationSummary?: HostDelegationTelemetrySummary;
30
32
  createdAt: string;
31
33
  finalizedAt?: string;
32
34
  }
@@ -38,6 +40,26 @@ export type TurnTelemetryPatch = Partial<Omit<TurnTelemetrySnapshot, "turnId" |
38
40
  * same turnId record. Safe to call before the projectId is known; the first call seeds it.
39
41
  */
40
42
  export declare function recordTurnTelemetry(turnId: string, patch: TurnTelemetryPatch): void;
43
+ export declare function recordTurnDelegationStarted(input: {
44
+ turnId: string;
45
+ subagentId: string;
46
+ agentType: string;
47
+ mode: "foreground" | "background";
48
+ }): void;
49
+ export declare function recordTurnDelegationCompleted(input: {
50
+ turnId: string;
51
+ subagentId: string;
52
+ agentType: string;
53
+ status: "completed" | "failed" | "cancelled";
54
+ durationMs: number;
55
+ totalTokens: number;
56
+ }): void;
57
+ export declare function recordTurnDelegationDecision(input: {
58
+ turnId: string;
59
+ subagentId: string;
60
+ decision: "adopted" | "partially_adopted" | "rejected";
61
+ }): void;
62
+ export declare function recordTurnArgumentCorrections(turnId: string, argumentCorrections: HostDelegationTelemetrySummary["argumentCorrections"]): void;
41
63
  /**
42
64
  * Flush the accumulated telemetry for a turn to disk and drop it from memory. Returns null
43
65
  * (after dropping the in-memory record) when no projectId resolved — telemetry, like the turn
@@ -6,19 +6,30 @@
6
6
  * Layer 2: Lightweight LLM pass (async, non-blocking) — extracts structured facts
7
7
  *
8
8
  * This runs AFTER turn.end, so the user already has their response.
9
- * Layer 2 fires only for turns with detectable signals. The often-quoted
10
- * "~10-20% of turns" was calibrated on general chat; this product's corpus is
11
- * engineering conversation, and the signal table is deliberately narrowed for
12
- * it (see the [B14] calibration note above SIGNAL_PATTERNS).
9
+ * With a semantic extractor configured, every completed user turn is eligible
10
+ * for the bounded Layer 2 pass. The regex detector is only a conservative
11
+ * no-model fallback and must remain independent of evaluation vocabulary.
13
12
  *
14
13
  * Reference: claude-code src/services/extractMemories/extractMemories.ts
15
14
  */
16
15
  import type { MemoryWriter } from "../ports/memory-writer.js";
16
+ import { type LongTermMemoryCategory, type MemoryClaimRelation } from "../../host-contract/memory-category.js";
17
17
  export interface ExtractedFact {
18
18
  text: string;
19
- category: "personal_fact" | "preference" | "event" | "lesson" | "pattern" | "decision";
19
+ /** Exact evidence span copied from the current user message; never assistant prose. */
20
+ evidenceQuote: string;
21
+ /** True only when this atomic fact replaces or cancels prior state. */
22
+ correction?: boolean;
23
+ category: LongTermMemoryCategory;
20
24
  importance: number;
25
+ confidence: number;
21
26
  eventDate?: string;
27
+ evidenceKind: "direct_user_statement" | "inference";
28
+ scope: "global" | "project" | "session";
29
+ entity: string;
30
+ predicate: string;
31
+ relation: MemoryClaimRelation;
32
+ value: Record<string, unknown>;
22
33
  }
23
34
  export interface ImplicitExtractionDeps {
24
35
  /** Local memory provider for writing extracted facts. */
@@ -34,9 +45,14 @@ export interface ImplicitExtractionDeps {
34
45
  * LLM invocation function (uses background/cheap model).
35
46
  * Returns structured JSON string or null if unavailable.
36
47
  */
37
- llmExtract?: (prompt: string) => Promise<string | null>;
48
+ llmExtract?: (prompt: string, signal?: AbortSignal) => Promise<string | null>;
49
+ signal?: AbortSignal;
50
+ sessionId?: string;
51
+ /** Durable Host extraction-job id used to make preemption/restart replay idempotent. */
52
+ idempotencyNamespace?: string;
38
53
  }
39
54
  export interface TurnPair {
55
+ priorUserMessage?: string;
40
56
  userMessage: string;
41
57
  assistantMessage: string;
42
58
  }
@@ -51,7 +67,7 @@ export declare function hasExtractionSignal(userMessage: string): boolean;
51
67
  * Layer 2: Use a lightweight LLM to extract structured facts from a turn pair.
52
68
  * Only called when Layer 1 signals are detected.
53
69
  */
54
- export declare function extractFactsViaLlm(turn: TurnPair, llmExtract: (prompt: string) => Promise<string | null>, today: string): Promise<ExtractedFact[]>;
70
+ export declare function extractFactsViaLlm(turn: TurnPair, llmExtract: (prompt: string) => Promise<string | null>, today: string, onDiagnostic?: (message: string) => void): Promise<ExtractedFact[]>;
55
71
  /**
56
72
  * Run the full implicit extraction pipeline for a completed turn.
57
73
  * This is async and non-blocking — call it after turn.end.
@@ -1,10 +1,6 @@
1
1
  import type { HookRegistry } from "../../contracts/hooks.js";
2
2
  import type { OpenAiToolCall } from "./tool-call-contracts.js";
3
3
  import type { AgentLogger, ToolDefinition, ToolInvoker } from "./agent-execution-contracts.js";
4
- export interface RecallCategoryHint {
5
- preferred: string[];
6
- deprioritized: string[];
7
- }
8
4
  export interface RuntimeCompressibleMessage {
9
5
  role: string;
10
6
  content?: string | unknown;
@@ -76,6 +72,5 @@ export interface ToolLoopRuntimePorts {
76
72
  }
77
73
  export interface AgentRuntimePorts {
78
74
  toolLoop: ToolLoopRuntimePorts;
79
- detectRecallCategories(query: string): RecallCategoryHint;
80
75
  resolveModelForPurpose(purpose: "smallModel" | string): string | null;
81
76
  }
@@ -1,4 +1,4 @@
1
- export type { MemoryWriter, MemoryWriteMetadata } from "./memory-writer.js";
1
+ export type { MemoryExtractionQueue, MemoryWriter, MemoryWriteMetadata, } from "./memory-writer.js";
2
2
  export type { MemoryRecallSource } from "./memory-recall-source.js";
3
3
  export type { MemoryProvider, MemorySearchOptions, MemorySearchResult, MemoryIngestOptions, MemoryExtractionItem, MemoryConsolidationWriteResult, MemoryLearningSink, MemoryToolLocalProvider, MemoryActivitySummary, MemoryAtlasRecord, MemoryAtlasResult, MemoryUpdatePatch, MemoryHandlerProvider, MemoryDreamProvider, MemoryDecayProvider, MemoryRuntimeHookProvider, MemoryToolProvider, MemoryDreamRuntimeProvider, } from "./memory-provider.js";
4
4
  export type { PathService } from "./path-service.js";
@@ -11,9 +11,8 @@ export type { RuntimeToolContract, ToolBootstrap, ToolBootstrapConfig, ToolBoots
11
11
  export { defineToolRegistrationModule } from "./tool-contracts.js";
12
12
  export type { Checkpoint, CheckpointBackend, CheckpointResult } from "./checkpoint-contracts.js";
13
13
  export type { WorktreeBackend, WorktreeInfo, WorktreeResult } from "./worktree-contracts.js";
14
- export type { WebSearchBackend, WebSearchOutput, WebSearchResult } from "./web-search-contracts.js";
15
14
  export { buildToolResultMessage, type OpenAiToolCall, type ToolResultMessageInput } from "./tool-call-contracts.js";
16
15
  export type { AgentLogger, AgentRunRequest, ChatMessage, ForkedAgentRunner, ForkedAgentRunnerFactory, HookRegistry, TokenUsage, ToolDefinition, ToolInvoker, TurnEvent, } from "./agent-execution-contracts.js";
17
16
  export type { AccumulatedToolCall, LLMChunk, LLMRequest, LLMTransport, } from "./model-transport-contracts.js";
18
17
  export { accumulateToolCalls } from "./model-transport-contracts.js";
19
- export type { AgentRuntimePorts, ContextCompressionEnginePort, RecallCategoryHint, RuntimeCompressibleMessage, RuntimeCompressionResult, StreamingToolExecutorPort, StreamingToolExecutorPortConfig, ToolExecutionResultPort, ToolLoopRuntimePorts, } from "./agent-runtime-ports.js";
18
+ export type { AgentRuntimePorts, ContextCompressionEnginePort, RuntimeCompressibleMessage, RuntimeCompressionResult, StreamingToolExecutorPort, StreamingToolExecutorPortConfig, ToolExecutionResultPort, ToolLoopRuntimePorts, } from "./agent-runtime-ports.js";
@@ -1,22 +1,19 @@
1
1
  import type { MemoryWriter } from "./memory-writer.js";
2
+ import type { MemoryClaimRelation } from "../../host-contract/memory-category.js";
2
3
  export interface MemorySearchOptions {
3
4
  limit?: number;
4
- minScore?: number;
5
- preferredCategories?: string[];
6
5
  llmConfig?: unknown;
6
+ /** Foreground-turn identity. When present, Host records every considered
7
+ * candidate and applies the task-relevance injection decision. */
8
+ recallContext?: {
9
+ sessionId: string;
10
+ turnId: string;
11
+ };
7
12
  /** Drop hits whose tags contain ALL strings of any group (resident-layer exclusion). */
8
13
  excludeTagGroups?: string[][];
9
14
  }
10
15
  export interface MemorySearchResult {
11
- blockId: string;
12
- /** [A8] REQUIRED — host guarantees id === blockId on every search hit. When id/content
13
- * were optional, the {blockId,text} ↔ {id,path,content} drift silently type-checked:
14
- * the LRU dedup key was always "" (dedup dead) and byte accounting always 0. */
15
16
  id: string;
16
- /** Historical field: L2 hits never carry a path (kept optional for legacy readers). */
17
- path?: string;
18
- text: string;
19
- /** [A8] REQUIRED — host guarantees content === text on every search hit (see id). */
20
17
  content: string;
21
18
  score: number;
22
19
  source?: string;
@@ -26,6 +23,8 @@ export interface MemoryIngestOptions {
26
23
  llmConfig?: unknown;
27
24
  source?: string;
28
25
  sessionId?: string;
26
+ /** Transport-only cancellation; adapters must not serialize this into Host business params. */
27
+ requestSignal?: AbortSignal;
29
28
  }
30
29
  export interface MemoryExtractionItem {
31
30
  text: string;
@@ -35,6 +34,14 @@ export interface MemoryExtractionItem {
35
34
  source?: string;
36
35
  event_date?: string;
37
36
  tags?: string[];
37
+ /** Atomic claim identity produced by the evidence extractor and validated again by Host. */
38
+ claim?: {
39
+ scope: "global" | "project" | "session";
40
+ entity: string;
41
+ predicate: string;
42
+ relation: MemoryClaimRelation;
43
+ value: Record<string, unknown>;
44
+ };
38
45
  /** Ids of already-uploaded attachments to link to this memory on commit. */
39
46
  attachmentIds?: string[];
40
47
  /** Evidence trail for distillation-style sources (feedback — §10): audit refs + crash-safe dedup key. */
@@ -43,6 +50,8 @@ export interface MemoryExtractionItem {
43
50
  refs: string[];
44
51
  idempotencyKey: string;
45
52
  };
53
+ /** The source turn explicitly retracts or replaces an earlier decision. */
54
+ correction?: boolean;
46
55
  }
47
56
  export interface MemoryConsolidationWriteResult {
48
57
  memoriesAdded?: number;
@@ -77,9 +86,12 @@ export interface MemoryToolLocalProvider {
77
86
  category?: string;
78
87
  importance?: number;
79
88
  }): Promise<unknown>;
80
- /** B3(X7)异步化:16 面 host-state proxy 方法(host 模式经 x/host.request 跨进程)。 */
89
+ /** B3(X7)异步化:Host 契约方法(host 模式经 x/host.request 跨进程)。 */
81
90
  synthesizeTimeline(userId: string, startMs: number, endMs: number): Promise<string>;
82
- feedback(ids: string[], signal: "useful" | "irrelevant" | "outdated" | "wrong"): Promise<{
91
+ feedback(ids: string[], signal: "useful" | "irrelevant" | "outdated" | "wrong", context?: {
92
+ sessionId?: string;
93
+ silent?: boolean;
94
+ }): Promise<{
83
95
  affected: number;
84
96
  }>;
85
97
  }
@@ -138,9 +150,9 @@ export interface MemoryUpdatePatch {
138
150
  tags?: string[];
139
151
  }
140
152
  export interface MemoryHandlerProvider extends MemoryProvider {
141
- /** B3(X7)异步化:16 面 host-state proxy 方法(host 模式经 x/host.request 跨进程)。 */
153
+ /** B3(X7)异步化:Host 契约方法(host 模式经 x/host.request 跨进程)。 */
142
154
  getAtlas?(userId: string, options?: Record<string, unknown>): Promise<MemoryAtlasResult>;
143
- /** B3(X7)异步化:16 host-state proxy 方法。 */
155
+ /** B3(X7)异步化:Host 契约方法。 */
144
156
  getActivitySummary?(userId: string, days: number): Promise<MemoryActivitySummary>;
145
157
  observeExtracted?(items: MemoryExtractionItem[], userId: string, options?: MemoryIngestOptions): Promise<MemoryConsolidationWriteResult>;
146
158
  proposeExtracted?(items: MemoryExtractionItem[], userId: string, options?: MemoryIngestOptions): Promise<MemoryConsolidationWriteResult>;
@@ -201,10 +213,13 @@ export interface MemoryDreamProvider {
201
213
  }): Promise<{
202
214
  proposalsAdded?: number;
203
215
  }>;
204
- feedback?(memoryIds: string[], signal: "useful" | "irrelevant" | "outdated" | "wrong"): Promise<{
216
+ feedback?(memoryIds: string[], signal: "useful" | "irrelevant" | "outdated" | "wrong", context?: {
217
+ sessionId?: string;
218
+ silent?: boolean;
219
+ }): Promise<{
205
220
  affected: number;
206
221
  }>;
207
- /** B3(X7)异步化:以下四个 16 host-state proxy 方法签名统一 Promise(host 模式经 x/host.request 跨进程)。 */
222
+ /** B3(X7)异步化:以下 Host 契约方法签名统一 Promise(host 模式经 x/host.request 跨进程)。 */
208
223
  getActivitySummary?(userId: string, days?: number): Promise<MemoryActivitySummary>;
209
224
  synthesizeTimeline?(userId: string, startMs: number, endMs: number): Promise<string>;
210
225
  getProfile?(userId: string, key: string): string | null;
@@ -1,21 +1,70 @@
1
+ import type { HostMemoryExtractionTurnInput, HostPendingMemoryExtractionTurn, MemoryClaimRelation } from "../../host-contract/index.js";
1
2
  export interface MemoryWriteMetadata {
2
3
  source?: string;
3
4
  category?: string;
4
5
  importance?: number;
5
6
  eventDate?: string;
7
+ sessionId?: string;
6
8
  [key: string]: unknown;
7
9
  }
10
+ export interface StructuredMemoryClaim {
11
+ scope: "global" | "project" | "session";
12
+ entity: string;
13
+ predicate: string;
14
+ relation: MemoryClaimRelation;
15
+ value: Record<string, unknown>;
16
+ }
17
+ export interface MemoryWriterExtractionItem {
18
+ text: string;
19
+ category?: string;
20
+ importance?: number;
21
+ confidence?: number;
22
+ event_date?: string;
23
+ tags?: string[];
24
+ claim?: StructuredMemoryClaim;
25
+ evidence?: {
26
+ kind: string;
27
+ refs: string[];
28
+ idempotencyKey: string;
29
+ };
30
+ /** The source turn explicitly retracts or replaces an earlier decision. */
31
+ correction?: boolean;
32
+ }
8
33
  export interface MemoryWriter {
9
34
  addText(text: string, userId: string, metadata?: MemoryWriteMetadata): Promise<unknown>;
10
- proposeExtracted?(items: Array<{
11
- text: string;
12
- category?: string;
13
- importance?: number;
14
- }>, userId: string, metadata?: MemoryWriteMetadata): Promise<{
35
+ ingestExtracted?(items: MemoryWriterExtractionItem[], userId: string, metadata?: MemoryWriteMetadata): Promise<{
36
+ memoriesAdded?: number;
37
+ items?: Array<{
38
+ outcome: "created" | "merged" | "conflicted" | "pending" | "rejected";
39
+ }>;
40
+ }>;
41
+ proposeExtracted?(items: MemoryWriterExtractionItem[], userId: string, metadata?: MemoryWriteMetadata): Promise<{
15
42
  proposalsAdded?: number;
16
43
  }>;
17
44
  findRelatedEvents?(text: string, userId: string): Promise<Array<{
18
45
  id: string;
19
46
  }>>;
20
- feedback?(ids: string[], signal: "useful" | "irrelevant" | "outdated" | "wrong"): Promise<unknown>;
47
+ feedback?(ids: string[], signal: "useful" | "irrelevant" | "outdated" | "wrong", context?: {
48
+ sessionId?: string;
49
+ silent?: boolean;
50
+ }): Promise<unknown>;
51
+ }
52
+ export interface MemoryExtractionQueue {
53
+ enqueueExtractionTurn(input: HostMemoryExtractionTurnInput, userId: string): Promise<{
54
+ jobId: string;
55
+ created: boolean;
56
+ }>;
57
+ claimPendingExtractionTurns(userId: string, workerId: string, options?: {
58
+ limit?: number;
59
+ leaseMs?: number;
60
+ }): Promise<HostPendingMemoryExtractionTurn[]>;
61
+ completeExtractionTurn(jobId: string, userId: string, workerId: string): Promise<{
62
+ deleted: boolean;
63
+ }>;
64
+ releaseExtractionTurn(jobId: string, userId: string, workerId: string): Promise<{
65
+ released: boolean;
66
+ }>;
67
+ recordExtractionTurnFailure(jobId: string, userId: string, workerId: string, error: string): Promise<{
68
+ updated: boolean;
69
+ }>;
21
70
  }
@@ -116,6 +116,8 @@ export interface PermissionRuleEnginePort {
116
116
  setMode(mode: PermissionMode): void;
117
117
  getRules(): readonly PermissionRuleEntry[];
118
118
  replaceRules(rules: PermissionRuleEntry[]): void;
119
+ /** Session-approved writable roots used by the OS sandbox projection. */
120
+ getAllowedWriteDirectories?(): string[];
119
121
  }
120
122
  export interface PermissionApprovalResolver {
121
123
  resolveApproval(response: ApprovalResponse): void;
@@ -48,8 +48,9 @@ export interface ToolCatalog {
48
48
  }
49
49
  export interface ToolBootstrapProgress {
50
50
  output: string;
51
+ progressSnapshot: string;
51
52
  elapsedTimeSeconds: number;
52
- totalLines: number;
53
+ visibleLines: number;
53
54
  totalBytes: number;
54
55
  }
55
56
  export interface ToolBootstrapConfig {
@@ -65,6 +66,7 @@ export interface ToolBootstrapConfig {
65
66
  };
66
67
  getTaskScopeKey?(): string | undefined;
67
68
  getCurrentTurnId?(): string | undefined;
69
+ isGroupSecurityMode?(): boolean;
68
70
  sendNotification?(method: string, params: Record<string, unknown>): void;
69
71
  onExecProgress?(progress: ToolBootstrapProgress): void;
70
72
  /** Lazy host-owned tunable overrides; web registration must not call it until first invoke. */
@@ -13,5 +13,7 @@ export interface IdentityInfo {
13
13
  provider?: string;
14
14
  /** 本轮解析出的真实模型 id */
15
15
  model?: string;
16
+ /** Exact tool names enabled for this turn; capability claims are derived from these. */
17
+ availableToolNames?: readonly string[];
16
18
  }
17
19
  export declare function createIdentitySection(info: IdentityInfo): SystemPromptSection;
@@ -0,0 +1,6 @@
1
+ export interface SessionPermissionStorePathOptions {
2
+ /** Test-only path override; production always uses the centralized session directory. */
3
+ sessionDir?: string;
4
+ }
5
+ export declare function loadSessionAllowedDirectories(sessionId: string, projectRoot: string, options?: SessionPermissionStorePathOptions): string[];
6
+ export declare function appendSessionAllowedDirectory(sessionId: string, projectRoot: string, pathPrefix: string, options?: SessionPermissionStorePathOptions): Promise<void>;
@@ -64,6 +64,8 @@ export interface TaskStateBase {
64
64
  * the client and agent mint turn ids in separate namespaces (they never match),
65
65
  * but the session id is shared across the boundary. */
66
66
  launchSessionId?: string;
67
+ /** Root turn that launched this task. */
68
+ launchTurnId?: string;
67
69
  /** Execution depth (0 = root). */
68
70
  depth: number;
69
71
  /** Maximum turns before forced stop. */