@axiom-lattice/core 3.0.3 → 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
 
@@ -11023,6 +11124,10 @@ Always set metadata on agent creation. At minimum:
11023
11124
  - verified: "unverified" (upgraded after eval passes)
11024
11125
  - version: "1.0" (bump on each update_agent)
11025
11126
  - source: the material name or "user-description"
11127
+ - role: "orchestrator" | "sub-agent" \u2014 set when the agent is part of a
11128
+ parent+subAgents structure (role clarity, User Interaction Rules).
11129
+ The eval project is NOT recorded \u2014 it is derived by naming convention
11130
+ (eval-{agent-id}, see [[eval-verify]] Setup).
11026
11131
  When trust upgrades, update both the skill's verified frontmatter and
11027
11132
  the agent's metadata.verified \u2014 they must stay in sync.
11028
11133
 
@@ -11138,16 +11243,32 @@ description: Run agent evaluations, interpret results, fix failures, and
11138
11243
  metadata:
11139
11244
  domain: agent-building
11140
11245
  verified: unverified
11246
+ subSkills:
11247
+ - eval-design-tests
11248
+ - eval-run-and-govern
11141
11249
  ---
11142
11250
  # Eval Verify \u2014 Run Evaluations and Upgrade Trust
11143
11251
 
11144
11252
  ## Setup
11145
11253
 
11146
11254
  0. Load [[eval-design-tests]] for case design guidance
11147
- 1. \`read_eval list_projects\` \u2192 find "eval-{domain}"
11148
- Exists \u2192 reuse projectId. New \u2192 create_project(name: "eval-{domain}")
11255
+ 1. **One eval project per agent**, named \`eval-{agent-id}\`:
11256
+ \`read_eval list_projects\` \u2192 find "eval-{agent-id}"
11257
+ Exists \u2192 reuse projectId. New \u2192 create_project(name: "eval-{agent-id}")
11258
+ - The agent-id is the eval project's subject. Observability: from an
11259
+ agent's id you can find its eval project by naming convention.
11260
+ - Orchestrator + subAgents \u2192 one eval project per sub-agent
11261
+ (eval-{sub-agent-id}) PLUS one integration eval project for the
11262
+ parent (eval-{parent-id}) \u2014 see Layered verification below.
11149
11263
  2. \`manage_eval create_case(suiteId, ...)\` per sample or scenario
11150
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.
11151
11272
 
11152
11273
  ## Suites per skill, by source
11153
11274
 
@@ -11491,10 +11612,20 @@ async function resolveConnections(type, connections, tenantId2) {
11491
11612
  throw err;
11492
11613
  }
11493
11614
  }
11494
- async function createCommonMiddlewares(middlewareConfigs, filesystemBackend, fsIsExised, tenantId2) {
11615
+ async function createCommonMiddlewares(middlewareConfigs, filesystemBackend, fsIsExised, tenantId2, model) {
11495
11616
  const middlewares = [];
11496
11617
  middlewares.push(createUnknownToolHandlerMiddleware());
11497
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
+ }
11498
11629
  const filesystemConfig = middlewareConfigs.find((m) => m.type === "filesystem");
11499
11630
  const clawConfig = middlewareConfigs.find((m) => m.type === "claw");
11500
11631
  const needsFilesystemBackend = filesystemConfig?.enabled || clawConfig?.enabled;
@@ -11826,7 +11957,7 @@ var ReActAgentGraphBuilder = class {
11826
11957
  const stateSchema2 = createReactAgentSchema(params.stateSchema);
11827
11958
  const middlewareConfigs = params.middleware || [];
11828
11959
  const filesystemBackend = createFilesystemBackendFactory(middlewareConfigs);
11829
- const middlewares = await createCommonMiddlewares(middlewareConfigs, filesystemBackend, void 0, params.tenantId);
11960
+ const middlewares = await createCommonMiddlewares(middlewareConfigs, filesystemBackend, void 0, params.tenantId, params.model);
11830
11961
  return createAgent({
11831
11962
  model: params.model,
11832
11963
  tools,
@@ -11844,17 +11975,16 @@ var ReActAgentGraphBuilder = class {
11844
11975
  import {
11845
11976
  createAgent as createAgent3,
11846
11977
  humanInTheLoopMiddleware as humanInTheLoopMiddleware2,
11847
- anthropicPromptCachingMiddleware,
11848
- summarizationMiddleware
11978
+ anthropicPromptCachingMiddleware
11849
11979
  } from "langchain";
11850
11980
 
11851
11981
  // src/deep_agent_new/middleware/subagents.ts
11852
11982
  import { z as z42 } from "zod/v3";
11853
11983
  import {
11854
- createMiddleware as createMiddleware10,
11984
+ createMiddleware as createMiddleware11,
11855
11985
  createAgent as createAgent2,
11856
11986
  tool as tool40,
11857
- ToolMessage as ToolMessage3,
11987
+ ToolMessage as ToolMessage4,
11858
11988
  humanInTheLoopMiddleware
11859
11989
  } from "langchain";
11860
11990
  import { Command as Command3, getCurrentTaskInput as getCurrentTaskInput2, GraphInterrupt as GraphInterrupt3 } from "@langchain/langgraph";
@@ -13800,7 +13930,7 @@ var AgentInstanceManager = class _AgentInstanceManager {
13800
13930
  var agentInstanceManager = AgentInstanceManager.getInstance();
13801
13931
 
13802
13932
  // src/middlewares/taskMiddleware.ts
13803
- import { createMiddleware as createMiddleware9, tool as tool39 } from "langchain";
13933
+ import { createMiddleware as createMiddleware10, tool as tool39 } from "langchain";
13804
13934
  import { z as z41 } from "zod";
13805
13935
  import { GraphInterrupt as GraphInterrupt2, interrupt as interrupt3 } from "@langchain/langgraph";
13806
13936
  function getRunConfig(config) {
@@ -14108,26 +14238,37 @@ function createTaskMiddleware() {
14108
14238
  });
14109
14239
  }
14110
14240
  };
14111
- return createMiddleware9({
14241
+ return createMiddleware10({
14112
14242
  name: "TaskMiddleware",
14113
14243
  contextSchema,
14114
14244
  wrapModelCall: async (request, handler) => {
14115
14245
  const taskPrompt = `## Task Management
14116
14246
 
14117
- 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.
14118
14249
 
14119
- ### 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.
14120
14254
  - The user explicitly asks you to track, manage, or follow up on work
14121
14255
  - The work spans multiple sessions or might need resumption later
14122
14256
  - The user needs to review or approve output before it is considered done
