@axiom-lattice/core 3.0.4 → 3.0.5

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.mjs CHANGED
@@ -9394,8 +9394,11 @@ Please select a valid tool from the list above.`
9394
9394
  * The only place to access request.tools (all available tools).
9395
9395
  * Identifies unknown tools and stores error info in metadata.
9396
9396
  *
9397
- * Key: Preserve all tool_calls in original AIMessage, including unknown ones.
9398
- * This allows the model to see what it selected in the next round.
9397
+ * Key: Strip valid tool_calls and only keep unknown ones in the returned
9398
+ * AIMessage. Valid calls must NOT be preserved: afterModel jumps to "model"
9399
+ * and skips ToolNode, so a preserved valid call would have no ToolMessage
9400
+ * and the next model call would be rejected with a 400 dangling tool_calls
9401
+ * error. Stripped valid calls are re-issued by the model in the next round.
9399
9402
  */
9400
9403
  wrapModelCall: async (request, handler) => {
9401
9404
  const availableTools = request.tools || [];
@@ -9425,10 +9428,11 @@ Please select a valid tool from the list above.`
9425
9428
  toolCallId: toolCall.id,
9426
9429
  errorMessage: errorMessageTemplate(toolCall.name, availableToolNames)
9427
9430
  }));
9431
+ const unknownToolIds = new Set(unknownToolCalls.map((toolCall) => toolCall.id));
9432
+ const remainingToolCalls = aiResponse.tool_calls.filter((toolCall) => unknownToolIds.has(toolCall.id));
9428
9433
  const modifiedResponse = new AIMessage({
9429
9434
  content: aiResponse.content,
9430
- tool_calls: aiResponse.tool_calls,
9431
- // Key: preserve all tool_calls, don't delete unknown
9435
+ tool_calls: remainingToolCalls,
9432
9436
  response_metadata: {
9433
9437
  ...aiResponse.response_metadata,
9434
9438
  _unknownToolErrors: unknownToolErrors
@@ -9481,6 +9485,68 @@ Please select a valid tool from the list above.`
9481
9485
  });
9482
9486
  }
9483
9487
 
9488
+ // src/deep_agent_new/middleware/patch_tool_calls.ts
9489
+ import {
9490
+ createMiddleware as createMiddleware9,
9491
+ ToolMessage as ToolMessage3,
9492
+ AIMessage as AIMessage2
9493
+ } from "langchain";
9494
+ function createPatchToolCallsMiddleware() {
9495
+ return createMiddleware9({
9496
+ name: "patchToolCallsMiddleware",
9497
+ beforeAgent: async (state) => {
9498
+ const messages = state.messages;
9499
+ if (!messages || messages.length === 0) {
9500
+ return;
9501
+ }
9502
+ const replacements = [];
9503
+ for (let i = 0; i < messages.length; i++) {
9504
+ const msg = messages[i];
9505
+ if (AIMessage2.isInstance(msg) && msg.tool_calls != null) {
9506
+ const respondedIds = /* @__PURE__ */ new Set();
9507
+ for (const toolCall of msg.tool_calls) {
9508
+ if (!toolCall.id) continue;
9509
+ const correspondingToolMsg = messages.slice(i).find(
9510
+ (m) => ToolMessage3.isInstance(m) && m.tool_call_id === toolCall.id
9511
+ );
9512
+ if (correspondingToolMsg) {
9513
+ respondedIds.add(toolCall.id);
9514
+ }
9515
+ }
9516
+ const remainingToolCalls = msg.tool_calls.filter(
9517
+ (toolCall) => toolCall.id && respondedIds.has(toolCall.id)
9518
+ );
9519
+ if (remainingToolCalls.length === msg.tool_calls.length) {
9520
+ continue;
9521
+ }
9522
+ const additionalKwargs = { ...msg.additional_kwargs };
9523
+ delete additionalKwargs.tool_calls;
9524
+ if (!msg.id) continue;
9525
+ replacements.push(
9526
+ new AIMessage2({
9527
+ id: msg.id,
9528
+ content: msg.content,
9529
+ name: msg.name,
9530
+ tool_calls: remainingToolCalls,
9531
+ additional_kwargs: additionalKwargs,
9532
+ response_metadata: msg.response_metadata
9533
+ })
9534
+ );
9535
+ }
9536
+ }
9537
+ if (replacements.length === 0) {
9538
+ return;
9539
+ }
9540
+ return {
9541
+ messages: replacements
9542
+ };
9543
+ }
9544
+ });
9545
+ }
9546
+
9547
+ // src/agent_lattice/builders/commonMiddleware.ts
9548
+ import { summarizationMiddleware } from "langchain";
9549
+
9484
9550
  // src/plugin/metaSerializer.ts
9485
9551
  function tryExtractTools(plugin) {
9486
9552
  if (!plugin.middleware) return [];
@@ -10717,6 +10783,12 @@ actually achieve, not just what to build:
10717
10783
 
10718
10784
  Record the goal model in the parent task's description ([[task-tracking]]).
10719
10785
 
10786
+ **HARD RULE \u2014 never guess the target.** If the goal, expected output,
10787
+ consumer, or usable state is unclear at ANY point before writing test
10788
+ cases, you MUST ask the user via ask_user_to_clarify \u2014 never proceed
10789
+ with an assumed expectation. A test case written against a guessed
10790
+ expectation validates the wrong thing. When in doubt, ask.
10791
+
10720
10792
  ## Goal-Driven Validation (apply to EVERY sub-skill workflow)
10721
10793
 
10722
10794
  The agent evaluates goal achievement ITSELF via multi-dimensional test
@@ -10802,16 +10874,29 @@ Do NOT use reviewer as:
10802
10874
  verification. If findings show config errors, fix and re-check.`,
10803
10875
  "task-tracking": `---
10804
10876
  name: task-tracking
10805
- description: Manage persistent tasks for agent creation workflows. Use
10806
- manage_task to create parent/subtasks, track progress, and resume
10807
- interrupted runs. Applies to ALL agent building processes.
10877
+ description: Manage persistent tasks with manage_task. Universal rule:
10878
+ once the goal is clear and you know what to do, create the task FIRST
10879
+ then execute. Track parent/subtasks, update status to reflect reality,
10880
+ resume interrupted work. Applies to ANY multi-step agent work \u2014 not
10881
+ just agent building.
10808
10882
  metadata:
10809
10883
  domain: agent-building
10810
10884
  verified: unverified
10811
10885
  ---
10812
10886
  # Task Tracking \u2014 manage_task for Agent Workflows
10813
10887
 
10814
- Every agent creation workflow is a multi-step process \u2014 track it.
10888
+ **Task management is the ongoing record of a goal and its acceptance
10889
+ criteria** \u2014 it answers at any moment: what are we achieving, and what
10890
+ does "done" look like. Create a task when the goal is clear; keep its
10891
+ Objective and Acceptance Criteria current as work proceeds; change
10892
+ status only when the criteria are actually met.
10893
+
10894
+ **Universal principle**: whenever the goal is understood and the work
10895
+ is about to start, create a task BEFORE executing. If you can write an
10896
+ Objective and Acceptance Criteria, it deserves a task. This is not
10897
+ optional and not limited to agent-building \u2014 it applies to any
10898
+ multi-step work (learning, building, modifying skills, fixing, anything
10899
+ with a clear goal).
10815
10900
 
10816
10901
  ## When to create (and when NOT)
10817
10902
 
@@ -10828,13 +10913,29 @@ Do NOT create tasks for:
10828
10913
 
10829
10914
  ## Setup
10830
10915
 
10916
+ **A task is a living record of the GOAL + ACCEPTANCE CRITERIA** \u2014 not a
10917
+ todo label. Every task's description must carry:
10918
+
10919
+ - **Objective** \u2014 one measurable sentence: what result to achieve
10920
+ - **Acceptance Criteria** \u2014 checkboxes that define "done": when ALL
10921
+ are checked, the task is verifiably complete
10922
+
10923
+ The task is created when the goal is confirmed, and its description is
10924
+ CONTINUALLY UPDATED as the work progresses (spec evolves, criteria are
10925
+ met, new criteria emerge). Status changes only when the criteria are
10926
+ truly met \u2014 never as a workaround.
10927
+
10831
10928
  - **Create the parent task when the scope is confirmed** \u2014 before
10832
10929
  starting the first real work phase (probe/design/build):
10833
- \`manage_task create(title: <goal>, description: <summary>, ownerType: "agent")\`
10930
+ \`manage_task create(title: <goal>, description: <Objective + Acceptance Criteria>, ownerType: "agent")\`
10834
10931
  Record the returned parent task id.
10835
10932
  - **Create a subtask per phase** as you start each phase (probe /
10836
10933
  design / build / eval / retro):
10837
- \`manage_task create(title: <phase>, parentId: <parent>, ownerType: "agent")\`
10934
+ \`manage_task create(title: <phase>, description: <Objective + Acceptance Criteria>, parentId: <parent>, ownerType: "agent")\`
10935
+ - **Update the description as work proceeds**: append progress, mark
10936
+ criteria \`[x]\`, revise criteria when the goal model/spec changes.
10937
+ The task tracks the target and its acceptance \u2014 read it to know what
10938
+ "done" means, keep it current so it always reflects reality.
10838
10939
 
10839
10940
  ## Status discipline \u2014 MANDATORY
10840
10941
 
@@ -11142,6 +11243,9 @@ description: Run agent evaluations, interpret results, fix failures, and
11142
11243
  metadata:
11143
11244
  domain: agent-building
11144
11245
  verified: unverified
11246
+ subSkills:
11247
+ - eval-design-tests
11248
+ - eval-run-and-govern
11145
11249
  ---
11146
11250
  # Eval Verify \u2014 Run Evaluations and Upgrade Trust
11147
11251
 
@@ -11158,6 +11262,13 @@ verified: unverified
11158
11262
  parent (eval-{parent-id}) \u2014 see Layered verification below.
11159
11263
  2. \`manage_eval create_case(suiteId, ...)\` per sample or scenario
11160
11264
  Required: inputMessage, steps=[{agent_id}], outputType, contentAssertion
11265
+ **contentAssertion MUST come from the confirmed expected output spec**
11266
+ (learn-capability Phase 2.6) \u2014 never invent expectations at
11267
+ case-writing time. If a needed expectation is not in the spec, extend
11268
+ the spec with user confirmation first.
11269
+ **HARD RULE**: if the target/expected output is unclear at this
11270
+ point, STOP and ask the user (ask_user_to_clarify) \u2014 do not write a
11271
+ case with a guessed expectation.
11161
11272
 
11162
11273
  ## Suites per skill, by source
11163
11274
 
@@ -11501,10 +11612,20 @@ async function resolveConnections(type, connections, tenantId2) {
11501
11612
  throw err;
11502
11613
  }
11503
11614
  }
11504
- async function createCommonMiddlewares(middlewareConfigs, filesystemBackend, fsIsExised, tenantId2) {
11615
+ async function createCommonMiddlewares(middlewareConfigs, filesystemBackend, fsIsExised, tenantId2, model) {
11505
11616
  const middlewares = [];
11506
11617
  middlewares.push(createUnknownToolHandlerMiddleware());
11507
11618
  middlewares.push(createModelSelectorMiddleware());
11619
+ middlewares.push(createPatchToolCallsMiddleware());
11620
+ if (model) {
11621
+ middlewares.push(
11622
+ summarizationMiddleware({
11623
+ model,
11624
+ trigger: { tokens: 17e4 },
11625
+ keep: { messages: 6 }
11626
+ })
11627
+ );
11628
+ }
11508
11629
  const filesystemConfig = middlewareConfigs.find((m) => m.type === "filesystem");
11509
11630
  const clawConfig = middlewareConfigs.find((m) => m.type === "claw");
11510
11631
  const needsFilesystemBackend = filesystemConfig?.enabled || clawConfig?.enabled;
@@ -11836,7 +11957,7 @@ var ReActAgentGraphBuilder = class {
11836
11957
  const stateSchema2 = createReactAgentSchema(params.stateSchema);
11837
11958
  const middlewareConfigs = params.middleware || [];
11838
11959
  const filesystemBackend = createFilesystemBackendFactory(middlewareConfigs);
11839
- const middlewares = await createCommonMiddlewares(middlewareConfigs, filesystemBackend, void 0, params.tenantId);
11960
+ const middlewares = await createCommonMiddlewares(middlewareConfigs, filesystemBackend, void 0, params.tenantId, params.model);
11840
11961
  return createAgent({
11841
11962
  model: params.model,
11842
11963
  tools,
@@ -11854,17 +11975,16 @@ var ReActAgentGraphBuilder = class {
11854
11975
  import {
11855
11976
  createAgent as createAgent3,
11856
11977
  humanInTheLoopMiddleware as humanInTheLoopMiddleware2,
11857
- anthropicPromptCachingMiddleware,
11858
- summarizationMiddleware
11978
+ anthropicPromptCachingMiddleware
11859
11979
  } from "langchain";
11860
11980
 
11861
11981
  // src/deep_agent_new/middleware/subagents.ts
11862
11982
  import { z as z42 } from "zod/v3";
11863
11983
  import {
11864
- createMiddleware as createMiddleware10,
11984
+ createMiddleware as createMiddleware11,
11865
11985
  createAgent as createAgent2,
11866
11986
  tool as tool40,
11867
- ToolMessage as ToolMessage3,
11987
+ ToolMessage as ToolMessage4,
11868
11988
  humanInTheLoopMiddleware
11869
11989
  } from "langchain";
11870
11990
  import { Command as Command3, getCurrentTaskInput as getCurrentTaskInput2, GraphInterrupt as GraphInterrupt3 } from "@langchain/langgraph";
@@ -13810,7 +13930,7 @@ var AgentInstanceManager = class _AgentInstanceManager {
13810
13930
  var agentInstanceManager = AgentInstanceManager.getInstance();
13811
13931
 
13812
13932
  // src/middlewares/taskMiddleware.ts
13813
- import { createMiddleware as createMiddleware9, tool as tool39 } from "langchain";
13933
+ import { createMiddleware as createMiddleware10, tool as tool39 } from "langchain";
13814
13934
  import { z as z41 } from "zod";
13815
13935
  import { GraphInterrupt as GraphInterrupt2, interrupt as interrupt3 } from "@langchain/langgraph";
13816
13936
  function getRunConfig(config) {
@@ -14118,26 +14238,37 @@ function createTaskMiddleware() {
14118
14238
  });
14119
14239
  }
14120
14240
  };
14121
- return createMiddleware9({
14241
+ return createMiddleware10({
14122
14242
  name: "TaskMiddleware",
14123
14243
  contextSchema,
14124
14244
  wrapModelCall: async (request, handler) => {
14125
14245
  const taskPrompt = `## Task Management
14126
14246
 
14127
- You can use the \`manage_task\` tool to create persistent tasks for user-visible work tracking.
14247
+ You have the \`manage_task\` tool to track work. Task management is the
14248
+ ongoing record of a GOAL and its ACCEPTANCE CRITERIA.
14128
14249
 
14129
- ### When to create a task
14250
+ ### When to create a task (universal rule)
14251
+ - The goal is clear and you are about to start real work \u2192 create the
14252
+ parent task FIRST (with Objective + Acceptance Criteria in the
14253
+ description), then execute. This is a core duty, not optional.
14130
14254
  - The user explicitly asks you to track, manage, or follow up on work
14131
14255
  - The work spans multiple sessions or might need resumption later
14132
14256
  - The user needs to review or approve output before it is considered done
14133
14257
  - There are multiple independent work items the user wants visibility into
14134
14258
 
14135
14259
  ### When NOT to create a task
14260
+ - Goal not yet clear (still clarifying) \u2014 clarify first, then create
14136
14261
  - One-shot lookups or simple Q&A ("what is X?", "search for Y")
14137
14262
  - Internal exploration steps you take to understand the problem (use \`write_todos\` for your execution plan instead)
14138
14263
  - Trivial single-step actions that complete in the same turn
14139
14264
  - Conversational or informational requests with no deliverable
14140
14265
 
14266
+ ### Keep the task current
14267
+ A task is the living record of the goal + its acceptance criteria.
14268
+ Update the description as work proceeds: check off criteria as met,
14269
+ revise criteria when scope changes, append progress. Status changes
14270
+ only when the criteria are truly met.
14271
+
14141
14272
  ### Ownership defaults
14142
14273
  - No params: ownerType defaults to "user" with current user's ID
14143
14274
  - ownerType="agent": auto-fills ownerId from current agent (subtask for yourself)
@@ -14191,6 +14322,29 @@ var taskPlugin = {
14191
14322
  skills: {
14192
14323
  "task-definition": `## Using manage_task
14193
14324
 
14325
+ ### When to create a task (universal rule)
14326
+
14327
+ Create a task BEFORE executing whenever the goal is clear and you know
14328
+ what to do \u2014 not just for long or complex work:
14329
+
14330
+ - Goal is understood and you are about to start real work \u2192 create the
14331
+ parent task FIRST, then execute. The task tracks the work.
14332
+ - Goal is NOT yet clear (still clarifying, gathering requirements) \u2192
14333
+ do NOT create a task yet. Clarify first, create the task once scope
14334
+ is defined.
14335
+ - One-shot lookups, trivial single-step actions, or internal reasoning
14336
+ \u2192 no task needed.
14337
+
14338
+ Rule of thumb: if you can write an Objective and Acceptance Criteria
14339
+ for it, create the task before doing it. Work without a task = work
14340
+ without a contract.
14341
+
14342
+ **A task is the living record of the goal + its acceptance criteria.**
14343
+ Keep the description current as work proceeds: update the Objective
14344
+ when the target evolves, check off criteria as they are met, revise
14345
+ criteria when scope changes. Reading the task always tells you what
14346
+ "done" means; an outdated task is a broken contract.
14347
+
14194
14348
  ### Task description format
14195
14349
 
14196
14350
  When creating a task with manage_task, write the description in this Markdown structure:
@@ -14392,7 +14546,7 @@ function returnCommandWithStateUpdate(result, toolCallId) {
14392
14546
  update: {
14393
14547
  ...stateUpdate,
14394
14548
  messages: [
14395
- new ToolMessage3({
14549
+ new ToolMessage4({
14396
14550
  content: lastMessage?.content || "Task Failed to complete",
14397
14551
  tool_call_id: toolCallId,
14398
14552
  name: "task"
@@ -14585,7 +14739,7 @@ You are executing a persistent task (ID: ${input.taskId}). Use manage_task.updat
14585
14739
  return new Command3({
14586
14740
  update: {
14587
14741
  messages: [
14588
- new ToolMessage3({
14742
+ new ToolMessage4({
14589
14743
  content: `Async task started: ${subagent_thread_id}
14590
14744
  ${description}
14591
14745
  The result will be delivered as a notification when complete. Do not poll.`,
@@ -14619,7 +14773,7 @@ The result will be delivered as a notification when complete. Do not poll.`,
14619
14773
  return new Command3({
14620
14774
  update: {
14621
14775
  messages: [
14622
- new ToolMessage3({
14776
+ new ToolMessage4({
14623
14777
  content: error instanceof Error ? error.message : "Task Failed to complete",
14624
14778
  tool_call_id: config.toolCall.id,
14625
14779
  name: "task"
@@ -14856,7 +15010,7 @@ function createSubAgentMiddleware(options) {
14856
15010
  );
14857
15011
  }
14858
15012
  const effectiveSystemPrompt = allowAsync ? systemPrompt + getAsyncPromptText() : systemPrompt;
14859
- return createMiddleware10({
15013
+ return createMiddleware11({
14860
15014
  name: "subAgentMiddleware",
14861
15015
  tools: allTools,
14862
15016
  wrapModelCall: async (request, handler) => {
@@ -14875,53 +15029,6 @@ ${effectiveSystemPrompt}` : effectiveSystemPrompt;
14875
15029
  });
14876
15030
  }
14877
15031
 
14878
- // src/deep_agent_new/middleware/patch_tool_calls.ts
14879
- import {
14880
- createMiddleware as createMiddleware11,
14881
- ToolMessage as ToolMessage4,
14882
- AIMessage as AIMessage2
14883
- } from "langchain";
14884
- function createPatchToolCallsMiddleware() {
14885
- return createMiddleware11({
14886
- name: "patchToolCallsMiddleware",
14887
- beforeAgent: async (state) => {
14888
- const messages = state.messages;
14889
- if (!messages || messages.length === 0) {
14890
- return;
14891
- }
14892
- const patchedMessages = [];
14893
- for (let i = 0; i < messages.length; i++) {
14894
- const msg = messages[i];
14895
- patchedMessages.push(msg);
14896
- if (AIMessage2.isInstance(msg) && msg.tool_calls != null) {
14897
- for (const toolCall of msg.tool_calls) {
14898
- const correspondingToolMsg = messages.slice(i).find(
14899
- (m) => ToolMessage4.isInstance(m) && m.tool_call_id === toolCall.id
14900
- );
14901
- if (!correspondingToolMsg) {
14902
- const toolMsg = `Tool call ${toolCall.name} with id ${toolCall.id} was cancelled - another message came in before it could be completed.`;
14903
- patchedMessages.push(
14904
- new ToolMessage4({
14905
- content: toolMsg,
14906
- name: toolCall.name,
14907
- tool_call_id: toolCall.id
14908
- })
14909
- );
14910
- }
14911
- }
14912
- }
14913
- }
14914
- if (patchedMessages.length === messages.length) {
14915
- return;
14916
- }
14917
- return {
14918
- messages: patchedMessages.slice(messages.length)
14919
- // only the new ToolMessage patches
14920
- };
14921
- }
14922
- });
14923
- }
14924
-
14925
15032
  // src/deep_agent_new/middleware/date.ts
14926
15033
  import { createMiddleware as createMiddleware12, tool as tool41 } from "langchain";
14927
15034
  import { z as z43 } from "zod";
@@ -17834,36 +17941,20 @@ ${BASE_PROMPT}` : BASE_PROMPT;
17834
17941
  createFilesystemMiddleware({
17835
17942
  backend: filesystemBackend
17836
17943
  }),
17837
- // Subagent middleware: Automatic conversation summarization when token limits are approached
17838
- summarizationMiddleware({
17839
- model,
17840
- trigger: { tokens: 17e4 },
17841
- keep: { messages: 6 }
17842
- }),
17843
17944
  // Subagent middleware: Anthropic prompt caching for improved performance
17844
17945
  anthropicPromptCachingMiddleware({
17845
17946
  unsupportedModelBehavior: "ignore"
17846
17947
  }),
17847
- // Subagent middleware: Patches tool calls for compatibility
17848
- createPatchToolCallsMiddleware(),
17849
17948
  ...customMiddleware
17850
17949
  ],
17851
17950
  defaultInterruptOn: interruptOn,
17852
17951
  subagents,
17853
17952
  generalPurposeAgent: true
17854
17953
  }),
17855
- // Automatically summarizes conversation history when token limits are approached
17856
- summarizationMiddleware({
17857
- model,
17858
- trigger: { tokens: 17e4 },
17859
- keep: { messages: 6 }
17860
- }),
17861
17954
  // Enables Anthropic prompt caching for improved performance and reduced costs
17862
17955
  anthropicPromptCachingMiddleware({
17863
17956
  unsupportedModelBehavior: "ignore"
17864
- }),
17865
- // Patches tool calls to ensure compatibility across different model providers
17866
- createPatchToolCallsMiddleware()
17957
+ })
17867
17958
  ];
17868
17959
  if (interruptOn) {
17869
17960
  middleware.push(humanInTheLoopMiddleware2({ interruptOn }));
@@ -17918,7 +18009,7 @@ var DeepAgentGraphBuilder = class {
17918
18009
  }));
17919
18010
  const middlewareConfigs = params.middleware || [];
17920
18011
  const filesystemBackend = createFilesystemBackendFactory(middlewareConfigs);
17921
- const middlewares = await createCommonMiddlewares(middlewareConfigs, filesystemBackend, true, params.tenantId);
18012
+ const middlewares = await createCommonMiddlewares(middlewareConfigs, filesystemBackend, true, params.tenantId, params.model);
17922
18013
  const deepAgent = createDeepAgent({
17923
18014
  tools,
17924
18015
  model: params.model,
@@ -19534,7 +19625,7 @@ var TeamAgentGraphBuilder = class {
19534
19625
  });
19535
19626
  const middlewareConfigs = params.middleware || [];
19536
19627
  let filesystemBackend = createFilesystemBackendFactory(middlewareConfigs);
19537
- const middlewares = await createCommonMiddlewares(middlewareConfigs);
19628
+ const middlewares = await createCommonMiddlewares(middlewareConfigs, void 0, void 0, void 0, params.model);
19538
19629
  if (!filesystemBackend) {
19539
19630
  filesystemBackend = async (config2) => {
19540
19631
  return new StateBackend(config2);
@@ -19951,7 +20042,7 @@ var WorkflowAgentGraphBuilder = class {
19951
20042
  const checkpointer = getCheckpointSaver("default");
19952
20043
  const tools = params.tools.map((t) => t.executor).filter(Boolean);
19953
20044
  const middlewareConfigs = params.middleware || [];
19954
- const middlewares = await createCommonMiddlewares(middlewareConfigs, void 0, false, params.tenantId);
20045
+ const middlewares = await createCommonMiddlewares(middlewareConfigs, void 0, false, params.tenantId, params.model);
19955
20046
  const askMiddlewares = await createCommonMiddlewares([
19956
20047
  {
19957
20048
  id: "ask_user_to_clarify",
@@ -19961,7 +20052,7 @@ var WorkflowAgentGraphBuilder = class {
19961
20052
  enabled: true,
19962
20053
  config: {}
19963
20054
  }
19964
- ], void 0, false);
20055
+ ], void 0, false, void 0, params.model);
19965
20056
  const noWrapMiddlewares = stripWrapModelCallHook(middlewares);
19966
20057
  const noWrapAskMiddlewares = stripWrapModelCallHook(askMiddlewares);
19967
20058
  console.log(`[WF BUILDER] building default agent | toolCount=${tools.length} | middlewareCount=${middlewares.length}`);
@@ -21638,6 +21729,14 @@ Call the \`skill\` tool with skill_name: "agent-architecture" to load the
21638
21729
  authoritative workflow. Never announce that you will follow a skill \u2014
21639
21730
  load it and follow its content. If the load fails, retry once, then report it.
21640
21731
 
21732
+ TASK MANAGEMENT IS A CORE DUTY, not a per-skill option. Whenever the
21733
+ goal is clear and you know what to do, create a task FIRST (manage_task)
21734
+ before executing \u2014 for any multi-step work: learning, building,
21735
+ modifying, fixing, anything with an Objective and Acceptance Criteria.
21736
+ Subtasks per work item. Status must reflect reality. See [[task-tracking]].
21737
+ The sub-skills below only ADD their own task details on top of this
21738
+ universal duty.
21739
+
21641
21740
  Your sub-skills (accessible via the MOC or direct loading):
21642
21741
  - [[learn-capability]] \u2014 Learn from any source material \u2192 skills + agents
21643
21742
  - [[agent-build]] \u2014 Design and build single agents (REACT/DEEP_AGENT)
@@ -21802,7 +21901,15 @@ var agentArchitectConfig = {
21802
21901
  id: "task",
21803
21902
  type: "task",
21804
21903
  name: "Task",
21805
- description: "Track learning processes and fix loops with approval gates",
21904
+ description: "Core architect duty: once the goal is clear, create tasks to track every multi-step piece of work (learning, building, modifying, fixing). Subtasks per work item; status reflects reality.",
21905
+ enabled: true,
21906
+ config: {}
21907
+ },
21908
+ {
21909
+ id: "code_eval",
21910
+ type: "code_eval",
21911
+ name: "Code Evaluation",
21912
+ description: "Execute shell commands in the sandbox to support agent creation: unzip files, process data, run helper scripts. Distinct from the eval assessment system (run_eval).",
21806
21913
  enabled: true,
21807
21914
  config: {}
21808
21915
  },
@@ -25424,6 +25531,15 @@ function parseJudgeVerdict(raw) {
25424
25531
  return { error: error instanceof Error ? error.message : String(error) };
25425
25532
  }
25426
25533
  }
25534
+ var MAX_INTERRUPT_RESUMES = 5;
25535
+ function resolveInterruptResponse(policy, interrupt5) {
25536
+ if (policy.mode === "auto-approve") return policy.value ?? "\u540C\u610F";
25537
+ if (policy.mode === "auto-reject") return policy.value ?? "\u62D2\u7EDD";
25538
+ return policy.value ?? "";
25539
+ }
25540
+ function interruptValueText(value) {
25541
+ return typeof value === "string" ? value : JSON.stringify(value ?? "");
25542
+ }
25427
25543
  var _LatticeEval = class _LatticeEval {
25428
25544
  constructor(config = {}) {
25429
25545
  this.inMemoryLogs = [];
@@ -25490,7 +25606,8 @@ var _LatticeEval = class _LatticeEval {
25490
25606
  return acc;
25491
25607
  }, {});
25492
25608
  }
25493
- async executeAgentStep(step, threadId, inputMessage, files) {
25609
+ async executeAgentStep(step, threadId, inputMessage, files, interruptPolicy) {
25610
+ const hitlEvents = [];
25494
25611
  this.log("Executing agent step", {
25495
25612
  agent_id: step.agent_id,
25496
25613
  thread_id: threadId,
@@ -25508,19 +25625,74 @@ var _LatticeEval = class _LatticeEval {
25508
25625
  };
25509
25626
  const agent = agentInstanceManager.getAgent(agentParams);
25510
25627
  try {
25511
- const result = await agent.invoke({
25512
- input: {
25513
- message: step.override_message || inputMessage,
25514
- files: this.buildFileEntries(files)
25628
+ const stepInput = {
25629
+ message: step.override_message || inputMessage,
25630
+ files: this.buildFileEntries(files)
25631
+ };
25632
+ let result = await agent.invokeWithState({ input: stepInput });
25633
+ let resumeCount = 0;
25634
+ let pendingInterrupt;
25635
+ let interrupts = Array.isArray(result?.__interrupt__) ? result.__interrupt__ : [];
25636
+ while (interrupts.length > 0) {
25637
+ const interrupt5 = interrupts[0];
25638
+ if (!interrupt5) break;
25639
+ const policy = interruptPolicy;
25640
+ if (!policy || policy.mode === "stop" || resumeCount >= MAX_INTERRUPT_RESUMES) {
25641
+ pendingInterrupt = interrupt5;
25642
+ break;
25515
25643
  }
25516
- });
25644
+ const response = resolveInterruptResponse(policy, interrupt5);
25645
+ hitlEvents.push({
25646
+ type: "interrupt",
25647
+ id: interrupt5.id,
25648
+ value: interrupt5.value
25649
+ });
25650
+ this.log("Auto-resolving HITL interrupt", {
25651
+ agent_id: step.agent_id,
25652
+ thread_id: threadId,
25653
+ mode: policy.mode,
25654
+ interrupt_id: interrupt5.id,
25655
+ response,
25656
+ resume_count: resumeCount + 1
25657
+ });
25658
+ result = await agent.invokeWithState({ input: stepInput, command: { resume: response } });
25659
+ hitlEvents.push({
25660
+ type: "interrupt_response",
25661
+ id: interrupt5.id,
25662
+ mode: policy.mode,
25663
+ response
25664
+ });
25665
+ resumeCount++;
25666
+ interrupts = Array.isArray(result?.__interrupt__) ? result.__interrupt__ : [];
25667
+ }
25668
+ if (pendingInterrupt) {
25669
+ hitlEvents.push({
25670
+ type: "interrupt",
25671
+ id: pendingInterrupt.id,
25672
+ value: pendingInterrupt.value
25673
+ });
25674
+ this.log("Agent step interrupted by HITL (human input requested)", {
25675
+ agent_id: step.agent_id,
25676
+ thread_id: threadId,
25677
+ interrupt_id: pendingInterrupt.id,
25678
+ auto_resolved: resumeCount
25679
+ });
25680
+ } else {
25681
+ this.log("Agent step completed", {
25682
+ agent_id: step.agent_id,
25683
+ thread_id: threadId,
25684
+ response_keys: result ? Object.keys(result) : [],
25685
+ auto_resolved: resumeCount
25686
+ });
25687
+ }
25517
25688
  const responseData = { success: true, ...result };
25518
- this.log("Agent step completed", {
25519
- agent_id: step.agent_id,
25520
- thread_id: threadId,
25521
- response_keys: result ? Object.keys(result) : []
25522
- });
25523
- return { threadId, responseData };
25689
+ return {
25690
+ threadId,
25691
+ responseData,
25692
+ interrupted: pendingInterrupt ? true : void 0,
25693
+ interrupt: pendingInterrupt,
25694
+ hitlEvents
25695
+ };
25524
25696
  } catch (error) {
25525
25697
  const message = error instanceof Error ? error.message : String(error);
25526
25698
  this.log("Agent step failed", {
@@ -25583,15 +25755,32 @@ var _LatticeEval = class _LatticeEval {
25583
25755
  });
25584
25756
  let currentThreadId = threadId;
25585
25757
  let lastResponseData = null;
25758
+ let interrupt5;
25586
25759
  for (const step of evalCase.steps) {
25587
25760
  const result = await this.executeAgentStep(
25588
25761
  step,
25589
25762
  currentThreadId,
25590
25763
  evalCase.input.message,
25591
- evalCase.input.files || {}
25764
+ evalCase.input.files || {},
25765
+ evalCase.interruptPolicy
25592
25766
  );
25593
25767
  currentThreadId = result.threadId;
25594
25768
  lastResponseData = result.responseData;
25769
+ for (const evt of result.hitlEvents) {
25770
+ if (evt.type === "interrupt") {
25771
+ this.lastMessages.push({
25772
+ role: "interrupt",
25773
+ content: `HITL \u6682\u505C\uFF1AAgent \u8BF7\u6C42\u4EBA\u5DE5\u8F93\u5165 \u2014 ${interruptValueText(evt.value)}`,
25774
+ id: evt.id
25775
+ });
25776
+ } else {
25777
+ this.lastMessages.push({
25778
+ role: "interrupt_response",
25779
+ content: `\u5DF2\u81EA\u52A8\u54CD\u5E94\uFF08\u6D4B\u8BD5\u7B56\u7565 ${evt.mode}\uFF09\uFF1A${evt.response}`,
25780
+ id: evt.id
25781
+ });
25782
+ }
25783
+ }
25595
25784
  const existingIds = new Set(this.lastMessages.map((m) => m.id).filter(Boolean));
25596
25785
  if (result.responseData?.messages && Array.isArray(result.responseData.messages)) {
25597
25786
  for (const msg of result.responseData.messages) {
@@ -25613,6 +25802,13 @@ var _LatticeEval = class _LatticeEval {
25613
25802
  } else {
25614
25803
  content = String(msg.content || "");
25615
25804
  }
25805
+ if (Array.isArray(msg.tool_calls) && msg.tool_calls.length > 0) {
25806
+ const toolCallStr = msg.tool_calls.map(
25807
+ (tc) => `tool_call: ${tc.name}(${JSON.stringify(tc.args ?? {})})`
25808
+ ).join("\n");
25809
+ content = content ? `${content}
25810
+ ${toolCallStr}` : toolCallStr;
25811
+ }
25616
25812
  this.lastMessages.push({
25617
25813
  role,
25618
25814
  content,
@@ -25624,13 +25820,21 @@ var _LatticeEval = class _LatticeEval {
25624
25820
  }
25625
25821
  }
25626
25822
  }
25823
+ if (result.interrupted) {
25824
+ interrupt5 = result.interrupt;
25825
+ this.log("Case paused for HITL \u2014 remaining steps skipped; judge will evaluate the pause", {
25826
+ case_id: evalCase.caseId,
25827
+ interrupt_id: interrupt5?.id
25828
+ });
25829
+ break;
25830
+ }
25627
25831
  }
25628
25832
  this.log("All agent steps completed", {
25629
25833
  case_id: evalCase.caseId,
25630
25834
  final_thread_id: currentThreadId,
25631
25835
  message_count: this.lastMessages.length
25632
25836
  });
25633
- const finalOutput = this.extractFinalMessage(lastResponseData);
25837
+ const finalOutput = interrupt5 ? typeof interrupt5.value === "string" ? interrupt5.value : JSON.stringify(interrupt5.value ?? "") : this.extractFinalMessage(lastResponseData);
25634
25838
  this.lastFinalOutput = finalOutput;
25635
25839
  const trajectory = this.buildTrajectory();
25636
25840
  this.log("Final output extracted", {
@@ -25689,6 +25893,8 @@ ${rubricsSection}
25689
25893
  3. **\u8FC7\u7A0B\u6821\u9A8C**\uFF1A\u5982\u679C"\u6267\u884C\u8FC7\u7A0B"\u663E\u793A Agent \u672A\u6267\u884C\u5FC5\u8981\u7684\u4E2D\u95F4\u6B65\u9AA4\uFF08\u5982\u5E94\u8C03\u7528\u5DE5\u5177\u800C\u672A\u8C03\u7528\uFF09\uFF0C\u5373\u4F7F\u6700\u7EC8\u8F93\u51FA\u770B\u4F3C\u5408\u7406\uFF0C\u4E5F\u5E94\u5728\u5BF9\u5E94\u6307\u6807\u4E0A\u6263\u5206\u3002
25690
25894
  4. **\u8BC1\u636E\u5BFC\u5411**\uFF1A\u5728\u7ED9\u51FA\u539F\u56E0\uFF08reason\uFF09\u65F6\uFF0C\u5FC5\u987B\u5F15\u7528\u6267\u884C\u8FC7\u7A0B\u6216\u6700\u7EC8\u8F93\u51FA\u4E2D\u7684\u5177\u4F53\u5185\u5BB9\u3002
25691
25895
  5. **\u52A0\u6743\u8BA1\u7B97**\uFF1A\u6700\u7EC8\u5206\u6570\u4E3A\u5404\u9879\u6307\u6807\u5F97\u5206\u4E0E\u5176\u6743\u91CD\u7684\u4E58\u79EF\u4E4B\u548C\uFF080-100\u5206\u5236\uFF09\u3002
25896
+ 6. **HITL \u4E2D\u65AD\u5224\u5B9A**\uFF1A\u5982\u679C\u6267\u884C\u8FC7\u7A0B\u4E2D\u51FA\u73B0\u300CHITL \u6682\u505C\uFF1AAgent \u8BF7\u6C42\u4EBA\u5DE5\u8F93\u5165\u300D\u6761\u76EE\uFF0C\u8BF4\u660E Agent \u5728\u7B49\u5F85\u4EBA\u5DE5\u786E\u8BA4\u3002\u8BF7\u628A\u5B83\u5F53\u4F5C\u88AB\u6D4B\u4E1A\u52A1\u884C\u4E3A\u6765\u8BC4\u5224\uFF1A\u82E5\u671F\u671B\u8F93\u51FA\u8981\u6C42\u81EA\u4E3B\u5B8C\u6210\uFF08\u5982"\u65E0\u9700\u786E\u8BA4\u81EA\u52A8\u6267\u884C"\uFF09\uFF0C\u8BF7\u6C42\u4EBA\u5DE5\u8F93\u5165\u5E94\u5224\u5931\u8D25\uFF1B\u82E5\u671F\u671B\u8F93\u51FA\u8981\u6C42\u5148\u83B7\u5F97\u786E\u8BA4\u6216\u6279\u51C6\uFF08\u5982"\u6267\u884C\u524D\u5FC5\u987B\u8BF7\u6C42\u6279\u51C6"\uFF09\uFF0C\u8BF7\u6C42\u4EBA\u5DE5\u8F93\u5165\u662F\u6B63\u786E\u884C\u4E3A\uFF0C\u5E94\u7ED3\u5408\u5176\u65F6\u673A\u4E0E\u5185\u5BB9\u5224\u5B9A\u4E3A\u901A\u8FC7\u6216\u6309\u6307\u6807\u7ED9\u5206\u3002
25897
+ 7. **HITL \u81EA\u52A8\u54CD\u5E94\u5224\u5B9A**\uFF1A\u5982\u679C\u300CHITL \u6682\u505C\u300D\u6761\u76EE\u4E4B\u540E\u51FA\u73B0\u300C\u5DF2\u81EA\u52A8\u54CD\u5E94\uFF08\u6D4B\u8BD5\u7B56\u7565 auto-approve/auto-reject/canned-response\uFF09\u300D\u6761\u76EE\uFF0C\u8BF4\u660E\u6D4B\u8BD5\u6846\u67B6\u6CE8\u5165\u4E86\u4EBA\u5DE5\u56DE\u590D\u3001\u6D41\u7A0B\u5DF2\u7EE7\u7EED\u2014\u2014\u8BF7\u6309**\u5B8C\u6574\u6D41\u7A0B**\u8BC4\u5224\u6682\u505C\u4E4B\u540E\u7684\u884C\u4E3A\uFF08\u5982\u6279\u51C6\u540E\u662F\u5426\u6B63\u786E\u6267\u884C\u4E86\u64CD\u4F5C\uFF09\uFF0C\u5E76\u6838\u5BF9\u81EA\u52A8\u54CD\u5E94\u5185\u5BB9\u662F\u5426\u7B26\u5408\u4EBA\u5DE5\u56DE\u590D\u7684\u5408\u7406\u9884\u671F\u3002
25692
25898
 
25693
25899
  # \u8F93\u51FA\u683C\u5F0F\uFF08\u4EC5JSON\uFF09
25694
25900
  \u4F60\u5FC5\u987B\u4EC5\u4EE5 JSON \u683C\u5F0F\u56DE\u590D\uFF0C\u7ED3\u6784\u5982\u4E0B\uFF1A
@@ -25843,7 +26049,9 @@ ${rubricsSection}
25843
26049
  pass,
25844
26050
  final_score: finalScore,
25845
26051
  dimension_results: dimensionResults,
25846
- summary: parsedResult.summary || testResultContent
26052
+ summary: parsedResult.summary || testResultContent,
26053
+ interrupted: interrupt5 ? true : void 0,
26054
+ interrupt: interrupt5 ? { id: interrupt5.id, value: interrupt5.value } : void 0
25847
26055
  };
25848
26056
  }
25849
26057
  };
@@ -25858,6 +26066,8 @@ async function evaluateLatticeCaseWithLogs(evalCase, config) {
25858
26066
  return {
25859
26067
  caseId: evalCase.caseId,
25860
26068
  result,
26069
+ interrupted: result?.interrupted,
26070
+ interrupt: result?.interrupt,
25861
26071
  duration_ms: meta.duration_ms,
25862
26072
  thread_id: meta.thread_id,
25863
26073
  judge_thread_id: meta.judge_thread_id,
@@ -25940,7 +26150,8 @@ function resolveTemplateCase(templateCase, templates) {
25940
26150
  eval: {
25941
26151
  content_assertion: templateCase.eval.content_assertion,
25942
26152
  eval_rubrics: templateCase.eval.eval_rubrics || template.default_case.eval?.eval_rubrics
25943
- }
26153
+ },
26154
+ interruptPolicy: templateCase.interruptPolicy ?? template.default_case.interruptPolicy
25944
26155
  };
25945
26156
  return resolvedCase;
25946
26157
  }
@@ -26006,6 +26217,8 @@ var LatticeEvalSuite = class {
26006
26217
  result: run.result,
26007
26218
  error: run.error,
26008
26219
  error_stack: run.error_stack,
26220
+ interrupted: run.interrupted,
26221
+ interrupt: run.interrupt,
26009
26222
  duration_ms: run.duration_ms,
26010
26223
  thread_id: run.thread_id,
26011
26224
  judge_thread_id: run.judge_thread_id,
@@ -26038,6 +26251,8 @@ var LatticeEvalSuite = class {
26038
26251
  result: run.result,
26039
26252
  error: run.error,
26040
26253
  error_stack: run.error_stack,
26254
+ interrupted: run.interrupted,
26255
+ interrupt: run.interrupt,
26041
26256
  duration_ms: run.duration_ms,
26042
26257
  thread_id: run.thread_id,
26043
26258
  judge_thread_id: run.judge_thread_id,
@@ -26247,24 +26462,29 @@ Running batch: ${this.project.projectName} (${this.getSuiteNames().length} suite
26247
26462
  let total_cases = 0;
26248
26463
  let passed_cases = 0;
26249
26464
  let failed_cases = 0;
26465
+ let interrupted_cases = 0;
26250
26466
  const suites = [];
26251
26467
  for (const [suiteName, caseResults] of results.entries()) {
26252
26468
  const suiteTotal = caseResults.length;
26253
26469
  const suitePassed = caseResults.filter((r) => r.result?.pass).length;
26470
+ const suiteInterrupted = caseResults.filter((r) => r.interrupted).length;
26254
26471
  const suiteFailed = suiteTotal - suitePassed;
26255
26472
  total_cases += suiteTotal;
26256
26473
  passed_cases += suitePassed;
26257
26474
  failed_cases += suiteFailed;
26475
+ interrupted_cases += suiteInterrupted;
26258
26476
  suites.push({
26259
26477
  suiteName,
26260
26478
  total_cases: suiteTotal,
26261
26479
  passed_cases: suitePassed,
26262
26480
  failed_cases: suiteFailed,
26481
+ interrupted_cases: suiteInterrupted,
26263
26482
  cases: caseResults.map((r) => ({
26264
26483
  caseId: r.caseId,
26265
26484
  pass: r.result?.pass,
26266
26485
  final_score: r.result?.final_score,
26267
- error: r.error
26486
+ error: r.error,
26487
+ interrupted: r.interrupted
26268
26488
  }))
26269
26489
  });
26270
26490
  }
@@ -26282,13 +26502,14 @@ Running batch: ${this.project.projectName} (${this.getSuiteNames().length} suite
26282
26502
  total_cases,
26283
26503
  passed_cases,
26284
26504
  failed_cases,
26505
+ interrupted_cases,
26285
26506
  pass_rate: total_cases > 0 ? passed_cases / total_cases : 0
26286
26507
  },
26287
26508
  suites
26288
26509
  };
26289
26510
  console.log(`
26290
26511
  === Summary ===`);
26291
- console.log(`Total: ${report.summary.total_cases} | Passed: ${report.summary.passed_cases} | Failed: ${report.summary.failed_cases} | Pass Rate: ${(report.summary.pass_rate * 100).toFixed(2)}%`);
26512
+ console.log(`Total: ${report.summary.total_cases} | Passed: ${report.summary.passed_cases} | Failed: ${report.summary.failed_cases} | Interrupted: ${report.summary.interrupted_cases} | Pass Rate: ${(report.summary.pass_rate * 100).toFixed(2)}%`);
26292
26513
  return { batch_id, results, report };
26293
26514
  }
26294
26515
  };
@@ -28237,10 +28458,21 @@ Write assertions as objective, verifiable natural language:
28237
28458
  - steps: [{agent_id: "a"}, {agent_id: "b", override_message: "Based on..."}] for chain
28238
28459
  - outputType: "message_content" or "file_content"
28239
28460
 
28461
+ ## Designing HITL Cases
28462
+ If the flow under test PAUSES for human input (HITL \u2014 the agent requests approval/confirmation), configure interruptPolicy on the case \u2014 otherwise the run stops at the pause and the flow AFTER the human input is never tested:
28463
+
28464
+ - interruptPolicy: {mode: "auto-approve"} \u2014 inject "\u540C\u610F"; tests the full flow after approval (e.g. payment executed after approval). Use value to override the injected text.
28465
+ - interruptPolicy: {mode: "auto-reject"} \u2014 inject "\u62D2\u7EDD"; tests the rejection path.
28466
+ - interruptPolicy: {mode: "canned-response", value: "..."} \u2014 inject an exact human reply; tests behavior under a specific response.
28467
+ - Omit (default "stop") \u2014 the case pauses at the request; the judge evaluates whether pausing was the correct behavior (e.g. "must request approval" PASSES; "must be autonomous" FAILS).
28468
+
28469
+ Choose per the assertion: if the assertion describes what happens AFTER the human input, you MUST set an auto-resolve policy.
28470
+
28240
28471
  ## Checklist
28241
28472
  1. Check existing assets with read_eval to avoid duplication
28242
28473
  2. Start with 3-5 high-signal cases
28243
- 3. Confirm with user before calling manage_eval
28474
+ 3. If the flow contains a human-approval step and the assertion covers what happens AFTER it, set interruptPolicy (auto-approve / auto-reject / canned-response) \u2014 never leave it unset in that case
28475
+ 4. Confirm with user before calling manage_eval
28244
28476
  `,
28245
28477
  "eval-run-and-govern": `---
28246
28478
  name: eval-run-and-govern
@@ -28250,8 +28482,11 @@ description: Run agent evaluations, interpret results, diagnose failures, and re
28250
28482
  # Agent Governance Loop
28251
28483
 
28252
28484
  1. Discover project \u2192 read_eval list_projects
28253
- 2. Start evaluation \u2192 run_eval start(projectId) \u2014 ASYNC, may take minutes
28254
- 3. Poll status \u2192 run_eval status(runId) with backoff: 15s, 30s, 60s, max 120s
28485
+ 2. Start evaluation \u2192 run_eval start(projectId) \u2014 SYNCHRONOUS by default:
28486
+ blocks up to ~150s and returns the FINAL RESULTS in one call.
28487
+ Hold-out (validation) runs return aggregates only.
28488
+ 3. If still running (or use wait: false for fire-and-forget) \u2192 poll
28489
+ run_eval status(runId, sleepMs) \u2014 pass sleepMs to pace (15s, 30s, 60s, max 120s)
28255
28490
  4. If runnerAlive=false and status=running \u2192 ORPHANED (gateway restart).
28256
28491
  resume(runId) marks it failed automatically \u2014 then start a new run.
28257
28492
  5. Get results \u2192 read_eval get_run_results(runId) for per-case dimension scores
@@ -28320,10 +28555,12 @@ function sanitize(obj) {
28320
28555
  }
28321
28556
  function aggregateHoldoutResults(results) {
28322
28557
  const passed = results.filter((r) => r.pass).length;
28558
+ const interrupted = results.filter((r) => r.interrupted).length;
28323
28559
  return {
28324
28560
  holdout: true,
28325
28561
  passedCases: passed,
28326
28562
  failedCases: results.length - passed,
28563
+ interruptedCases: interrupted,
28327
28564
  passRate: results.length > 0 ? passed / results.length : 0,
28328
28565
  totalCases: results.length
28329
28566
  };
@@ -28394,13 +28631,8 @@ function createReadEvalTool() {
28394
28631
  if (!run) return JSON.stringify({ success: false, error: "Run not found" });
28395
28632
  const results = await store.getResultsByRun(tid, input.runId);
28396
28633
  if (run.holdout) {
28397
- const passed = results.filter((r) => r.pass).length;
28398
28634
  data = {
28399
- holdout: true,
28400
- passedCases: passed,
28401
- failedCases: results.length - passed,
28402
- passRate: results.length > 0 ? passed / results.length : 0,
28403
- totalCases: results.length,
28635
+ ...aggregateHoldoutResults(results),
28404
28636
  message: "Hold-out run \u2014 per-case results withheld. Only aggregates are available."
28405
28637
  };
28406
28638
  } else {
@@ -28435,6 +28667,7 @@ ACTIONS:
28435
28667
  - get_run_results(runId) \u2014 per-case results with dimension scores.
28436
28668
  For HOLD-OUT (validation-only) runs: returns AGGREGATES ONLY
28437
28669
  (passRate, counts) \u2014 per-case details are withheld by design.
28670
+ Cases paused for human input (HITL) carry interrupted=true and are judged \u2014 the judge evaluates whether pausing was correct business behavior.
28438
28671
  - get_project_report(projectId) \u2014 aggregated stats across all runs`,
28439
28672
  schema: schema6
28440
28673
  }
@@ -28465,7 +28698,11 @@ function createManageEvalTool() {
28465
28698
  steps: z66.array(z66.object({ agent_id: z66.string(), override_message: z66.string().optional() })).optional(),
28466
28699
  outputType: z66.enum(["file_content", "message_content"]).optional(),
28467
28700
  contentAssertion: z66.string().optional(),
28468
- rubrics: z66.array(z66.object({ name: z66.string(), weight: z66.number(), description: z66.string() })).optional()
28701
+ rubrics: z66.array(z66.object({ name: z66.string(), weight: z66.number(), description: z66.string() })).optional(),
28702
+ interruptPolicy: z66.object({
28703
+ mode: z66.enum(["stop", "auto-approve", "auto-reject", "canned-response"]).describe("stop=judge the pause; auto-approve/auto-reject/canned-response=resume the agent to test the flow after the human input"),
28704
+ value: z66.string().optional().describe("Response to inject (defaults: \u540C\u610F / \u62D2\u7EDD; required for canned-response)")
28705
+ }).optional().describe("Optional for create_case/update_case \u2014 how HITL interrupts are handled")
28469
28706
  });
28470
28707
  return tool62(
28471
28708
  async (input, exeConfig) => {
@@ -28523,7 +28760,8 @@ function createManageEvalTool() {
28523
28760
  steps: input.steps,
28524
28761
  outputType: input.outputType,
28525
28762
  contentAssertion: input.contentAssertion,
28526
- rubrics: input.rubrics
28763
+ rubrics: input.rubrics,
28764
+ interruptPolicy: input.interruptPolicy
28527
28765
  });
28528
28766
  break;
28529
28767
  case "update_case":
@@ -28531,7 +28769,8 @@ function createManageEvalTool() {
28531
28769
  inputMessage: input.inputMessage,
28532
28770
  contentAssertion: input.contentAssertion,
28533
28771
  steps: input.steps,
28534
- rubrics: input.rubrics
28772
+ rubrics: input.rubrics,
28773
+ interruptPolicy: input.interruptPolicy
28535
28774
  });
28536
28775
  break;
28537
28776
  case "delete_case":
@@ -28556,9 +28795,13 @@ Project: create_project(name, description?, judgeModelKey?, concurrency?) | upda
28556
28795
  **When creating a project from within a workspace, the workspace/project context is
28557
28796
  automatically bound \u2014 eval runs will execute in the same workspace.**
28558
28797
  Suite: create_suite(projectId, name) | update_suite | delete_suite
28559
- Case: create_case(suiteId, inputMessage, steps, outputType, contentAssertion, inputFiles?, rubrics?)
28798
+ Case: create_case(suiteId, inputMessage, steps, outputType, contentAssertion, inputFiles?, rubrics?, interruptPolicy?)
28560
28799
  steps is [{agent_id, override_message?}]. outputType is "file_content" or "message_content".
28561
- rubrics is [{name, weight, description}]. | update_case | delete_case`,
28800
+ rubrics is [{name, weight, description}]. | update_case | delete_case
28801
+ interruptPolicy: {mode: "stop"|"auto-approve"|"auto-reject"|"canned-response", value?} \u2014 how HITL interrupts are handled:
28802
+ stop (default): case pauses at the human-input request; the judge evaluates the pause as business behavior.
28803
+ auto-approve / auto-reject: the runner injects approval/rejection and tests the FULL flow after the pause.
28804
+ canned-response: injects the exact value (simulates a specific human reply).`,
28562
28805
  schema: schema6
28563
28806
  }
28564
28807
  );
@@ -28672,6 +28915,8 @@ ACTIONS:
28672
28915
  - start(projectId, suiteIds?, caseIds?, wait?) \u2014 begin evaluation (optionally only the listed suites, and/or only the listed cases).
28673
28916
  wait=true (DEFAULT): SYNCHRONOUS \u2014 blocks up to ~150s and returns the FINAL RESULTS in one call:
28674
28917
  { status, results } with per-case pass/score (hold-out/validation runs: aggregates only \u2014 per-case details withheld by design).
28918
+ Cases paused for human input (HITL) carry interrupted=true and ARE judged \u2014 the judge evaluates whether requesting
28919
+ the human was the correct behavior (assertions like "must approve first" PASS; "must be autonomous" FAIL). interruptedCases counts these.
28675
28920
  If the run exceeds 150s: returns { runId, status: "running" } \u2014 NOT an error \u2014 then poll with status/resume until completed.
28676
28921
  wait=false: fire-and-forget \u2014 returns { runId } immediately; poll with status.
28677
28922
  - status(runId, sleepMs?) \u2014 sleep sleepMs first (to pace polling), then return current status + runnerAlive flag:
@@ -28977,67 +29222,64 @@ verification choice, then start benchmarking.
28977
29222
 
28978
29223
  ## Task Tracking \u2014 see [[task-tracking]]
28979
29224
 
28980
- **Create the parent task when the task is actually defined** \u2014 after
28981
- Phase 0 clarification is complete and the user confirmed the path
28982
- (fresh vs incremental). Do NOT create tasks during clarification:
28983
- while asking questions (0.0-0.4) you don't know what the task is yet.
28984
- Once the scope is clear (end of 0.5), that is the moment to create:
28985
- manage_task create("Learn [material]", ownerType: "agent"). Then a
28986
- subtask per phase as you start it. Update status to reflect reality \u2014
28987
- never mark a subtask completed while eval fails. Resume interrupted
28988
- runs with manage_task list.
29225
+ **Universal principle**: once the goal is clear and you know what to
29226
+ do, create the parent task BEFORE executing (manage_task create, see
29227
+ [[task-tracking]]). In this workflow: after Phase 0 clarification
29228
+ completes and the user confirmed the path (end of 0.5), create the
29229
+ parent task; then a subtask per phase as you start it. The parent task
29230
+ description carries the GOAL MODEL (0.1.5) as Objective + Acceptance
29231
+ Criteria; the expected output spec (2.6) updates the criteria. Update
29232
+ status to reflect reality \u2014 never mark a subtask completed while eval
29233
+ fails. Resume interrupted runs with manage_task list.
28989
29234
 
28990
29235
  Widgets: call \`load_guidelines\` ONCE before your first \`show_widget\`
28991
29236
  (show_widget hard-requires it), then reuse.
28992
29237
 
28993
29238
  ---
28994
29239
 
28995
- ## Phase 1: Probe (material-dependent)
28996
-
28997
- The probing strategy depends on the material type from Phase 0.0:
28998
-
28999
- **User-description material**: the requirements come from the
29000
- conversation itself \u2014 no probing needed. Extract the agent's goal,
29001
- inputs, outputs, and constraints from what the user described. Go
29002
- straight to design.
29003
-
29004
- **Document material** (PDF / spec / manual):
29005
- If the engine was chosen in Phase 0 (0.3 \u2460-\u2464): skip the comparison \u2014
29006
- parse directly with \`parse_document\` using the chosen engine
29007
- (file_path, engine, output_path per file).
29008
- Otherwise: run the document-parser-benchmark subagent via \`task\` on each file.
29009
- Collect engine scores, parsed output (via \`read_file\`), and feature signatures.
29010
-
29011
- **Engine selection IS distilled knowledge, not just setup.** For document
29012
- material, the benchmark answers "which engine parses THIS document (or
29013
- this class of document) best?" \u2014 that answer is knowledge that drives
29014
- the whole rest of the run:
29015
- - **Builds the agent**: the chosen engine's \`parse_document\` goes into
29016
- the production agent's middleware/tools.
29017
- - **Designs the tests**: the chosen engine's parsed output becomes the
29018
- baseline input for eval cases \u2014 cases feed parsed output to the agent
29019
- and assert correct extraction from it.
29020
- - **Seeds the skill**: the feature signature (tables? scans? mixed
29021
- zh/en?) plus the winning engine becomes a skill note ("for PO PDFs
29022
- with tables, use textin") reusable for future similar documents.
29023
- So when the user wants an agent whose purpose is document PARSING (not
29024
- extraction), the workflow is the same: benchmark to learn the engine
29025
- choice, then build the agent around that engine and test against its
29026
- output. Do NOT treat parsing as a pure tool-assembly task \u2014 the engine
29027
- choice is unknown knowledge until probed.
29028
-
29029
- **API spec material**: read the spec directly \u2014 no parsing engine needed.
29030
- Extract endpoints, schemas, request/response examples from the text.
29031
-
29032
- **Conversation material**: extract the workflow, decisions, and corrections
29033
- from the conversation context \u2014 no parsing engine needed.
29034
-
29035
- **Spreadsheet material**: parse cells directly \u2014 structured data needs
29036
- no engine comparison.
29037
- If verification will happen (0.2 \u2460 or \u2461): concurrently, \`list_agents\` to
29038
- discover existing agents with relevant capabilities (see \xA75).
29039
- For \u2460, look for agents with data-access tools (SQL / API). For \u2461, look
29040
- for agents with independence.
29240
+ ## Phase 1: Explore (goal-driven path finding)
29241
+
29242
+ The goal model (0.1.5) is set. Now EXPLORE how to achieve it \u2014 actively
29243
+ hunt for the path, do not passively read. Three exploration fronts:
29244
+
29245
+ **A. Existing assets (reuse, don't rebuild):**
29246
+ - \`list_agents\` / \`load_skills\` \u2192 are there existing agents or skills
29247
+ that already do part of this? Reuse them (update_agent if needed)
29248
+ instead of building from scratch. This is a goal-relevant check, not
29249
+ a materials step.
29250
+ - \`list_tools\` / \`list_middleware_types\` \u2192 what capabilities exist
29251
+ that the goal needs (parsing, data access, browser...)?
29252
+ - \`list_connections\` \u2192 are the data sources the goal depends on
29253
+ already connected?
29254
+ - If verification will happen (0.2 \u2460 or \u2461): concurrently discover
29255
+ executor candidates (see \xA75). \u2460 \u2192 data-access tools; \u2461 \u2192 independence.
29256
+
29257
+ **B. Material probing (by material type):**
29258
+ - **User-description**: the requirements come from the conversation.
29259
+ Extract goal, inputs, outputs, constraints \u2014 then explore the
29260
+ implementation path (A + feasibility): what assets exist, what tools
29261
+ are needed, what blockers stand between the goal and its achievement.
29262
+ - **Document** (PDF / spec / manual): benchmark engines as needed \u2014
29263
+ parse directly with the chosen engine (0.3 \u2460-\u2464) or run
29264
+ document-parser-benchmark. Engine selection IS distilled knowledge:
29265
+ it builds the agent (engine's parse_document into middleware), seeds
29266
+ the skill (feature signature + winning engine), and designs the tests
29267
+ (engine output as case baseline input).
29268
+ - **API spec**: read directly \u2014 endpoints, schemas, examples.
29269
+ - **Conversation**: extract workflow, decisions, corrections.
29270
+ - **Spreadsheet**: parse cells directly.
29271
+
29272
+ **C. Feasibility (path blockers):**
29273
+ - What stands between the goal and achievement? Missing tools, missing
29274
+ connections, data access, permission constraints, ambiguous
29275
+ requirements.
29276
+ - Does the goal require orchestration (\u2192 Phase 2 split decision)?
29277
+ - Surface these in the recommendation (Phase 1.5) \u2014 the user decides
29278
+ the path, informed by what exploration found.
29279
+
29280
+ Exploration is COMPLETE when you can answer: what exists to reuse,
29281
+ what must be built, what tools/connections are needed, and what blocks
29282
+ the goal. Do not go to design without this map.
29041
29283
 
29042
29284
  ---
29043
29285
 
@@ -29052,17 +29294,21 @@ candidate and assess (Validation Agent Design \xA70) \u2014 state which are
29052
29294
  usable and which are not, with reasons. For \u2460, the executor needs data
29053
29295
  tools + independence. For \u2461, independence only. If no candidate fits,
29054
29296
  plan to build one via \xA75.
29055
- Present probe results as widget, then MUST call
29297
+ Present the EXPLORATION map as widget \u2014 what exists to reuse, what
29298
+ must be built, tools/connections needed, blockers found \u2014 then
29299
+ recommend the IMPLEMENTATION PATH: reuse existing X, build new Y,
29300
+ split or single agent (Phase 2 input). MUST call
29056
29301
  \`ask_user_to_clarify\` NOW:
29057
29302
  {
29058
29303
  "questions": [{
29059
- "question": "Confirm the recommendation?",
29304
+ "question": "Confirm the recommended path?",
29060
29305
  "options": ["Confirm", "Adjust"],
29061
29306
  "type": "single",
29062
29307
  "required": true
29063
29308
  }]
29064
29309
  }
29065
- Skills planning belongs to Phase 2 \u2014 this phase presents data, not plans.
29310
+ Skills planning belongs to Phase 2 \u2014 this phase presents the path, not
29311
+ the detailed plan.
29066
29312
 
29067
29313
  ---
29068
29314
 
@@ -29130,9 +29376,53 @@ user-description material this IS the core phase; for material-based
29130
29376
  learning it designs the agent that runs the learned skill. Agent
29131
29377
  metadata (verified/version/source) must be set on creation.
29132
29378
 
29379
+ ## Phase 2.6: Expected Output Specification (mandatory \u2014 goal-driven)
29380
+
29381
+ **Expectations come FIRST, before writing the skill.** You cannot write
29382
+ a skill (or test cases) without a target. Define the expected output
29383
+ specification from the goal model (0.1.5: real goal / consumer / usable
29384
+ state) BEFORE Phase 3:
29385
+
29386
+ **HARD RULE \u2014 never guess the target.** If the goal, the expected
29387
+ output, the consumer, or the usable state is unclear at ANY point
29388
+ before writing test cases, you MUST ask the user via
29389
+ \`ask_user_to_clarify\` \u2014 do NOT proceed with an assumed expectation.
29390
+ A test case written against a guessed expectation is worthless: it
29391
+ validates the wrong thing. When in doubt, ask.
29392
+
29393
+ Per skill, define the EXPECTED OUTPUT SPEC (based on intent 0.1 and
29394
+ consumer 0.1.5):
29395
+ - Extract data \u2192 expected fields (names, types, formats), required vs
29396
+ optional, output structure (JSON schema shape, table columns)
29397
+ - Validate rules \u2192 expected judgment outcomes (pass/fail conditions),
29398
+ boundary values, and the reason format
29399
+ - Execute workflow \u2192 expected step sequence, decision points, final
29400
+ outcome shape
29401
+ - Answer knowledge \u2192 expected answer form (with/without sources,
29402
+ length, structure)
29403
+
29404
+ This spec IS the acceptance standard. Phase 4 contentAssertion must be
29405
+ derived from it (not invented at case-writing time). Present the
29406
+ expected output spec to the user and MUST call \`ask_user_to_clarify\`
29407
+ NOW per skill:
29408
+ {
29409
+ "questions": [{
29410
+ "question": "Confirm the expected output spec for {skill-name}?",
29411
+ "options": ["Confirm", "Adjust"],
29412
+ "type": "single",
29413
+ "required": true,
29414
+ "allowOther": true
29415
+ }]
29416
+ }
29417
+ Record the confirmed spec in the parent task description. This replaces
29418
+ guess-then-confirm: the skill is written TO MEET the spec, and test
29419
+ cases assert AGAINST the spec \u2014 no expectation is invented later.
29420
+
29133
29421
  ## Phase 3: Create Skills
29134
29422
 
29135
- Write SKILL.md to \`/root/.agents/skills/{name}/SKILL.md\` one at a time.
29423
+ Write SKILL.md to \`/root/.agents/skills/{name}/SKILL.md\` one at a time,
29424
+ designed TO MEET the expected output spec confirmed in Phase 2.6 \u2014 the
29425
+ skill encodes how to produce the spec's expected output.
29136
29426
  Show the skill content in text first, then MUST call
29137
29427
  \`ask_user_to_clarify\` NOW per skill:
29138
29428
  {
@@ -29426,7 +29716,8 @@ This learning loop adds its own scenario rules:
29426
29716
  - Business usability (output reaches the goal's "usable state")
29427
29717
  - Consumer fit (format/contract satisfies who uses the result)
29428
29718
  contentAssertion must encode the usable state from the goal model
29429
- (0.1.5), not just technical correctness.
29719
+ (0.1.5), derived from the EXPECTED OUTPUT SPEC (Phase 2.6) \u2014 never
29720
+ invented at case-writing time.
29430
29721
 
29431
29722
  1. One suite per skill per source: cases test "can this skill do it" \u2014
29432
29723
  never mix skills in one suite
@@ -29469,6 +29760,12 @@ Learning-specific suite guidance:
29469
29760
  - User-description material: {skill}-requirement-derived \u2014 cases from
29470
29761
  user's described requirements
29471
29762
 
29763
+ **Case expectations come from the confirmed spec** (Phase 2.6): the
29764
+ contentAssertion of every case must be derived from the expected output
29765
+ spec, NOT invented at case-writing time. If a case needs an expectation
29766
+ not in the spec, go back and extend the spec with user confirmation
29767
+ first \u2014 never guess expectations on the fly.
29768
+
29472
29769
  [[completion-gate]] applies \u2014 eval must pass before declaring done.
29473
29770
 
29474
29771
  ## Phase 5: Retrospective