14123
14257
  - There are multiple independent work items the user wants visibility into
14124
14258
 
14125
14259
  ### When NOT to create a task
14260
+ - Goal not yet clear (still clarifying) \u2014 clarify first, then create
14126
14261
  - One-shot lookups or simple Q&A ("what is X?", "search for Y")
14127
14262
  - Internal exploration steps you take to understand the problem (use \`write_todos\` for your execution plan instead)
14128
14263
  - Trivial single-step actions that complete in the same turn
14129
14264
  - Conversational or informational requests with no deliverable
14130
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
+
14131
14272
  ### Ownership defaults
14132
14273
  - No params: ownerType defaults to "user" with current user's ID
14133
14274
  - ownerType="agent": auto-fills ownerId from current agent (subtask for yourself)
@@ -14181,6 +14322,29 @@ var taskPlugin = {
14181
14322
  skills: {
14182
14323
  "task-definition": `## Using manage_task
14183
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
+
14184
14348
  ### Task description format
14185
14349
 
14186
14350
  When creating a task with manage_task, write the description in this Markdown structure:
@@ -14382,7 +14546,7 @@ function returnCommandWithStateUpdate(result, toolCallId) {
14382
14546
  update: {
14383
14547
  ...stateUpdate,
14384
14548
  messages: [
14385
- new ToolMessage3({
14549
+ new ToolMessage4({
14386
14550
  content: lastMessage?.content || "Task Failed to complete",
14387
14551
  tool_call_id: toolCallId,
14388
14552
  name: "task"
@@ -14575,7 +14739,7 @@ You are executing a persistent task (ID: ${input.taskId}). Use manage_task.updat
14575
14739
  return new Command3({
14576
14740
  update: {
14577
14741
  messages: [
14578
- new ToolMessage3({
14742
+ new ToolMessage4({
14579
14743
  content: `Async task started: ${subagent_thread_id}
14580
14744
  ${description}
14581
14745
  The result will be delivered as a notification when complete. Do not poll.`,
@@ -14609,7 +14773,7 @@ The result will be delivered as a notification when complete. Do not poll.`,
14609
14773
  return new Command3({
14610
14774
  update: {
14611
14775
  messages: [
14612
- new ToolMessage3({
14776
+ new ToolMessage4({
14613
14777
  content: error instanceof Error ? error.message : "Task Failed to complete",
14614
14778
  tool_call_id: config.toolCall.id,
14615
14779
  name: "task"
@@ -14846,7 +15010,7 @@ function createSubAgentMiddleware(options) {
14846
15010
  );
14847
15011
  }
14848
15012
  const effectiveSystemPrompt = allowAsync ? systemPrompt + getAsyncPromptText() : systemPrompt;
14849
- return createMiddleware10({
15013
+ return createMiddleware11({
14850
15014
  name: "subAgentMiddleware",
14851
15015
  tools: allTools,
14852
15016
  wrapModelCall: async (request, handler) => {
@@ -14865,53 +15029,6 @@ ${effectiveSystemPrompt}` : effectiveSystemPrompt;
14865
15029
  });
14866
15030
  }
14867
15031
 
14868
- // src/deep_agent_new/middleware/patch_tool_calls.ts
14869
- import {
14870
- createMiddleware as createMiddleware11,
14871
- ToolMessage as ToolMessage4,
14872
- AIMessage as AIMessage2
14873
- } from "langchain";
14874
- function createPatchToolCallsMiddleware() {
14875
- return createMiddleware11({
14876
- name: "patchToolCallsMiddleware",
14877
- beforeAgent: async (state) => {
14878
- const messages = state.messages;
14879
- if (!messages || messages.length === 0) {
14880
- return;
14881
- }
14882
- const patchedMessages = [];
14883
- for (let i = 0; i < messages.length; i++) {
14884
- const msg = messages[i];
14885
- patchedMessages.push(msg);
14886
- if (AIMessage2.isInstance(msg) && msg.tool_calls != null) {
14887
- for (const toolCall of msg.tool_calls) {
14888
- const correspondingToolMsg = messages.slice(i).find(
14889
- (m) => ToolMessage4.isInstance(m) && m.tool_call_id === toolCall.id
14890
- );
14891
- if (!correspondingToolMsg) {
14892
- const toolMsg = `Tool call ${toolCall.name} with id ${toolCall.id} was cancelled - another message came in before it could be completed.`;
14893
- patchedMessages.push(
14894
- new ToolMessage4({
14895
- content: toolMsg,
14896
- name: toolCall.name,
14897
- tool_call_id: toolCall.id
14898
- })
14899
- );
14900
- }
14901
- }
14902
- }
14903
- }
14904
- if (patchedMessages.length === messages.length) {
14905
- return;
14906
- }
14907
- return {
14908
- messages: patchedMessages.slice(messages.length)
14909
- // only the new ToolMessage patches
14910
- };
14911
- }
14912
- });
14913
- }
14914
-
14915
15032
  // src/deep_agent_new/middleware/date.ts
14916
15033
  import { createMiddleware as createMiddleware12, tool as tool41 } from "langchain";
14917
15034
  import { z as z43 } from "zod";
@@ -17824,36 +17941,20 @@ ${BASE_PROMPT}` : BASE_PROMPT;
17824
17941
  createFilesystemMiddleware({
17825
17942
  backend: filesystemBackend
17826
17943
  }),
17827
- // Subagent middleware: Automatic conversation summarization when token limits are approached
17828
- summarizationMiddleware({
17829
- model,
17830
- trigger: { tokens: 17e4 },
17831
- keep: { messages: 6 }
17832
- }),
17833
17944
  // Subagent middleware: Anthropic prompt caching for improved performance
17834
17945
  anthropicPromptCachingMiddleware({
17835
17946
  unsupportedModelBehavior: "ignore"
17836
17947
  }),
17837
- // Subagent middleware: Patches tool calls for compatibility
17838
- createPatchToolCallsMiddleware(),
17839
17948
  ...customMiddleware
17840
17949
  ],
17841
17950
  defaultInterruptOn: interruptOn,
17842
17951
  subagents,
17843
17952
  generalPurposeAgent: true
17844
17953
  }),
17845
- // Automatically summarizes conversation history when token limits are approached
17846
- summarizationMiddleware({
17847
- model,
17848
- trigger: { tokens: 17e4 },
17849
- keep: { messages: 6 }
17850
- }),
17851
17954
  // Enables Anthropic prompt caching for improved performance and reduced costs
17852
17955
  anthropicPromptCachingMiddleware({
17853
17956
  unsupportedModelBehavior: "ignore"
17854
- }),
17855
- // Patches tool calls to ensure compatibility across different model providers
17856
- createPatchToolCallsMiddleware()
17957
+ })
17857
17958
  ];
17858
17959
  if (interruptOn) {
17859
17960
  middleware.push(humanInTheLoopMiddleware2({ interruptOn }));
@@ -17908,7 +18009,7 @@ var DeepAgentGraphBuilder = class {
17908
18009
  }));
17909
18010
  const middlewareConfigs = params.middleware || [];
17910
18011
  const filesystemBackend = createFilesystemBackendFactory(middlewareConfigs);
17911
- const middlewares = await createCommonMiddlewares(middlewareConfigs, filesystemBackend, true, params.tenantId);
18012
+ const middlewares = await createCommonMiddlewares(middlewareConfigs, filesystemBackend, true, params.tenantId, params.model);
17912
18013
  const deepAgent = createDeepAgent({
17913
18014
  tools,
17914
18015
  model: params.model,
@@ -19524,7 +19625,7 @@ var TeamAgentGraphBuilder = class {
19524
19625
  });
19525
19626
  const middlewareConfigs = params.middleware || [];
19526
19627
  let filesystemBackend = createFilesystemBackendFactory(middlewareConfigs);
19527
- const middlewares = await createCommonMiddlewares(middlewareConfigs);
19628
+ const middlewares = await createCommonMiddlewares(middlewareConfigs, void 0, void 0, void 0, params.model);
19528
19629
  if (!filesystemBackend) {
19529
19630
  filesystemBackend = async (config2) => {
19530
19631
  return new StateBackend(config2);
@@ -19941,7 +20042,7 @@ var WorkflowAgentGraphBuilder = class {
19941
20042
  const checkpointer = getCheckpointSaver("default");
19942
20043
  const tools = params.tools.map((t) => t.executor).filter(Boolean);
19943
20044
  const middlewareConfigs = params.middleware || [];
19944
- const middlewares = await createCommonMiddlewares(middlewareConfigs, void 0, false, params.tenantId);
20045
+ const middlewares = await createCommonMiddlewares(middlewareConfigs, void 0, false, params.tenantId, params.model);
19945
20046
  const askMiddlewares = await createCommonMiddlewares([
19946
20047
  {
19947
20048
  id: "ask_user_to_clarify",
@@ -19951,7 +20052,7 @@ var WorkflowAgentGraphBuilder = class {
19951
20052
  enabled: true,
19952
20053
  config: {}
19953
20054
  }
19954
- ], void 0, false);
20055
+ ], void 0, false, void 0, params.model);
19955
20056
  const noWrapMiddlewares = stripWrapModelCallHook(middlewares);
19956
20057
  const noWrapAskMiddlewares = stripWrapModelCallHook(askMiddlewares);
19957
20058
  console.log(`[WF BUILDER] building default agent | toolCount=${tools.length} | middlewareCount=${middlewares.length}`);
@@ -21628,10 +21729,19 @@ Call the \`skill\` tool with skill_name: "agent-architecture" to load the
21628
21729
  authoritative workflow. Never announce that you will follow a skill \u2014
21629
21730
  load it and follow its content. If the load fails, retry once, then report it.
21630
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
+
21631
21740
  Your sub-skills (accessible via the MOC or direct loading):
21632
21741
  - [[learn-capability]] \u2014 Learn from any source material \u2192 skills + agents
21633
21742
  - [[agent-build]] \u2014 Design and build single agents (REACT/DEEP_AGENT)
21634
21743
  - [[design-workflow]] \u2014 Design workflow agents
21744
+ - [[eval-verify]] \u2014 Run evaluations with fix loop, hold-out, trust upgrade
21635
21745
  - [[task-tracking]] \u2014 Manage persistent tasks (manage_task)
21636
21746
  - [[completion-gate]] \u2014 THE rule: no agent is "done" without eval
21637
21747
  - [[domain-moc]] \u2014 Create the domain MOC (mandatory per learning run)
@@ -21791,7 +21901,15 @@ var agentArchitectConfig = {
21791
21901
  id: "task",
21792
21902
  type: "task",
21793
21903
  name: "Task",
21794
- 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).",
21795
21913
  enabled: true,
21796
21914
  config: {}
21797
21915
  },
@@ -25413,6 +25531,15 @@ function parseJudgeVerdict(raw) {
25413
25531
  return { error: error instanceof Error ? error.message : String(error) };
25414
25532
  }
25415
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
+ }
25416
25543
  var _LatticeEval = class _LatticeEval {
25417
25544
  constructor(config = {}) {
25418
25545
  this.inMemoryLogs = [];
@@ -25479,7 +25606,8 @@ var _LatticeEval = class _LatticeEval {
25479
25606
  return acc;
25480
25607
  }, {});
25481
25608
  }
25482
- async executeAgentStep(step, threadId, inputMessage, files) {
25609
+ async executeAgentStep(step, threadId, inputMessage, files, interruptPolicy) {
25610
+ const hitlEvents = [];
25483
25611
  this.log("Executing agent step", {
25484
25612
  agent_id: step.agent_id,
25485
25613
  thread_id: threadId,
@@ -25497,19 +25625,74 @@ var _LatticeEval = class _LatticeEval {
25497
25625
  };
25498
25626
  const agent = agentInstanceManager.getAgent(agentParams);
25499
25627
  try {
25500
- const result = await agent.invoke({
25501
- input: {
25502
- message: step.override_message || inputMessage,
25503
- 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;
25504
25643
  }
25505
- });
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
+ }
25506
25688
  const responseData = { success: true, ...result };
25507
- this.log("Agent step completed", {
25508
- agent_id: step.agent_id,
25509
- thread_id: threadId,
25510
- response_keys: result ? Object.keys(result) : []
25511
- });
25512
- return { threadId, responseData };
25689
+ return {
25690
+ threadId,
25691
+ responseData,
25692
+ interrupted: pendingInterrupt ? true : void 0,
25693
+ interrupt: pendingInterrupt,
25694
+ hitlEvents
25695
+ };
25513
25696
  } catch (error) {
25514
25697
  const message = error instanceof Error ? error.message : String(error);
25515
25698
  this.log("Agent step failed", {
@@ -25572,15 +25755,32 @@ var _LatticeEval = class _LatticeEval {
25572
25755
  });
25573
25756
  let currentThreadId = threadId;
25574
25757
  let lastResponseData = null;
25758
+ let interrupt5;
25575
25759
  for (const step of evalCase.steps) {
25576
25760
  const result = await this.executeAgentStep(
25577
25761
  step,
25578
25762
  currentThreadId,
25579
25763
  evalCase.input.message,
25580
- evalCase.input.files || {}
25764
+ evalCase.input.files || {},
25765
+ evalCase.interruptPolicy
25581
25766
  );
25582
25767
  currentThreadId = result.threadId;
25583
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
+ }
25584
25784
  const existingIds = new Set(this.lastMessages.map((m) => m.id).filter(Boolean));
25585
25785
  if (result.responseData?.messages && Array.isArray(result.responseData.messages)) {
25586
25786
  for (const msg of result.responseData.messages) {
@@ -25602,6 +25802,13 @@ var _LatticeEval = class _LatticeEval {
25602
25802
  } else {
25603
25803
  content = String(msg.content || "");
25604
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
+ }
25605
25812
  this.lastMessages.push({
25606
25813
  role,
25607
25814
  content,
@@ -25613,13 +25820,21 @@ var _LatticeEval = class _LatticeEval {
25613
25820
  }
25614
25821
  }
25615
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
+ }
25616
25831
  }
25617
25832
  this.log("All agent steps completed", {
25618
25833
  case_id: evalCase.caseId,
25619
25834
  final_thread_id: currentThreadId,
25620
25835
  message_count: this.lastMessages.length
25621
25836
  });
25622
- const finalOutput = this.extractFinalMessage(lastResponseData);
25837
+ const finalOutput = interrupt5 ? typeof interrupt5.value === "string" ? interrupt5.value : JSON.stringify(interrupt5.value ?? "") : this.extractFinalMessage(lastResponseData);
25623
25838
  this.lastFinalOutput = finalOutput;
25624
25839
  const trajectory = this.buildTrajectory();
25625
25840
  this.log("Final output extracted", {
@@ -25678,6 +25893,8 @@ ${rubricsSection}
25678
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
25679
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
25680
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
25681
25898
 
25682
25899
  # \u8F93\u51FA\u683C\u5F0F\uFF08\u4EC5JSON\uFF09
25683
25900
  \u4F60\u5FC5\u987B\u4EC5\u4EE5 JSON \u683C\u5F0F\u56DE\u590D\uFF0C\u7ED3\u6784\u5982\u4E0B\uFF1A
@@ -25832,7 +26049,9 @@ ${rubricsSection}
25832
26049
  pass,
25833
26050
  final_score: finalScore,
25834
26051
  dimension_results: dimensionResults,
25835
- 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
25836
26055
  };
25837
26056
  }
25838
26057
  };
@@ -25847,6 +26066,8 @@ async function evaluateLatticeCaseWithLogs(evalCase, config) {
25847
26066
  return {
25848
26067
  caseId: evalCase.caseId,
25849
26068
  result,
26069
+ interrupted: result?.interrupted,
26070
+ interrupt: result?.interrupt,
25850
26071
  duration_ms: meta.duration_ms,
25851
26072
  thread_id: meta.thread_id,
25852
26073
  judge_thread_id: meta.judge_thread_id,
@@ -25929,7 +26150,8 @@ function resolveTemplateCase(templateCase, templates) {
25929
26150
  eval: {
25930
26151
  content_assertion: templateCase.eval.content_assertion,
25931
26152
  eval_rubrics: templateCase.eval.eval_rubrics || template.default_case.eval?.eval_rubrics
25932
- }
26153
+ },
26154
+ interruptPolicy: templateCase.interruptPolicy ?? template.default_case.interruptPolicy
25933
26155
  };
25934
26156
  return resolvedCase;
25935
26157
  }
@@ -25995,6 +26217,8 @@ var LatticeEvalSuite = class {
25995
26217
  result: run.result,
25996
26218
  error: run.error,
25997
26219
  error_stack: run.error_stack,
26220
+ interrupted: run.interrupted,
26221
+ interrupt: run.interrupt,
25998
26222
  duration_ms: run.duration_ms,
25999
26223
  thread_id: run.thread_id,
26000
26224
  judge_thread_id: run.judge_thread_id,
@@ -26027,6 +26251,8 @@ var LatticeEvalSuite = class {
26027
26251
  result: run.result,
26028
26252
  error: run.error,
26029
26253
  error_stack: run.error_stack,
26254
+ interrupted: run.interrupted,
26255
+ interrupt: run.interrupt,
26030
26256
  duration_ms: run.duration_ms,
26031
26257
  thread_id: run.thread_id,
26032
26258
  judge_thread_id: run.judge_thread_id,
@@ -26236,24 +26462,29 @@ Running batch: ${this.project.projectName} (${this.getSuiteNames().length} suite
26236
26462
  let total_cases = 0;
26237
26463
  let passed_cases = 0;
26238
26464
  let failed_cases = 0;
26465
+ let interrupted_cases = 0;
26239
26466
  const suites = [];
26240
26467
  for (const [suiteName, caseResults] of results.entries()) {
26241
26468
  const suiteTotal = caseResults.length;
26242
26469
  const suitePassed = caseResults.filter((r) => r.result?.pass).length;
26470
+ const suiteInterrupted = caseResults.filter((r) => r.interrupted).length;
26243
26471
  const suiteFailed = suiteTotal - suitePassed;
26244
26472
  total_cases += suiteTotal;
26245
26473
  passed_cases += suitePassed;
26246
26474
  failed_cases += suiteFailed;
26475
+ interrupted_cases += suiteInterrupted;
26247
26476
  suites.push({
26248
26477
  suiteName,
26249
26478
  total_cases: suiteTotal,
26250
26479
  passed_cases: suitePassed,
26251
26480
  failed_cases: suiteFailed,
26481
+ interrupted_cases: suiteInterrupted,
26252
26482
  cases: caseResults.map((r) => ({
26253
26483
  caseId: r.caseId,
26254
26484
  pass: r.result?.pass,
26255
26485
  final_score: r.result?.final_score,
26256
- error: r.error
26486
+ error: r.error,
26487
+ interrupted: r.interrupted
26257
26488
  }))
26258
26489
  });
26259
26490
  }
@@ -26271,13 +26502,14 @@ Running batch: ${this.project.projectName} (${this.getSuiteNames().length} suite
26271
26502
  total_cases,
26272
26503
  passed_cases,
26273
26504
  failed_cases,
26505
+ interrupted_cases,
26274
26506
  pass_rate: total_cases > 0 ? passed_cases / total_cases : 0
26275
26507
  },
26276
26508
  suites
26277
26509
  };
26278
26510
  console.log(`
26279
26511
  === Summary ===`);
26280
- 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)}%`);
26281
26513
  return { batch_id, results, report };
26282
26514
  }
26283
26515
  };
@@ -28226,10 +28458,21 @@ Write assertions as objective, verifiable natural language:
28226
28458
  - steps: [{agent_id: "a"}, {agent_id: "b", override_message: "Based on..."}] for chain
28227
28459
  - outputType: "message_content" or "file_content"
28228
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
+
28229
28471
  ## Checklist
28230
28472
  1. Check existing assets with read_eval to avoid duplication
28231
28473
  2. Start with 3-5 high-signal cases
28232
- 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
28233
28476
  `,
28234
28477
  "eval-run-and-govern": `---
28235
28478
  name: eval-run-and-govern
@@ -28239,8 +28482,11 @@ description: Run agent evaluations, interpret results, diagnose failures, and re
28239
28482
  # Agent Governance Loop
28240
28483
 
28241
28484
  1. Discover project \u2192 read_eval list_projects
28242
- 2. Start evaluation \u2192 run_eval start(projectId) \u2014 ASYNC, may take minutes
28243
- 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)
28244
28490
  4. If runnerAlive=false and status=running \u2192 ORPHANED (gateway restart).
28245
28491
  resume(runId) marks it failed automatically \u2014 then start a new run.
28246
28492
  5. Get results \u2192 read_eval get_run_results(runId) for per-case dimension scores
@@ -28261,7 +28507,26 @@ Use read_eval get_run_results for multiple runs and present comparison.
28261
28507
  `
28262
28508
  };
28263
28509
 
28510
+ // src/tool_lattice/withToolTimeout.ts
28511
+ function withToolTimeout(executor, timeoutMs = 18e4) {
28512
+ return async (input, exeConfig) => {
28513
+ return new Promise((resolve4, reject) => {
28514
+ const timer = setTimeout(() => {
28515
+ reject(new Error(`Tool execution timed out after ${timeoutMs}ms`));
28516
+ }, timeoutMs);
28517
+ executor(input, exeConfig).then((result) => {
28518
+ clearTimeout(timer);
28519
+ resolve4(result);
28520
+ }).catch((err) => {
28521
+ clearTimeout(timer);
28522
+ reject(err);
28523
+ });
28524
+ });
28525
+ };
28526
+ }
28527
+
28264
28528
  // src/middlewares/evalMiddleware.ts
28529
+ var RUN_EVAL_SYNC_WAIT_MS = 15e4;
28265
28530
  function getStore() {
28266
28531
  return getStoreLattice("default", "eval").store;
28267
28532
  }
@@ -28288,6 +28553,25 @@ function sanitize(obj) {
28288
28553
  }
28289
28554
  return out;
28290
28555
  }
28556
+ function aggregateHoldoutResults(results) {
28557
+ const passed = results.filter((r) => r.pass).length;
28558
+ const interrupted = results.filter((r) => r.interrupted).length;
28559
+ return {
28560
+ holdout: true,
28561
+ passedCases: passed,
28562
+ failedCases: results.length - passed,
28563
+ interruptedCases: interrupted,
28564
+ passRate: results.length > 0 ? passed / results.length : 0,
28565
+ totalCases: results.length
28566
+ };
28567
+ }
28568
+ async function runWithResults(tid, store, svc, run, runnerAlive) {
28569
+ const results = run.status === "completed" ? await store.getResultsByRun(tid, run.id) : void 0;
28570
+ if (run.holdout && results) {
28571
+ return { ...run, runnerAlive, results: aggregateHoldoutResults(results) };
28572
+ }
28573
+ return { ...run, runnerAlive, results };
28574
+ }
28291
28575
  function createReadEvalTool() {
28292
28576
  const schema6 = z66.object({
28293
28577
  action: z66.enum([
@@ -28347,13 +28631,8 @@ function createReadEvalTool() {
28347
28631
  if (!run) return JSON.stringify({ success: false, error: "Run not found" });
28348
28632
  const results = await store.getResultsByRun(tid, input.runId);
28349
28633
  if (run.holdout) {
28350
- const passed = results.filter((r) => r.pass).length;
28351
28634
  data = {
28352
- holdout: true,
28353
- passedCases: passed,
28354
- failedCases: results.length - passed,
28355
- passRate: results.length > 0 ? passed / results.length : 0,
28356
- totalCases: results.length,
28635
+ ...aggregateHoldoutResults(results),
28357
28636
  message: "Hold-out run \u2014 per-case results withheld. Only aggregates are available."
28358
28637
  };
28359
28638
  } else {
@@ -28388,6 +28667,7 @@ ACTIONS:
28388
28667
  - get_run_results(runId) \u2014 per-case results with dimension scores.
28389
28668
  For HOLD-OUT (validation-only) runs: returns AGGREGATES ONLY
28390
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.
28391
28671
  - get_project_report(projectId) \u2014 aggregated stats across all runs`,
28392
28672
  schema: schema6
28393
28673
  }
@@ -28418,7 +28698,11 @@ function createManageEvalTool() {
28418
28698
  steps: z66.array(z66.object({ agent_id: z66.string(), override_message: z66.string().optional() })).optional(),
28419
28699
  outputType: z66.enum(["file_content", "message_content"]).optional(),
28420
28700
  contentAssertion: z66.string().optional(),
28421
- 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")
28422
28706
  });
28423
28707
  return tool62(
28424
28708
  async (input, exeConfig) => {
@@ -28476,7 +28760,8 @@ function createManageEvalTool() {
28476
28760
  steps: input.steps,
28477
28761
  outputType: input.outputType,
28478
28762
  contentAssertion: input.contentAssertion,
28479
- rubrics: input.rubrics
28763
+ rubrics: input.rubrics,
28764
+ interruptPolicy: input.interruptPolicy
28480
28765
  });
28481
28766
  break;
28482
28767
  case "update_case":
@@ -28484,7 +28769,8 @@ function createManageEvalTool() {
28484
28769
  inputMessage: input.inputMessage,
28485
28770
  contentAssertion: input.contentAssertion,
28486
28771
  steps: input.steps,
28487
- rubrics: input.rubrics
28772
+ rubrics: input.rubrics,
28773
+ interruptPolicy: input.interruptPolicy
28488
28774
  });
28489
28775
  break;
28490
28776
  case "delete_case":
@@ -28509,9 +28795,13 @@ Project: create_project(name, description?, judgeModelKey?, concurrency?) | upda
28509
28795
  **When creating a project from within a workspace, the workspace/project context is
28510
28796
  automatically bound \u2014 eval runs will execute in the same workspace.**
28511
28797
  Suite: create_suite(projectId, name) | update_suite | delete_suite
28512
- Case: create_case(suiteId, inputMessage, steps, outputType, contentAssertion, inputFiles?, rubrics?)
28798
+ Case: create_case(suiteId, inputMessage, steps, outputType, contentAssertion, inputFiles?, rubrics?, interruptPolicy?)
28513
28799
  steps is [{agent_id, override_message?}]. outputType is "file_content" or "message_content".
28514
- 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).`,
28515
28805
  schema: schema6
28516
28806
  }
28517
28807
  );
@@ -28522,100 +28812,121 @@ function createRunEvalTool() {
28522
28812
  projectId: z66.string().optional().describe("Required for start"),
28523
28813
  suiteIds: z66.array(z66.string()).optional().describe("Optional for start \u2014 only run these suites (e.g. dev set only). Omit to run all."),
28524
28814
  caseIds: z66.array(z66.string()).optional().describe("Optional for start \u2014 only run these cases across the selected suites. Omit to run all cases in those suites."),
28525
- runId: z66.string().optional().describe("Required for status, resume, abort")
28815
+ runId: z66.string().optional().describe("Required for status, resume, abort"),
28816
+ sleepMs: z66.number().int().min(0).max(12e4).optional().describe("Optional for status \u2014 sleep this many ms BEFORE checking the run, to pace polling (e.g. 15000 \u2192 30000 \u2192 60000 \u2192 120000). Omit to check immediately."),
28817
+ wait: z66.boolean().optional().describe("Optional for start \u2014 defaults to true: block synchronously (up to ~150s) and return final results in one call. Set false to return the runId immediately and poll.")
28526
28818
  });
28527
28819
  return tool62(
28528
- async (input, exeConfig) => {
28529
- const tid = tenantId(exeConfig);
28530
- if (!tid) {
28531
- return JSON.stringify({ success: false, error: "No tenant context. Agent must be invoked through gateway." });
28532
- }
28533
- try {
28534
- const store = getStore();
28535
- const svc = getEvalRunService();
28536
- let data;
28537
- switch (input.action) {
28538
- case "start": {
28539
- const ctx = workspaceContext(exeConfig);
28540
- const runId = await svc.startRun(tid, input.projectId, input.suiteIds, input.caseIds, {
28541
- workspaceId: ctx.workspaceId,
28542
- projectId: ctx.projectId
28543
- });
28544
- data = sanitize({ runId, message: "Run started. Poll with run_eval status (backoff: 15s\u219230s\u219260s\u2192max 120s)." });
28545
- break;
28546
- }
28547
- case "status": {
28548
- const run = await store.getRunById(tid, input.runId);
28549
- if (!run) return JSON.stringify({ success: false, error: "Run not found" });
28550
- data = sanitize({ ...run, runnerAlive: svc.isRunning(input.runId) });
28551
- break;
28552
- }
28553
- case "resume": {
28554
- const run = await store.getRunById(tid, input.runId);
28555
- if (!run) return JSON.stringify({ success: false, error: "Run not found" });
28556
- const runnerAlive = svc.isRunning(input.runId);
28557
- if (run.status === "running" && !runnerAlive) {
28558
- await store.updateRunStatus(tid, run.id, {
28559
- status: "failed",
28560
- error: "Gateway restarted \u2014 run orphaned",
28561
- completedAt: /* @__PURE__ */ new Date()
28562
- });
28563
- data = sanitize({
28564
- ...run,
28565
- status: "failed",
28566
- runnerAlive: false,
28567
- message: "Run was orphaned \u2014 marked failed. Start a new run."
28568
- });
28820
+ withToolTimeout(
28821
+ async (input, exeConfig) => {
28822
+ const tid = tenantId(exeConfig);
28823
+ if (!tid) {
28824
+ return JSON.stringify({ success: false, error: "No tenant context. Agent must be invoked through gateway." });
28825
+ }
28826
+ try {
28827
+ const store = getStore();
28828
+ const svc = getEvalRunService();
28829
+ let data;
28830
+ switch (input.action) {
28831
+ case "start": {
28832
+ const ctx = workspaceContext(exeConfig);
28833
+ const runId = await svc.startRun(tid, input.projectId, input.suiteIds, input.caseIds, ctx);
28834
+ if (input.wait === false) {
28835
+ data = sanitize({ runId, message: "Run started. Poll with run_eval status (backoff: 15s\u219230s\u219260s\u2192max 120s)." });
28836
+ break;
28837
+ }
28838
+ let timer;
28839
+ try {
28840
+ await Promise.race([
28841
+ svc.waitForRun(runId).catch(() => {
28842
+ }),
28843
+ new Promise((resolve4) => {
28844
+ timer = setTimeout(resolve4, RUN_EVAL_SYNC_WAIT_MS);
28845
+ })
28846
+ ]);
28847
+ } finally {
28848
+ if (timer) clearTimeout(timer);
28849
+ }
28850
+ const run = await store.getRunById(tid, runId);
28851
+ if (!run) return JSON.stringify({ success: false, error: "Run not found" });
28852
+ if (run.status === "running") {
28853
+ data = sanitize({
28854
+ runId,
28855
+ status: "running",
28856
+ runnerAlive: svc.isRunning(runId),
28857
+ message: `Run not finished within ${RUN_EVAL_SYNC_WAIT_MS / 1e3}s \u2014 poll with run_eval status(runId, sleepMs) (e.g. 15000, doubling up to 120000), or abort with run_eval abort.`
28858
+ });
28859
+ break;
28860
+ }
28861
+ data = sanitize({ synced: true, ...await runWithResults(tid, store, svc, run, svc.isRunning(runId)) });
28569
28862
  break;
28570
28863
  }
28571
- const results = run.status === "completed" ? await store.getResultsByRun(tid, run.id) : void 0;
28572
- if (run.holdout && results) {
28573
- const passed = results.filter((r) => r.pass).length;
28574
- data = sanitize({
28575
- ...run,
28576
- runnerAlive,
28577
- results: {
28578
- holdout: true,
28579
- passedCases: passed,
28580
- failedCases: results.length - passed,
28581
- passRate: results.length > 0 ? passed / results.length : 0,
28582
- totalCases: results.length
28583
- }
28584
- });
28864
+ case "status": {
28865
+ if (input.sleepMs && input.sleepMs > 0) {
28866
+ await new Promise((resolve4) => setTimeout(resolve4, input.sleepMs));
28867
+ }
28868
+ const run = await store.getRunById(tid, input.runId);
28869
+ if (!run) return JSON.stringify({ success: false, error: "Run not found" });
28870
+ data = sanitize({ ...run, runnerAlive: svc.isRunning(input.runId) });
28585
28871
  break;
28586
28872
  }
28587
- data = sanitize({ ...run, runnerAlive, results });
28588
- break;
28589
- }
28590
- case "abort": {
28591
- const run = await store.getRunById(tid, input.runId);
28592
- if (!run) return JSON.stringify({ success: false, error: "Run not found" });
28593
- const ok = await svc.abortRun(input.runId);
28594
- data = sanitize({ aborted: ok });
28595
- break;
28873
+ case "resume": {
28874
+ const run = await store.getRunById(tid, input.runId);
28875
+ if (!run) return JSON.stringify({ success: false, error: "Run not found" });
28876
+ const runnerAlive = svc.isRunning(input.runId);
28877
+ if (run.status === "running" && !runnerAlive) {
28878
+ await store.updateRunStatus(tid, run.id, {
28879
+ status: "failed",
28880
+ error: "Gateway restarted \u2014 run orphaned",
28881
+ completedAt: /* @__PURE__ */ new Date()
28882
+ });
28883
+ data = sanitize({
28884
+ ...run,
28885
+ status: "failed",
28886
+ runnerAlive: false,
28887
+ message: "Run was orphaned \u2014 marked failed. Start a new run."
28888
+ });
28889
+ break;
28890
+ }
28891
+ data = sanitize(await runWithResults(tid, store, svc, run, runnerAlive));
28892
+ break;
28893
+ }
28894
+ case "abort": {
28895
+ const run = await store.getRunById(tid, input.runId);
28896
+ if (!run) return JSON.stringify({ success: false, error: "Run not found" });
28897
+ const ok = await svc.abortRun(input.runId);
28898
+ data = sanitize({ aborted: ok });
28899
+ break;
28900
+ }
28901
+ default:
28902
+ return JSON.stringify({ success: false, error: `Unknown action: ${input.action}` });
28596
28903
  }
28597
- default:
28598
- return JSON.stringify({ success: false, error: `Unknown action: ${input.action}` });
28904
+ return JSON.stringify({ success: true, data });
28905
+ } catch (e) {
28906
+ return JSON.stringify({ success: false, error: e.message });
28599
28907
  }
28600
- return JSON.stringify({ success: true, data });
28601
- } catch (e) {
28602
- return JSON.stringify({ success: false, error: e.message });
28603
28908
  }
28604
- },
28909
+ ),
28605
28910
  {
28606
28911
  name: "run_eval",
28607
- description: `Execute and manage evaluation runs. ASYNCHRONOUS \u2014 may take minutes.
28912
+ description: `Execute and manage evaluation runs.
28608
28913
 
28609
28914
  ACTIONS:
28610
- - start(projectId, suiteIds?, caseIds?) \u2014 begin evaluation (optionally only the listed suites, and/or only the listed cases). Returns runId.
28611
- - status(runId) \u2014 current status + runnerAlive flag:
28612
- \u2022 runnerAlive=true, status=running: keep polling
28915
+ - start(projectId, suiteIds?, caseIds?, wait?) \u2014 begin evaluation (optionally only the listed suites, and/or only the listed cases).
28916
+ wait=true (DEFAULT): SYNCHRONOUS \u2014 blocks up to ~150s and returns the FINAL RESULTS in one call:
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.
28920
+ If the run exceeds 150s: returns { runId, status: "running" } \u2014 NOT an error \u2014 then poll with status/resume until completed.
28921
+ wait=false: fire-and-forget \u2014 returns { runId } immediately; poll with status.
28922
+ - status(runId, sleepMs?) \u2014 sleep sleepMs first (to pace polling), then return current status + runnerAlive flag:
28923
+ \u2022 runnerAlive=true, status=running: keep polling \u2014 call status(runId, sleepMs) with backoff 15s\u219230s\u219260s\u2192max 120s
28613
28924
  \u2022 runnerAlive=false, status=running: ORPHANED \u2014 resume marks it failed automatically; then start a new run
28614
28925
  \u2022 status=completed: get results with read_eval get_run_results or run_eval resume
28615
28926
  - resume(runId) \u2014 reconnect from new conversation. Returns status + results if completed.
28616
28927
  - abort(runId) \u2014 cancel running evaluation.
28617
28928
 
28618
- Polling: start at 15s, double each time, max 120s between polls. Batch reports.`,
28929
+ Polling (only needed with wait=false or after a sync timeout): call status(runId, sleepMs) so the tool sleeps before checking; start at 15s, double each time, max 120s between polls. Batch reports.`,
28619
28930
  schema: schema6
28620
28931
  }
28621
28932
  );
@@ -28911,67 +29222,64 @@ verification choice, then start benchmarking.
28911
29222
 
28912
29223
  ## Task Tracking \u2014 see [[task-tracking]]
28913
29224
 
28914
- **Create the parent task when the task is actually defined** \u2014 after
28915
- Phase 0 clarification is complete and the user confirmed the path
28916
- (fresh vs incremental). Do NOT create tasks during clarification:
28917
- while asking questions (0.0-0.4) you don't know what the task is yet.
28918
- Once the scope is clear (end of 0.5), that is the moment to create:
28919
- manage_task create("Learn [material]", ownerType: "agent"). Then a
28920
- subtask per phase as you start it. Update status to reflect reality \u2014
28921
- never mark a subtask completed while eval fails. Resume interrupted
28922
- 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.
28923
29234
 
28924
29235
  Widgets: call \`load_guidelines\` ONCE before your first \`show_widget\`
28925
29236
  (show_widget hard-requires it), then reuse.
28926
29237
 
28927
29238
  ---
28928
29239
 
28929
- ## Phase 1: Probe (material-dependent)
28930
-
28931
- The probing strategy depends on the material type from Phase 0.0:
28932
-
28933
- **User-description material**: the requirements come from the
28934
- conversation itself \u2014 no probing needed. Extract the agent's goal,
28935
- inputs, outputs, and constraints from what the user described. Go
28936
- straight to design.
28937
-
28938
- **Document material** (PDF / spec / manual):
28939
- If the engine was chosen in Phase 0 (0.3 \u2460-\u2464): skip the comparison \u2014
28940
- parse directly with \`parse_document\` using the chosen engine
28941
- (file_path, engine, output_path per file).
28942
- Otherwise: run the document-parser-benchmark subagent via \`task\` on each file.
28943
- Collect engine scores, parsed output (via \`read_file\`), and feature signatures.
28944
-
28945
- **Engine selection IS distilled knowledge, not just setup.** For document
28946
- material, the benchmark answers "which engine parses THIS document (or
28947
- this class of document) best?" \u2014 that answer is knowledge that drives
28948
- the whole rest of the run:
28949
- - **Builds the agent**: the chosen engine's \`parse_document\` goes into
28950
- the production agent's middleware/tools.
28951
- - **Designs the tests**: the chosen engine's parsed output becomes the
28952
- baseline input for eval cases \u2014 cases feed parsed output to the agent
28953
- and assert correct extraction from it.
28954
- - **Seeds the skill**: the feature signature (tables? scans? mixed
28955
- zh/en?) plus the winning engine becomes a skill note ("for PO PDFs
28956
- with tables, use textin") reusable for future similar documents.
28957
- So when the user wants an agent whose purpose is document PARSING (not
28958
- extraction), the workflow is the same: benchmark to learn the engine
28959
- choice, then build the agent around that engine and test against its
28960
- output. Do NOT treat parsing as a pure tool-assembly task \u2014 the engine
28961
- choice is unknown knowledge until probed.
28962
-
28963
- **API spec material**: read the spec directly \u2014 no parsing engine needed.
28964
- Extract endpoints, schemas, request/response examples from the text.
28965
-
28966
- **Conversation material**: extract the workflow, decisions, and corrections
28967
- from the conversation context \u2014 no parsing engine needed.
28968
-
28969
- **Spreadsheet material**: parse cells directly \u2014 structured data needs
28970
- no engine comparison.
28971
- If verification will happen (0.2 \u2460 or \u2461): concurrently, \`list_agents\` to
28972
- discover existing agents with relevant capabilities (see \xA75).
28973
- For \u2460, look for agents with data-access tools (SQL / API). For \u2461, look
28974
- 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.
28975
29283
 
28976
29284
  ---
28977
29285
 
@@ -28986,17 +29294,21 @@ candidate and assess (Validation Agent Design \xA70) \u2014 state which are
28986
29294
  usable and which are not, with reasons. For \u2460, the executor needs data
28987
29295
  tools + independence. For \u2461, independence only. If no candidate fits,
28988
29296
  plan to build one via \xA75.
28989
- 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
28990
29301
  \`ask_user_to_clarify\` NOW:
28991
29302
  {
28992
29303
  "questions": [{
28993
- "question": "Confirm the recommendation?",
29304
+ "question": "Confirm the recommended path?",
28994
29305
  "options": ["Confirm", "Adjust"],
28995
29306
  "type": "single",
28996
29307
  "required": true
28997
29308
  }]
28998
29309
  }
28999
- 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.
29000
29312
 
29001
29313
  ---
29002
29314
 
@@ -29064,9 +29376,53 @@ user-description material this IS the core phase; for material-based
29064
29376
  learning it designs the agent that runs the learned skill. Agent
29065
29377
  metadata (verified/version/source) must be set on creation.
29066
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
+
29067
29421
  ## Phase 3: Create Skills
29068
29422
 
29069
- 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.
29070
29426
  Show the skill content in text first, then MUST call
29071
29427
  \`ask_user_to_clarify\` NOW per skill:
29072
29428
  {
@@ -29295,7 +29651,8 @@ Both modes use the same agent type \u2014 skill only, no domain tools:
29295
29651
  verified: "unverified", # upgraded after eval passes
29296
29652
  version: "1.0", # bump on each update_agent
29297
29653
  source: "{material name}", # provenance
29298
- skill: "skill-name"
29654
+ skill: "skill-name",
29655
+ role: "orchestrator" | "sub-agent" # only for parent+subAgents structure
29299
29656
  }
29300
29657
  )
29301
29658
 
@@ -29359,7 +29716,8 @@ This learning loop adds its own scenario rules:
29359
29716
  - Business usability (output reaches the goal's "usable state")
29360
29717
  - Consumer fit (format/contract satisfies who uses the result)
29361
29718
  contentAssertion must encode the usable state from the goal model
29362
- (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.
29363
29721
 
29364
29722
  1. One suite per skill per source: cases test "can this skill do it" \u2014
29365
29723
  never mix skills in one suite
@@ -29391,12 +29749,23 @@ Run evaluation, fix loop, hold-out validation, trust upgrade. See
29391
29749
  [[eval-verify]] for the full workflow. The eval-design-tests and
29392
29750
  eval-run-and-govern skills cover case design and run governance.
29393
29751
 
29752
+ **One eval project per agent**, named \`eval-{agent-id}\` \u2014 every agent
29753
+ built by this workflow gets its own eval project (see eval-verify
29754
+ Setup). Orchestrator + subAgents \u2192 one eval project per sub-agent plus
29755
+ one integration eval for the parent.
29756
+
29394
29757
  Learning-specific suite guidance:
29395
29758
  - 0.2 \u2461 \u2192 {skill}-user-sample; samples \u22658 \u2192 also {skill}-validation
29396
29759
  - 0.2 \u2460 \u2192 {skill}-api-verified (single step, \xA74.1)
29397
29760
  - User-description material: {skill}-requirement-derived \u2014 cases from
29398
29761
  user's described requirements
29399
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
+
29400
29769
  [[completion-gate]] applies \u2014 eval must pass before declaring done.
29401
29770
 
29402
29771
  ## Phase 5: Retrospective
@@ -29429,11 +29798,12 @@ base is wanted (it is extra work beyond the skill).
29429
29798
  ## Fallback
29430
29799
 
29431
29800
  - All engines fail \u2192 suggest text version or different format.
29432
- - No eval agent \u2192 judge-only scoring, or build a production agent via \xA75
29433
- (generic or API-verified variant, thin prompt) \u2014 never reuse an agent
29434
- whose knowledge derives from the learning material.
29435
- Judge-only scoring does NOT unlock machine-confirmed \u2014 trust caps at
29436
- human-reviewed; say so explicitly.
29801
+ - Eval runtime unavailable (no eval agent / service down) \u2192 still
29802
+ DESIGN and CREATE the eval project with test cases (every agent MUST
29803
+ have an eval \u2014 no skip). If the eval cannot RUN now, deliver with
29804
+ trust capped at human-reviewed and state: "Test framework created;
29805
+ run the evaluation once the eval service is available." Judge-only
29806
+ scoring (when run) does NOT unlock machine-confirmed.
29437
29807
  - No test files \u2192 user-described scenarios as contentAssertion.
29438
29808
  - run_eval orphaned (resume shows runnerAlive=false) \u2192 \`run_eval resume(runId)\`
29439
29809
  marks it failed automatically; then \`run_eval start(projectId)\` to restart.