@archmax-ai/harness 0.0.0
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/LICENSE +21 -0
- package/README.md +219 -0
- package/dist/agent.d.ts +174 -0
- package/dist/agent.js +140 -0
- package/dist/agent.js.map +1 -0
- package/dist/assembly/compose.d.ts +170 -0
- package/dist/assembly/compose.js +391 -0
- package/dist/assembly/compose.js.map +1 -0
- package/dist/assembly/delegation-registry.d.ts +5 -0
- package/dist/assembly/delegation-registry.js +118 -0
- package/dist/assembly/delegation-registry.js.map +1 -0
- package/dist/assembly/index.d.ts +192 -0
- package/dist/assembly/index.js +265 -0
- package/dist/assembly/index.js.map +1 -0
- package/dist/assembly/plain.d.ts +7 -0
- package/dist/assembly/plain.js +41 -0
- package/dist/assembly/plain.js.map +1 -0
- package/dist/authoring-skill/archmax-harness/README.md +76 -0
- package/dist/authoring-skill/archmax-harness/SKILL.md +241 -0
- package/dist/authoring-skill/archmax-harness/references/backend-integration.md +626 -0
- package/dist/authoring-skill/archmax-harness/references/cases.md +41 -0
- package/dist/authoring-skill/archmax-harness/references/diagnostics.md +51 -0
- package/dist/authoring-skill/archmax-harness/references/hook-and-test-scripts.md +1070 -0
- package/dist/authoring-skill/archmax-harness/references/hooks-and-scripts.md +82 -0
- package/dist/authoring-skill/archmax-harness/references/wiring.md +101 -0
- package/dist/authoring-skill/archmax-harness/references/workflow-schema.md +1631 -0
- package/dist/authoring-skill/archmax-harness/references/workflow-yaml.md +241 -0
- package/dist/behaviour/support.d.ts +232 -0
- package/dist/behaviour/support.js +339 -0
- package/dist/behaviour/support.js.map +1 -0
- package/dist/cli/banner.d.ts +11 -0
- package/dist/cli/banner.js +29 -0
- package/dist/cli/banner.js.map +1 -0
- package/dist/cli/bootstrap.d.ts +33 -0
- package/dist/cli/bootstrap.js +153 -0
- package/dist/cli/bootstrap.js.map +1 -0
- package/dist/cli/command.d.ts +39 -0
- package/dist/cli/command.js +97 -0
- package/dist/cli/command.js.map +1 -0
- package/dist/cli/state-flow.d.ts +46 -0
- package/dist/cli/state-flow.js +268 -0
- package/dist/cli/state-flow.js.map +1 -0
- package/dist/cli/style.d.ts +41 -0
- package/dist/cli/style.js +64 -0
- package/dist/cli/style.js.map +1 -0
- package/dist/cli/turn.d.ts +41 -0
- package/dist/cli/turn.js +161 -0
- package/dist/cli/turn.js.map +1 -0
- package/dist/cli.d.ts +10 -0
- package/dist/cli.js +426 -0
- package/dist/cli.js.map +1 -0
- package/dist/core/code-description.d.ts +22 -0
- package/dist/core/code-description.js +38 -0
- package/dist/core/code-description.js.map +1 -0
- package/dist/core/deepagents.d.ts +183 -0
- package/dist/core/deepagents.js +128 -0
- package/dist/core/deepagents.js.map +1 -0
- package/dist/core/events.d.ts +350 -0
- package/dist/core/events.js +205 -0
- package/dist/core/events.js.map +1 -0
- package/dist/core/frontmatter.d.ts +15 -0
- package/dist/core/frontmatter.js +32 -0
- package/dist/core/frontmatter.js.map +1 -0
- package/dist/core/listing-redact.d.ts +37 -0
- package/dist/core/listing-redact.js +88 -0
- package/dist/core/listing-redact.js.map +1 -0
- package/dist/core/match.d.ts +26 -0
- package/dist/core/match.js +75 -0
- package/dist/core/match.js.map +1 -0
- package/dist/core/message-readers.d.ts +84 -0
- package/dist/core/message-readers.js +159 -0
- package/dist/core/message-readers.js.map +1 -0
- package/dist/core/messages.d.ts +49 -0
- package/dist/core/messages.js +85 -0
- package/dist/core/messages.js.map +1 -0
- package/dist/core/mount-prompt.d.ts +28 -0
- package/dist/core/mount-prompt.js +55 -0
- package/dist/core/mount-prompt.js.map +1 -0
- package/dist/core/mount-redact.d.ts +28 -0
- package/dist/core/mount-redact.js +32 -0
- package/dist/core/mount-redact.js.map +1 -0
- package/dist/core/mounts.d.ts +88 -0
- package/dist/core/mounts.js +147 -0
- package/dist/core/mounts.js.map +1 -0
- package/dist/core/path-mapping.d.ts +52 -0
- package/dist/core/path-mapping.js +113 -0
- package/dist/core/path-mapping.js.map +1 -0
- package/dist/core/platform-prompt.generated.d.ts +2 -0
- package/dist/core/platform-prompt.generated.js +4 -0
- package/dist/core/platform-prompt.generated.js.map +1 -0
- package/dist/core/prompt.d.ts +42 -0
- package/dist/core/prompt.js +75 -0
- package/dist/core/prompt.js.map +1 -0
- package/dist/core/session-id.d.ts +38 -0
- package/dist/core/session-id.js +61 -0
- package/dist/core/session-id.js.map +1 -0
- package/dist/core/session-store.d.ts +85 -0
- package/dist/core/session-store.js +149 -0
- package/dist/core/session-store.js.map +1 -0
- package/dist/core/session-zone.d.ts +54 -0
- package/dist/core/session-zone.js +82 -0
- package/dist/core/session-zone.js.map +1 -0
- package/dist/core/skill-prompt.d.ts +31 -0
- package/dist/core/skill-prompt.js +54 -0
- package/dist/core/skill-prompt.js.map +1 -0
- package/dist/core/skill-redact.d.ts +15 -0
- package/dist/core/skill-redact.js +27 -0
- package/dist/core/skill-redact.js.map +1 -0
- package/dist/core/skills.d.ts +172 -0
- package/dist/core/skills.js +214 -0
- package/dist/core/skills.js.map +1 -0
- package/dist/core/tool-mocks.d.ts +23 -0
- package/dist/core/tool-mocks.js +39 -0
- package/dist/core/tool-mocks.js.map +1 -0
- package/dist/core/tool-telemetry.d.ts +16 -0
- package/dist/core/tool-telemetry.js +40 -0
- package/dist/core/tool-telemetry.js.map +1 -0
- package/dist/core/usage.d.ts +114 -0
- package/dist/core/usage.js +226 -0
- package/dist/core/usage.js.map +1 -0
- package/dist/core/workspace-context.d.ts +136 -0
- package/dist/core/workspace-context.js +178 -0
- package/dist/core/workspace-context.js.map +1 -0
- package/dist/core/workspace-prompt.d.ts +7 -0
- package/dist/core/workspace-prompt.js +102 -0
- package/dist/core/workspace-prompt.js.map +1 -0
- package/dist/core/workspace-router.d.ts +61 -0
- package/dist/core/workspace-router.js +145 -0
- package/dist/core/workspace-router.js.map +1 -0
- package/dist/core/workspace.d.ts +85 -0
- package/dist/core/workspace.js +193 -0
- package/dist/core/workspace.js.map +1 -0
- package/dist/core/zones.d.ts +181 -0
- package/dist/core/zones.js +202 -0
- package/dist/core/zones.js.map +1 -0
- package/dist/env.d.ts +99 -0
- package/dist/env.js +271 -0
- package/dist/env.js.map +1 -0
- package/dist/index.d.ts +265 -0
- package/dist/index.js +197 -0
- package/dist/index.js.map +1 -0
- package/dist/kernel/kernel.d.ts +192 -0
- package/dist/kernel/kernel.js +751 -0
- package/dist/kernel/kernel.js.map +1 -0
- package/dist/lifecycle/hook-shape.d.ts +44 -0
- package/dist/lifecycle/hook-shape.js +66 -0
- package/dist/lifecycle/hook-shape.js.map +1 -0
- package/dist/lifecycle/runner.d.ts +177 -0
- package/dist/lifecycle/runner.js +372 -0
- package/dist/lifecycle/runner.js.map +1 -0
- package/dist/lifecycle/transcript.d.ts +27 -0
- package/dist/lifecycle/transcript.js +43 -0
- package/dist/lifecycle/transcript.js.map +1 -0
- package/dist/machine/allow.d.ts +65 -0
- package/dist/machine/allow.js +107 -0
- package/dist/machine/allow.js.map +1 -0
- package/dist/machine/delegation.d.ts +9 -0
- package/dist/machine/delegation.js +10 -0
- package/dist/machine/delegation.js.map +1 -0
- package/dist/machine/diagnostic.d.ts +14 -0
- package/dist/machine/diagnostic.js +2 -0
- package/dist/machine/diagnostic.js.map +1 -0
- package/dist/machine/lint-spec.d.ts +4 -0
- package/dist/machine/lint-spec.js +266 -0
- package/dist/machine/lint-spec.js.map +1 -0
- package/dist/machine/load-spec.d.ts +90 -0
- package/dist/machine/load-spec.js +120 -0
- package/dist/machine/load-spec.js.map +1 -0
- package/dist/machine/machine.d.ts +273 -0
- package/dist/machine/machine.js +551 -0
- package/dist/machine/machine.js.map +1 -0
- package/dist/machine/mount-grants.d.ts +25 -0
- package/dist/machine/mount-grants.js +24 -0
- package/dist/machine/mount-grants.js.map +1 -0
- package/dist/machine/slug.d.ts +14 -0
- package/dist/machine/slug.js +17 -0
- package/dist/machine/slug.js.map +1 -0
- package/dist/machine/spec-schema.d.ts +662 -0
- package/dist/machine/spec-schema.js +717 -0
- package/dist/machine/spec-schema.js.map +1 -0
- package/dist/machine/tool-names.d.ts +98 -0
- package/dist/machine/tool-names.js +144 -0
- package/dist/machine/tool-names.js.map +1 -0
- package/dist/machine/triggers.d.ts +114 -0
- package/dist/machine/triggers.js +165 -0
- package/dist/machine/triggers.js.map +1 -0
- package/dist/machine/types.d.ts +186 -0
- package/dist/machine/types.js +12 -0
- package/dist/machine/types.js.map +1 -0
- package/dist/machine/validate-spec.d.ts +42 -0
- package/dist/machine/validate-spec.js +29 -0
- package/dist/machine/validate-spec.js.map +1 -0
- package/dist/machine/variables.d.ts +196 -0
- package/dist/machine/variables.js +415 -0
- package/dist/machine/variables.js.map +1 -0
- package/dist/public/cli.d.ts +15 -0
- package/dist/public/cli.js +14 -0
- package/dist/public/cli.js.map +1 -0
- package/dist/public/messages.d.ts +33 -0
- package/dist/public/messages.js +33 -0
- package/dist/public/messages.js.map +1 -0
- package/dist/public/sandbox.d.ts +99 -0
- package/dist/public/sandbox.js +32 -0
- package/dist/public/sandbox.js.map +1 -0
- package/dist/public/spec.d.ts +147 -0
- package/dist/public/spec.js +149 -0
- package/dist/public/spec.js.map +1 -0
- package/dist/public/testing.d.ts +71 -0
- package/dist/public/testing.js +39 -0
- package/dist/public/testing.js.map +1 -0
- package/dist/rubrics/rubrics.d.ts +135 -0
- package/dist/rubrics/rubrics.js +145 -0
- package/dist/rubrics/rubrics.js.map +1 -0
- package/dist/runtime/contract.d.ts +73 -0
- package/dist/runtime/contract.js +96 -0
- package/dist/runtime/contract.js.map +1 -0
- package/dist/sandbox/assets/parts/core.js +22 -0
- package/dist/sandbox/assets/parts/lifecycle-hook.js +47 -0
- package/dist/sandbox/bridge.d.ts +16 -0
- package/dist/sandbox/bridge.js +95 -0
- package/dist/sandbox/bridge.js.map +1 -0
- package/dist/sandbox/executor.d.ts +46 -0
- package/dist/sandbox/executor.js +119 -0
- package/dist/sandbox/executor.js.map +1 -0
- package/dist/sandbox/imports.d.ts +33 -0
- package/dist/sandbox/imports.js +67 -0
- package/dist/sandbox/imports.js.map +1 -0
- package/dist/sandbox/prelude.d.ts +12 -0
- package/dist/sandbox/prelude.js +51 -0
- package/dist/sandbox/prelude.js.map +1 -0
- package/dist/sandbox/ptc-gateway.d.ts +104 -0
- package/dist/sandbox/ptc-gateway.js +167 -0
- package/dist/sandbox/ptc-gateway.js.map +1 -0
- package/dist/sandbox/runtime.d.ts +59 -0
- package/dist/sandbox/runtime.js +51 -0
- package/dist/sandbox/runtime.js.map +1 -0
- package/dist/sandbox/tools.d.ts +34 -0
- package/dist/sandbox/tools.js +147 -0
- package/dist/sandbox/tools.js.map +1 -0
- package/dist/sessions/operations.d.ts +79 -0
- package/dist/sessions/operations.js +121 -0
- package/dist/sessions/operations.js.map +1 -0
- package/dist/sessions/resolve.d.ts +97 -0
- package/dist/sessions/resolve.js +145 -0
- package/dist/sessions/resolve.js.map +1 -0
- package/dist/sessions/resume.d.ts +242 -0
- package/dist/sessions/resume.js +288 -0
- package/dist/sessions/resume.js.map +1 -0
- package/dist/sessions/scope.d.ts +99 -0
- package/dist/sessions/scope.js +145 -0
- package/dist/sessions/scope.js.map +1 -0
- package/dist/sessions/summary.d.ts +65 -0
- package/dist/sessions/summary.js +56 -0
- package/dist/sessions/summary.js.map +1 -0
- package/dist/testing/assertions.d.ts +30 -0
- package/dist/testing/assertions.js +231 -0
- package/dist/testing/assertions.js.map +1 -0
- package/dist/testing/case-document.d.ts +141 -0
- package/dist/testing/case-document.js +51 -0
- package/dist/testing/case-document.js.map +1 -0
- package/dist/testing/case-schema.d.ts +29 -0
- package/dist/testing/case-schema.js +519 -0
- package/dist/testing/case-schema.js.map +1 -0
- package/dist/testing/discovery.d.ts +10 -0
- package/dist/testing/discovery.js +24 -0
- package/dist/testing/discovery.js.map +1 -0
- package/dist/testing/driver.d.ts +78 -0
- package/dist/testing/driver.js +117 -0
- package/dist/testing/driver.js.map +1 -0
- package/dist/testing/grade.d.ts +116 -0
- package/dist/testing/grade.js +261 -0
- package/dist/testing/grade.js.map +1 -0
- package/dist/testing/interpreter.d.ts +55 -0
- package/dist/testing/interpreter.js +103 -0
- package/dist/testing/interpreter.js.map +1 -0
- package/dist/testing/mock-middleware.d.ts +5 -0
- package/dist/testing/mock-middleware.js +33 -0
- package/dist/testing/mock-middleware.js.map +1 -0
- package/dist/testing/runner.d.ts +205 -0
- package/dist/testing/runner.js +266 -0
- package/dist/testing/runner.js.map +1 -0
- package/dist/testing/target.d.ts +27 -0
- package/dist/testing/target.js +21 -0
- package/dist/testing/target.js.map +1 -0
- package/dist/validate/validate.d.ts +41 -0
- package/dist/validate/validate.js +669 -0
- package/dist/validate/validate.js.map +1 -0
- package/dist/workflow/agent-tools.d.ts +10 -0
- package/dist/workflow/agent-tools.js +10 -0
- package/dist/workflow/agent-tools.js.map +1 -0
- package/dist/workflow/checkpointer.d.ts +52 -0
- package/dist/workflow/checkpointer.js +200 -0
- package/dist/workflow/checkpointer.js.map +1 -0
- package/dist/workflow/control-tools.d.ts +174 -0
- package/dist/workflow/control-tools.js +468 -0
- package/dist/workflow/control-tools.js.map +1 -0
- package/dist/workflow/governance.d.ts +104 -0
- package/dist/workflow/governance.js +546 -0
- package/dist/workflow/governance.js.map +1 -0
- package/dist/workflow/middleware.d.ts +85 -0
- package/dist/workflow/middleware.js +283 -0
- package/dist/workflow/middleware.js.map +1 -0
- package/dist/workflow/on-error.d.ts +25 -0
- package/dist/workflow/on-error.js +58 -0
- package/dist/workflow/on-error.js.map +1 -0
- package/dist/workflow/parks.d.ts +46 -0
- package/dist/workflow/parks.js +513 -0
- package/dist/workflow/parks.js.map +1 -0
- package/dist/workflow/paths.d.ts +67 -0
- package/dist/workflow/paths.js +108 -0
- package/dist/workflow/paths.js.map +1 -0
- package/dist/workflow/prompt-cache.d.ts +70 -0
- package/dist/workflow/prompt-cache.js +103 -0
- package/dist/workflow/prompt-cache.js.map +1 -0
- package/dist/workflow/prompt-pruning.d.ts +20 -0
- package/dist/workflow/prompt-pruning.js +83 -0
- package/dist/workflow/prompt-pruning.js.map +1 -0
- package/dist/workflow/render-prompt.d.ts +45 -0
- package/dist/workflow/render-prompt.js +140 -0
- package/dist/workflow/render-prompt.js.map +1 -0
- package/dist/workflow/runtime-config.d.ts +7 -0
- package/dist/workflow/runtime-config.js +9 -0
- package/dist/workflow/runtime-config.js.map +1 -0
- package/dist/workflow/session-artifacts.d.ts +90 -0
- package/dist/workflow/session-artifacts.js +134 -0
- package/dist/workflow/session-artifacts.js.map +1 -0
- package/dist/workflow/session-view.d.ts +31 -0
- package/dist/workflow/session-view.js +94 -0
- package/dist/workflow/session-view.js.map +1 -0
- package/dist/workflow/snapshot.d.ts +14 -0
- package/dist/workflow/snapshot.js +97 -0
- package/dist/workflow/snapshot.js.map +1 -0
- package/dist/workflow/state.d.ts +347 -0
- package/dist/workflow/state.js +322 -0
- package/dist/workflow/state.js.map +1 -0
- package/dist/workflow/sub-workflow.d.ts +174 -0
- package/dist/workflow/sub-workflow.js +392 -0
- package/dist/workflow/sub-workflow.js.map +1 -0
- package/dist/workflow/tool-service.d.ts +48 -0
- package/dist/workflow/tool-service.js +291 -0
- package/dist/workflow/tool-service.js.map +1 -0
- package/dist/workflow/turn-boundary.d.ts +39 -0
- package/dist/workflow/turn-boundary.js +128 -0
- package/dist/workflow/turn-boundary.js.map +1 -0
- package/dist/workflow/turn-runner.d.ts +40 -0
- package/dist/workflow/turn-runner.js +122 -0
- package/dist/workflow/turn-runner.js.map +1 -0
- package/dist/workflow/workflow-tools.d.ts +46 -0
- package/dist/workflow/workflow-tools.js +90 -0
- package/dist/workflow/workflow-tools.js.map +1 -0
- package/package.json +119 -0
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The host-side session engine cases drive the agent through. One engine per
|
|
3
|
+
* case; it owns the case's session on the agent under test, its declared start
|
|
4
|
+
* conditions (trigger, variables, seeded workspace files), and its latest turn
|
|
5
|
+
* view. A thin caller of `agent.workflow.send`: where a message lands — a turn,
|
|
6
|
+
* a reply to a session a person holds — is the session's business, decided by
|
|
7
|
+
* the runtime exactly as it is for a host.
|
|
8
|
+
*/
|
|
9
|
+
import type { GovernedAgent } from "../agent.js";
|
|
10
|
+
import { type SessionView } from "../workflow/session-view.js";
|
|
11
|
+
import type { ToolMockSpec } from "../core/tool-mocks.js";
|
|
12
|
+
/** The session name steps without a `session:` key drive. */
|
|
13
|
+
export declare const DEFAULT_SESSION_ID = "default";
|
|
14
|
+
export interface DriveSession {
|
|
15
|
+
/** The session's name as the case declares it (`session:` on a step). */
|
|
16
|
+
name: string;
|
|
17
|
+
/**
|
|
18
|
+
* The id the runtime addresses this session by — namespaced per case, since one
|
|
19
|
+
* agent serves every case. One id: the runtime has no separate session beneath it.
|
|
20
|
+
*/
|
|
21
|
+
sessionId: string;
|
|
22
|
+
/** The validated trigger every driven turn of this session starts from. */
|
|
23
|
+
trigger?: {
|
|
24
|
+
id: string;
|
|
25
|
+
};
|
|
26
|
+
/** Variables seeded into every driven turn of this session, locked by the runtime. */
|
|
27
|
+
variables?: Record<string, unknown>;
|
|
28
|
+
/** The session's latest turn view (empty until it first drives). */
|
|
29
|
+
lastView: SessionView;
|
|
30
|
+
}
|
|
31
|
+
export interface SessionStartConditions {
|
|
32
|
+
trigger?: {
|
|
33
|
+
id: string;
|
|
34
|
+
};
|
|
35
|
+
/**
|
|
36
|
+
* Variables seeded for this session, supplied per turn rather than at
|
|
37
|
+
* assembly: one agent serves every case of a suite, so a case's seeds cannot
|
|
38
|
+
* ride the assembly-time `variables` seeds.
|
|
39
|
+
*/
|
|
40
|
+
variables?: Record<string, unknown>;
|
|
41
|
+
/** Workspace files seeded into the session before any turn. */
|
|
42
|
+
files?: Record<string, unknown>;
|
|
43
|
+
}
|
|
44
|
+
/**
|
|
45
|
+
* The refusal for a sub-workflow mock against a target that cannot delegate.
|
|
46
|
+
* Separate from the interception refusal because the fix is different: this one
|
|
47
|
+
* is not about middleware but about the target being workflow-governed at all.
|
|
48
|
+
*/
|
|
49
|
+
export declare function unDelegatableMocksMessage(): string;
|
|
50
|
+
/** The refusal for mocks a target cannot intercept (single definition). */
|
|
51
|
+
export declare function unInterceptableMocksMessage(mockCount: number): string;
|
|
52
|
+
export interface SessionEngine {
|
|
53
|
+
/** Open a session, validating and applying its declared start conditions. */
|
|
54
|
+
open(sessionId: string, conditions?: SessionStartConditions): Promise<DriveSession>;
|
|
55
|
+
/** Drive one user turn on a session under its declared trigger. */
|
|
56
|
+
send(sessionId: string, message: string, toolMocks?: ToolMockSpec[]): Promise<SessionView>;
|
|
57
|
+
/** Resume a session parked at a human state with the person's chosen edge. */
|
|
58
|
+
decide(sessionId: string, to: string, comment?: string): Promise<SessionView>;
|
|
59
|
+
/**
|
|
60
|
+
* Resume a session parked awaiting an event with a delivered firing; the
|
|
61
|
+
* session continues in the state it parked in. Fails the case the way
|
|
62
|
+
* production fails the host — the session was not parked — rather than
|
|
63
|
+
* silently starting a new turn.
|
|
64
|
+
*/
|
|
65
|
+
deliver(sessionId: string, trigger: string, variables?: Record<string, unknown>): Promise<SessionView>;
|
|
66
|
+
}
|
|
67
|
+
/**
|
|
68
|
+
* Create the per-case session engine. The default session runs on
|
|
69
|
+
* `defaultSessionId` (caller-nameable via `sessionIdForCase`); every other
|
|
70
|
+
* session's id derives from it, so naming the default names the whole case and
|
|
71
|
+
* no session is a timestamped orphan.
|
|
72
|
+
*/
|
|
73
|
+
export declare function createSessionEngine(opts: {
|
|
74
|
+
agent: GovernedAgent;
|
|
75
|
+
defaultSessionId: string;
|
|
76
|
+
/** Aborts every run this engine drives — the case's wall-clock budget. */
|
|
77
|
+
signal?: AbortSignal;
|
|
78
|
+
}): SessionEngine;
|
|
@@ -0,0 +1,117 @@
|
|
|
1
|
+
import { messagesToSessionView } from "../workflow/session-view.js";
|
|
2
|
+
/** The session name steps without a `session:` key drive. */
|
|
3
|
+
export const DEFAULT_SESSION_ID = "default";
|
|
4
|
+
/**
|
|
5
|
+
* Validate-then-seed-then-store for a session's declared start conditions.
|
|
6
|
+
* Fails closed before any turn: an unknown trigger id surfaces
|
|
7
|
+
* `UnknownTriggerError` with the declared triggers, and declared files against
|
|
8
|
+
* a target without a `sessions.seed` handle are refused rather than skipped.
|
|
9
|
+
*/
|
|
10
|
+
async function applyStartConditions(agent, session, input) {
|
|
11
|
+
if (input.trigger) {
|
|
12
|
+
agent.workflow.resolveTrigger(input.trigger);
|
|
13
|
+
session.trigger = input.trigger;
|
|
14
|
+
}
|
|
15
|
+
if (input.variables && Object.keys(input.variables).length > 0) {
|
|
16
|
+
session.variables = { ...input.variables };
|
|
17
|
+
}
|
|
18
|
+
const files = input.files ?? {};
|
|
19
|
+
if (Object.keys(files).length > 0) {
|
|
20
|
+
if (typeof agent.sessions?.seed !== "function") {
|
|
21
|
+
throw new Error(`this case declares ${Object.keys(files).length} workspace file(s), but the target ` +
|
|
22
|
+
`runtime's session handles expose no 'seed' operation, so nothing can be placed in the ` +
|
|
23
|
+
`session workspace before the agent runs. Supply a target whose sessions handle can seed ` +
|
|
24
|
+
`(createAgent provides one).`);
|
|
25
|
+
}
|
|
26
|
+
await agent.sessions.seed(session.sessionId, files);
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
/**
|
|
30
|
+
* The refusal for a sub-workflow mock against a target that cannot delegate.
|
|
31
|
+
* Separate from the interception refusal because the fix is different: this one
|
|
32
|
+
* is not about middleware but about the target being workflow-governed at all.
|
|
33
|
+
*/
|
|
34
|
+
export function unDelegatableMocksMessage() {
|
|
35
|
+
return (`this case mocks a sub-workflow, but the target agent is not workflow-governed ` +
|
|
36
|
+
`(agent.workflow is undefined) — it cannot dispatch sub-workflows, so the mock would ` +
|
|
37
|
+
`never be consulted. Run the case against a workflow-governed target.`);
|
|
38
|
+
}
|
|
39
|
+
/** The refusal for mocks a target cannot intercept (single definition). */
|
|
40
|
+
export function unInterceptableMocksMessage(mockCount) {
|
|
41
|
+
return (`this case declares ${mockCount} tool mock(s), but the target agent ` +
|
|
42
|
+
`cannot intercept agent-initiated tool calls (interception would be partial: ` +
|
|
43
|
+
`scripts' PTC calls mocked, the agent's own calls real). Wire ` +
|
|
44
|
+
`createToolMockMiddleware() into the target's middleware, or set ` +
|
|
45
|
+
`toolMocks on a target that intercepts by other means.`);
|
|
46
|
+
}
|
|
47
|
+
/** The view of one settled {@link Outcome}, as assertions read it. */
|
|
48
|
+
function viewOf(session, outcome) {
|
|
49
|
+
const view = messagesToSessionView(outcome.messages, session.name, outcome.auditTrail, outcome.variables);
|
|
50
|
+
if (outcome.kind === "parked") {
|
|
51
|
+
view.parked = true;
|
|
52
|
+
if (outcome.parkedChannel)
|
|
53
|
+
view.parkedChannel = outcome.parkedChannel;
|
|
54
|
+
if (outcome.state)
|
|
55
|
+
view.node = outcome.state;
|
|
56
|
+
}
|
|
57
|
+
return view;
|
|
58
|
+
}
|
|
59
|
+
/**
|
|
60
|
+
* Create the per-case session engine. The default session runs on
|
|
61
|
+
* `defaultSessionId` (caller-nameable via `sessionIdForCase`); every other
|
|
62
|
+
* session's id derives from it, so naming the default names the whole case and
|
|
63
|
+
* no session is a timestamped orphan.
|
|
64
|
+
*/
|
|
65
|
+
export function createSessionEngine(opts) {
|
|
66
|
+
const { agent, defaultSessionId } = opts;
|
|
67
|
+
const runConfig = opts.signal ? { signal: opts.signal } : {};
|
|
68
|
+
const sessions = new Map();
|
|
69
|
+
function get(sessionId) {
|
|
70
|
+
const session = sessions.get(sessionId);
|
|
71
|
+
if (!session) {
|
|
72
|
+
throw new Error(`unknown session '${sessionId}' (opened: ${[...sessions.keys()].join(", ") || "none"})`);
|
|
73
|
+
}
|
|
74
|
+
return session;
|
|
75
|
+
}
|
|
76
|
+
async function settle(session, outcome) {
|
|
77
|
+
const view = viewOf(session, await outcome);
|
|
78
|
+
session.lastView = view;
|
|
79
|
+
return view;
|
|
80
|
+
}
|
|
81
|
+
return {
|
|
82
|
+
async open(name, conditions = {}) {
|
|
83
|
+
if (sessions.has(name))
|
|
84
|
+
throw new Error(`session '${name}' is already open`);
|
|
85
|
+
const session = {
|
|
86
|
+
name,
|
|
87
|
+
sessionId: name === DEFAULT_SESSION_ID ? defaultSessionId : `${defaultSessionId}-${name}`,
|
|
88
|
+
lastView: messagesToSessionView([], name),
|
|
89
|
+
};
|
|
90
|
+
await applyStartConditions(agent, session, conditions);
|
|
91
|
+
sessions.set(name, session);
|
|
92
|
+
return session;
|
|
93
|
+
},
|
|
94
|
+
async send(sessionId, message, toolMocks = []) {
|
|
95
|
+
const session = get(sessionId);
|
|
96
|
+
// Every turn of a session runs under the session's declared trigger and
|
|
97
|
+
// seeds — consistency by construction: a send step carries neither, so a
|
|
98
|
+
// follow-up message cannot restart the session at a different start state.
|
|
99
|
+
// Mocks ride the config, the one per-invocation channel; the runtime
|
|
100
|
+
// decides whether this is a turn or a reply to a session a person holds.
|
|
101
|
+
return settle(session, agent.workflow.send(session.sessionId, {
|
|
102
|
+
message,
|
|
103
|
+
...(session.trigger ? { trigger: session.trigger } : {}),
|
|
104
|
+
...(session.variables ? { variables: session.variables } : {}),
|
|
105
|
+
}, { ...runConfig, configurable: { __toolMocks: toolMocks } }));
|
|
106
|
+
},
|
|
107
|
+
async deliver(sessionId, trigger, variables) {
|
|
108
|
+
const session = get(sessionId);
|
|
109
|
+
return settle(session, agent.workflow.send(session.sessionId, { delivery: { trigger: { id: trigger }, ...(variables ? { variables } : {}) } }, runConfig));
|
|
110
|
+
},
|
|
111
|
+
async decide(sessionId, to, comment) {
|
|
112
|
+
const session = get(sessionId);
|
|
113
|
+
return settle(session, agent.workflow.send(session.sessionId, { decision: { target: to, ...(comment ? { comment } : {}) } }, runConfig));
|
|
114
|
+
},
|
|
115
|
+
};
|
|
116
|
+
}
|
|
117
|
+
//# sourceMappingURL=driver.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"driver.js","sourceRoot":"","sources":["../../src/testing/driver.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,116 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The grader behind a `grade:` assertion.
|
|
3
|
+
*
|
|
4
|
+
* Two halves of one job: the **evidence** a graded turn is shown — what the
|
|
5
|
+
* session said, separated from what it did, bounded with visible elision — and
|
|
6
|
+
* the **prompt** that grades it against the case's stated expectation. They are
|
|
7
|
+
* one module because the evidence exists only to be graded; nothing else reads it.
|
|
8
|
+
*
|
|
9
|
+
* A grade is a soft threshold, not a gate: it scores, and the case's declared bar
|
|
10
|
+
* decides pass or fail.
|
|
11
|
+
*/
|
|
12
|
+
import type { SessionView, ToolCallFact } from "../workflow/session-view.js";
|
|
13
|
+
import type { BaseChatModel } from "@langchain/core/language_models/chat_models";
|
|
14
|
+
import { type ModelFactory } from "../env.js";
|
|
15
|
+
import type { WorkflowTestsConfig } from "../machine/types.js";
|
|
16
|
+
/**
|
|
17
|
+
* The evidence an offline-test `judge:` assertion hands the grading model:
|
|
18
|
+
* the turn's final reply *and* a chronological record of how the run reached
|
|
19
|
+
* it. Grading only the last assistant message grades prose — a criterion about
|
|
20
|
+
* what the agent *did* ("looked up the order before answering", "never fetched
|
|
21
|
+
* another customer's record") has nothing to read.
|
|
22
|
+
*
|
|
23
|
+
* Building and rendering are separate on purpose: composition (ordering,
|
|
24
|
+
* call/output pairing, bounding) is asserted against a plain object, and only
|
|
25
|
+
* the final string formatting is prompt-shaped.
|
|
26
|
+
*/
|
|
27
|
+
export type JudgeEvidenceEntry = {
|
|
28
|
+
kind: "message";
|
|
29
|
+
text: string;
|
|
30
|
+
}
|
|
31
|
+
/** Something the runtime wrote into the transcript: a decision, a delivered event, an error route. */
|
|
32
|
+
| {
|
|
33
|
+
kind: "runtime";
|
|
34
|
+
note: string;
|
|
35
|
+
text: string;
|
|
36
|
+
} | {
|
|
37
|
+
kind: "tool";
|
|
38
|
+
name: string;
|
|
39
|
+
input: unknown;
|
|
40
|
+
output?: unknown;
|
|
41
|
+
status: ToolCallFact["status"];
|
|
42
|
+
};
|
|
43
|
+
export interface JudgeEvidence {
|
|
44
|
+
/** The turn's final assistant reply — what the user was told. */
|
|
45
|
+
reply: string;
|
|
46
|
+
/**
|
|
47
|
+
* The turn in order: assistant messages and tool calls interleaved as they
|
|
48
|
+
* happened. The final reply is not repeated here when it closed the turn —
|
|
49
|
+
* it is rendered in its own section.
|
|
50
|
+
*/
|
|
51
|
+
record: JudgeEvidenceEntry[];
|
|
52
|
+
}
|
|
53
|
+
/**
|
|
54
|
+
* Per-value character cap for a rendered tool input, tool output, or record
|
|
55
|
+
* message. The reply is deliberately exempt (see {@link renderJudgeEvidence}).
|
|
56
|
+
*/
|
|
57
|
+
export declare const MAX_VALUE_CHARS = 800;
|
|
58
|
+
/** Total character budget for the rendered record section. */
|
|
59
|
+
export declare const MAX_RECORD_CHARS = 12000;
|
|
60
|
+
/**
|
|
61
|
+
* Derive judge evidence from the view the step's other assertions evaluate
|
|
62
|
+
* against, so a `judge` and a `calledTool` on the same step can never disagree
|
|
63
|
+
* about what the run did.
|
|
64
|
+
*
|
|
65
|
+
* Order comes from `events` (chronological, but with no call→output pairing)
|
|
66
|
+
* and pairing from `toolCalls` (paired by tool-call id, but flat). Both are
|
|
67
|
+
* built in the same pass of `messagesToSessionView`, so the nth `tool.called` event
|
|
68
|
+
* is `toolCalls[n]` — reading them together is exact, where re-pairing the
|
|
69
|
+
* events by name here would re-implement that matching less accurately.
|
|
70
|
+
*/
|
|
71
|
+
export declare function judgeEvidenceFromView(view: SessionView): JudgeEvidence;
|
|
72
|
+
/**
|
|
73
|
+
* Render evidence for the grading prompt. Every reduction is visible: a capped
|
|
74
|
+
* value names the characters it dropped and a record trimmed to its budget
|
|
75
|
+
* opens with the number of entries omitted. A judge that silently received
|
|
76
|
+
* less than the run produced would grade a run nobody can reconstruct.
|
|
77
|
+
*/
|
|
78
|
+
export declare function renderJudgeEvidence(evidence: JudgeEvidence): string;
|
|
79
|
+
/**
|
|
80
|
+
* What the judge model said: a score in `[0, 1]` and its reasoning. There is
|
|
81
|
+
* deliberately no pass/fail here — the case's declared `atLeast` decides that,
|
|
82
|
+
* in `assertions.ts`, so a record can never contradict the verdict beside it.
|
|
83
|
+
*/
|
|
84
|
+
export interface JudgeResult {
|
|
85
|
+
score: number;
|
|
86
|
+
reason?: string;
|
|
87
|
+
}
|
|
88
|
+
/**
|
|
89
|
+
* Resolve the judge model through the model factory's `judge` role (default:
|
|
90
|
+
* the env-configured model). `tests.judge.model` and `tests.judge.modelOptions`
|
|
91
|
+
* (`temperature`, `maxTokens`) are applied over the environment the factory is
|
|
92
|
+
* handed, so the default factory builds exactly the grader the spec names; a
|
|
93
|
+
* factory that brings its own model ignores the env, and these with it.
|
|
94
|
+
*
|
|
95
|
+
* The environment is loaded lazily (same shape as the harness): a factory that
|
|
96
|
+
* carries its own credentials never reads it, so a host with no `ARCHMAX_*`
|
|
97
|
+
* variables can still grade a `judge:` expectation.
|
|
98
|
+
*/
|
|
99
|
+
export declare function createJudgeModel(config: WorkflowTestsConfig, modelFactory?: ModelFactory): BaseChatModel;
|
|
100
|
+
/**
|
|
101
|
+
* Grade one closed-QA criterion against a turn's run evidence (the offline-test
|
|
102
|
+
* judge). The evidence carries what the assistant *did* as well as what it
|
|
103
|
+
* said, so the two are weighed differently in the prompt: a criterion about
|
|
104
|
+
* what the user was told is met only by the final reply, while a criterion
|
|
105
|
+
* about behaviour may be met by the tool record. Without that split, adding the
|
|
106
|
+
* record would let "tells the customer the refund is approved" pass on the
|
|
107
|
+
* strength of an `approve_refund` call the customer never heard about.
|
|
108
|
+
*/
|
|
109
|
+
export declare function gradeClosedQA(model: BaseChatModel, criteria: string, evidence: JudgeEvidence): Promise<JudgeResult>;
|
|
110
|
+
/**
|
|
111
|
+
* Read the verdict out of a grader's reply. The reply is scanned for balanced
|
|
112
|
+
* `{...}` blocks — so prose around the JSON, a markdown fence, or a brace
|
|
113
|
+
* inside the prose (`the reply {mentions} …`) is tolerated — and the first
|
|
114
|
+
* block that parses as an object wins. `null` when nothing parses.
|
|
115
|
+
*/
|
|
116
|
+
export declare function parseVerdict(raw: string): JudgeResult | null;
|
|
@@ -0,0 +1,261 @@
|
|
|
1
|
+
import { HumanMessage, SystemMessage } from "@langchain/core/messages";
|
|
2
|
+
import { defaultModelFactory, loadEnv } from "../env.js";
|
|
3
|
+
/**
|
|
4
|
+
* Per-value character cap for a rendered tool input, tool output, or record
|
|
5
|
+
* message. The reply is deliberately exempt (see {@link renderJudgeEvidence}).
|
|
6
|
+
*/
|
|
7
|
+
export const MAX_VALUE_CHARS = 800;
|
|
8
|
+
/** Total character budget for the rendered record section. */
|
|
9
|
+
export const MAX_RECORD_CHARS = 12_000;
|
|
10
|
+
/**
|
|
11
|
+
* Derive judge evidence from the view the step's other assertions evaluate
|
|
12
|
+
* against, so a `judge` and a `calledTool` on the same step can never disagree
|
|
13
|
+
* about what the run did.
|
|
14
|
+
*
|
|
15
|
+
* Order comes from `events` (chronological, but with no call→output pairing)
|
|
16
|
+
* and pairing from `toolCalls` (paired by tool-call id, but flat). Both are
|
|
17
|
+
* built in the same pass of `messagesToSessionView`, so the nth `tool.called` event
|
|
18
|
+
* is `toolCalls[n]` — reading them together is exact, where re-pairing the
|
|
19
|
+
* events by name here would re-implement that matching less accurately.
|
|
20
|
+
*/
|
|
21
|
+
export function judgeEvidenceFromView(view) {
|
|
22
|
+
const record = [];
|
|
23
|
+
let nthCall = 0;
|
|
24
|
+
for (const event of view.events) {
|
|
25
|
+
if (event.type === "message.completed") {
|
|
26
|
+
const text = String(event.data?.message ?? "");
|
|
27
|
+
if (text)
|
|
28
|
+
record.push({ kind: "message", text });
|
|
29
|
+
continue;
|
|
30
|
+
}
|
|
31
|
+
if (event.type === "runtime.note") {
|
|
32
|
+
const text = String(event.data?.text ?? "");
|
|
33
|
+
if (text)
|
|
34
|
+
record.push({ kind: "runtime", note: String(event.data?.kind ?? ""), text });
|
|
35
|
+
continue;
|
|
36
|
+
}
|
|
37
|
+
if (event.type !== "tool.called")
|
|
38
|
+
continue;
|
|
39
|
+
// `tool.completed` carries no id, so its output is read from the paired
|
|
40
|
+
// fact instead — the event only marks position, which is already known.
|
|
41
|
+
const fact = view.toolCalls[nthCall++];
|
|
42
|
+
record.push({
|
|
43
|
+
kind: "tool",
|
|
44
|
+
name: fact?.name ?? String(event.data?.name ?? ""),
|
|
45
|
+
input: fact ? fact.input : event.data?.input,
|
|
46
|
+
...(fact?.output !== undefined ? { output: fact.output } : {}),
|
|
47
|
+
status: fact?.status ?? "pending",
|
|
48
|
+
});
|
|
49
|
+
}
|
|
50
|
+
const reply = view.reply ?? "";
|
|
51
|
+
// The closing message is the graded reply; it gets its own section, so
|
|
52
|
+
// carrying it in the record too would spend budget saying it twice. Only
|
|
53
|
+
// when it *closed* the turn — a reply followed by tool calls stays in place,
|
|
54
|
+
// because "said this, then did that" is the ordering the judge needs.
|
|
55
|
+
const last = record[record.length - 1];
|
|
56
|
+
if (reply && last?.kind === "message" && last.text === reply)
|
|
57
|
+
record.pop();
|
|
58
|
+
return { reply, record };
|
|
59
|
+
}
|
|
60
|
+
/**
|
|
61
|
+
* Render evidence for the grading prompt. Every reduction is visible: a capped
|
|
62
|
+
* value names the characters it dropped and a record trimmed to its budget
|
|
63
|
+
* opens with the number of entries omitted. A judge that silently received
|
|
64
|
+
* less than the run produced would grade a run nobody can reconstruct.
|
|
65
|
+
*/
|
|
66
|
+
export function renderJudgeEvidence(evidence) {
|
|
67
|
+
return [
|
|
68
|
+
"What the assistant did, in order:",
|
|
69
|
+
renderRecord(evidence.record),
|
|
70
|
+
"",
|
|
71
|
+
"Final reply to the user:",
|
|
72
|
+
evidence.reply || "(empty)",
|
|
73
|
+
].join("\n");
|
|
74
|
+
}
|
|
75
|
+
function renderRecord(record) {
|
|
76
|
+
if (record.length === 0)
|
|
77
|
+
return "(no tool calls or intermediate messages recorded)";
|
|
78
|
+
// Numbered by original position, so the numbering stays coherent with the
|
|
79
|
+
// omission marker when the oldest entries are dropped.
|
|
80
|
+
const lines = record.map((entry, i) => `${i + 1}. ${renderEntry(entry)}`);
|
|
81
|
+
// Oldest-first dropping: the graded reply is the end of the turn, so the
|
|
82
|
+
// work nearest it is what a criterion most often concerns.
|
|
83
|
+
let kept = 0;
|
|
84
|
+
let total = 0;
|
|
85
|
+
for (let i = lines.length - 1; i >= 0; i--) {
|
|
86
|
+
const next = total + lines[i].length + 1;
|
|
87
|
+
if (kept > 0 && next > MAX_RECORD_CHARS)
|
|
88
|
+
break;
|
|
89
|
+
total = next;
|
|
90
|
+
kept += 1;
|
|
91
|
+
}
|
|
92
|
+
const dropped = lines.length - kept;
|
|
93
|
+
const body = lines.slice(dropped);
|
|
94
|
+
return dropped > 0
|
|
95
|
+
? [
|
|
96
|
+
`… ${dropped} earlier ${dropped === 1 ? "entry" : "entries"} omitted (evidence budget)`,
|
|
97
|
+
...body,
|
|
98
|
+
].join("\n")
|
|
99
|
+
: body.join("\n");
|
|
100
|
+
}
|
|
101
|
+
function renderEntry(entry) {
|
|
102
|
+
if (entry.kind === "message")
|
|
103
|
+
return `[message] ${cap(entry.text)}`;
|
|
104
|
+
if (entry.kind === "runtime")
|
|
105
|
+
return `[runtime:${entry.note}] ${cap(entry.text)}`;
|
|
106
|
+
const output = entry.output === undefined ? "(no output recorded)" : `output=${cap(stringify(entry.output))}`;
|
|
107
|
+
return `[tool] ${entry.name} input=${cap(stringify(entry.input))} → ${entry.status} ${output}`;
|
|
108
|
+
}
|
|
109
|
+
function stringify(value) {
|
|
110
|
+
if (typeof value === "string")
|
|
111
|
+
return value;
|
|
112
|
+
try {
|
|
113
|
+
return JSON.stringify(value) ?? String(value);
|
|
114
|
+
}
|
|
115
|
+
catch {
|
|
116
|
+
return String(value);
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
function cap(text) {
|
|
120
|
+
if (text.length <= MAX_VALUE_CHARS)
|
|
121
|
+
return text;
|
|
122
|
+
return `${text.slice(0, MAX_VALUE_CHARS)} …(+${text.length - MAX_VALUE_CHARS} chars)`;
|
|
123
|
+
}
|
|
124
|
+
/**
|
|
125
|
+
* Resolve the judge model through the model factory's `judge` role (default:
|
|
126
|
+
* the env-configured model). `tests.judge.model` and `tests.judge.modelOptions`
|
|
127
|
+
* (`temperature`, `maxTokens`) are applied over the environment the factory is
|
|
128
|
+
* handed, so the default factory builds exactly the grader the spec names; a
|
|
129
|
+
* factory that brings its own model ignores the env, and these with it.
|
|
130
|
+
*
|
|
131
|
+
* The environment is loaded lazily (same shape as the harness): a factory that
|
|
132
|
+
* carries its own credentials never reads it, so a host with no `ARCHMAX_*`
|
|
133
|
+
* variables can still grade a `judge:` expectation.
|
|
134
|
+
*/
|
|
135
|
+
export function createJudgeModel(config, modelFactory = defaultModelFactory) {
|
|
136
|
+
const { model, modelOptions = {} } = config.judge ?? {};
|
|
137
|
+
let cachedEnv;
|
|
138
|
+
const env = () => (cachedEnv ??= {
|
|
139
|
+
...loadEnv(),
|
|
140
|
+
...(model ? { model } : {}),
|
|
141
|
+
...(modelOptions.temperature !== undefined ? { temperature: modelOptions.temperature } : {}),
|
|
142
|
+
...(modelOptions.maxTokens !== undefined ? { maxTokens: modelOptions.maxTokens } : {}),
|
|
143
|
+
});
|
|
144
|
+
return modelFactory("judge", env);
|
|
145
|
+
}
|
|
146
|
+
/**
|
|
147
|
+
* Grade one closed-QA criterion against a turn's run evidence (the offline-test
|
|
148
|
+
* judge). The evidence carries what the assistant *did* as well as what it
|
|
149
|
+
* said, so the two are weighed differently in the prompt: a criterion about
|
|
150
|
+
* what the user was told is met only by the final reply, while a criterion
|
|
151
|
+
* about behaviour may be met by the tool record. Without that split, adding the
|
|
152
|
+
* record would let "tells the customer the refund is approved" pass on the
|
|
153
|
+
* strength of an `approve_refund` call the customer never heard about.
|
|
154
|
+
*/
|
|
155
|
+
export async function gradeClosedQA(model, criteria, evidence) {
|
|
156
|
+
const messages = [
|
|
157
|
+
new SystemMessage([
|
|
158
|
+
"You grade whether an assistant's turn satisfies a yes/no criterion.",
|
|
159
|
+
"You are given a chronological record of the turn — the assistant's messages, the tool calls it made with their inputs, outputs and status, and the runtime's own notes (marked [runtime:<kind>]: a person's decision that routed the run, an event delivered to it, an error route) — followed by the assistant's final reply to the user.",
|
|
160
|
+
"Weigh them differently: a criterion about what the user was told is met only by the final reply, while a criterion about what the assistant did, or what happened to the run, may be met by the record.",
|
|
161
|
+
'Reply with JSON only: {"score": number between 0 and 1, "pass": boolean, "reason": string}',
|
|
162
|
+
"score 1 means the criterion is fully met; 0 means it is not met at all.",
|
|
163
|
+
// The reason is printed beside a verdict line, so it has to fit there.
|
|
164
|
+
// A bound in the prompt, not a truncation: cutting a model's
|
|
165
|
+
// explanation mid-clause reads worse than asking for a short one.
|
|
166
|
+
"Keep reason to 1-3 sentences: state the judgment and the evidence for it, nothing more.",
|
|
167
|
+
].join(" ")),
|
|
168
|
+
new HumanMessage(`Criterion: ${criteria}\n\n${renderJudgeEvidence(evidence)}`),
|
|
169
|
+
];
|
|
170
|
+
const first = await model.invoke(messages);
|
|
171
|
+
const verdict = parseVerdict(responseText(first));
|
|
172
|
+
if (verdict)
|
|
173
|
+
return verdict;
|
|
174
|
+
// A grader that wrapped or mangled its JSON gets exactly one reminder before
|
|
175
|
+
// its turn scores 0: a real model run failed a case this way, and re-asking
|
|
176
|
+
// is cheaper than a false failure. The retry carries the first reply, so the
|
|
177
|
+
// model corrects its own answer rather than grading afresh.
|
|
178
|
+
const retry = await model.invoke([
|
|
179
|
+
...messages,
|
|
180
|
+
first,
|
|
181
|
+
new HumanMessage("Your previous reply could not be read as JSON. Reply with the JSON object only — " +
|
|
182
|
+
'no prose, no code fences: {"score": number, "pass": boolean, "reason": string}'),
|
|
183
|
+
]);
|
|
184
|
+
return (parseVerdict(responseText(retry)) ?? {
|
|
185
|
+
score: 0,
|
|
186
|
+
reason: "grader returned no readable JSON verdict",
|
|
187
|
+
});
|
|
188
|
+
}
|
|
189
|
+
/** The textual content of a model reply, whatever shape the provider used. */
|
|
190
|
+
function responseText(response) {
|
|
191
|
+
const { content } = response;
|
|
192
|
+
if (typeof content === "string")
|
|
193
|
+
return content;
|
|
194
|
+
if (Array.isArray(content)) {
|
|
195
|
+
return content
|
|
196
|
+
.map((p) => (typeof p === "string" ? p : (p.text ?? "")))
|
|
197
|
+
.join("");
|
|
198
|
+
}
|
|
199
|
+
return String(content ?? "");
|
|
200
|
+
}
|
|
201
|
+
/**
|
|
202
|
+
* Read the verdict out of a grader's reply. The reply is scanned for balanced
|
|
203
|
+
* `{...}` blocks — so prose around the JSON, a markdown fence, or a brace
|
|
204
|
+
* inside the prose (`the reply {mentions} …`) is tolerated — and the first
|
|
205
|
+
* block that parses as an object wins. `null` when nothing parses.
|
|
206
|
+
*/
|
|
207
|
+
export function parseVerdict(raw) {
|
|
208
|
+
for (const block of balancedObjects(raw)) {
|
|
209
|
+
try {
|
|
210
|
+
const parsed = JSON.parse(block);
|
|
211
|
+
if (parsed === null || typeof parsed !== "object" || Array.isArray(parsed))
|
|
212
|
+
continue;
|
|
213
|
+
// The prompt asks the model for its own `pass` boolean (unchanged
|
|
214
|
+
// wording, so grading stays calibrated); it is not read — the declared
|
|
215
|
+
// `atLeast` is the only bar.
|
|
216
|
+
const { score, reason } = parsed;
|
|
217
|
+
return {
|
|
218
|
+
score: Math.max(0, Math.min(1, Number(score ?? 0))),
|
|
219
|
+
reason: typeof reason === "string" ? reason : undefined,
|
|
220
|
+
};
|
|
221
|
+
}
|
|
222
|
+
catch {
|
|
223
|
+
// Not JSON: try the next candidate.
|
|
224
|
+
}
|
|
225
|
+
}
|
|
226
|
+
return null;
|
|
227
|
+
}
|
|
228
|
+
/** Every balanced `{...}` block in `raw`, in order of its opening brace. */
|
|
229
|
+
function* balancedObjects(raw) {
|
|
230
|
+
let start = raw.indexOf("{");
|
|
231
|
+
while (start !== -1) {
|
|
232
|
+
let depth = 0;
|
|
233
|
+
let inString = false;
|
|
234
|
+
let escaped = false;
|
|
235
|
+
let end = -1;
|
|
236
|
+
for (let i = start; i < raw.length; i++) {
|
|
237
|
+
const ch = raw[i];
|
|
238
|
+
if (inString) {
|
|
239
|
+
if (escaped)
|
|
240
|
+
escaped = false;
|
|
241
|
+
else if (ch === "\\")
|
|
242
|
+
escaped = true;
|
|
243
|
+
else if (ch === '"')
|
|
244
|
+
inString = false;
|
|
245
|
+
continue;
|
|
246
|
+
}
|
|
247
|
+
if (ch === '"')
|
|
248
|
+
inString = true;
|
|
249
|
+
else if (ch === "{")
|
|
250
|
+
depth += 1;
|
|
251
|
+
else if (ch === "}" && --depth === 0) {
|
|
252
|
+
end = i;
|
|
253
|
+
break;
|
|
254
|
+
}
|
|
255
|
+
}
|
|
256
|
+
if (end !== -1)
|
|
257
|
+
yield raw.slice(start, end + 1);
|
|
258
|
+
start = raw.indexOf("{", start + 1);
|
|
259
|
+
}
|
|
260
|
+
}
|
|
261
|
+
//# sourceMappingURL=grade.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"grade.js","sourceRoot":"","sources":["../../src/testing/grade.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Host-side interpretation of one parsed case document: open the case's
|
|
3
|
+
* session with its start conditions, then walk the flat step list — actions
|
|
4
|
+
* (`send`, `decide`, `deliver`) drive the agent through `workflow.send`, and each
|
|
5
|
+
* assertion evaluates against the view of the nearest action above it. No sandbox is involved —
|
|
6
|
+
* the QuickJS sandbox serves only lifecycle hooks and the agent's own sandbox
|
|
7
|
+
* tools inside the driven agent.
|
|
8
|
+
*
|
|
9
|
+
* A case that dies partway is still reported: termination is returned as an
|
|
10
|
+
* `error` on the outcome, next to the records the case had already produced,
|
|
11
|
+
* so the steps that passed before it are never thrown away.
|
|
12
|
+
*/
|
|
13
|
+
import type { GovernedAgent } from "../agent.js";
|
|
14
|
+
import { type GradeFn } from "./assertions.js";
|
|
15
|
+
import type { CaseDocument } from "./case-schema.js";
|
|
16
|
+
import type { AssertionRecord } from "./runner.js";
|
|
17
|
+
export interface ExecuteCaseOptions {
|
|
18
|
+
/** Aborts the runs the case drives (the case's wall-clock budget). */
|
|
19
|
+
signal?: AbortSignal;
|
|
20
|
+
doc: CaseDocument;
|
|
21
|
+
/** Seed files, `from:` references already materialized by the runner. */
|
|
22
|
+
seeds: Record<string, unknown>;
|
|
23
|
+
agent: GovernedAgent;
|
|
24
|
+
/** The runtime session id for this case (caller-nameable via `sessionIdForCase`). */
|
|
25
|
+
sessionId: string;
|
|
26
|
+
grade?: GradeFn;
|
|
27
|
+
/**
|
|
28
|
+
* Invoked with each assertion record as it is produced — observe-only; the
|
|
29
|
+
* record objects are the same ones returned in the outcome. A caller that
|
|
30
|
+
* may stop waiting on this promise (the runner abandons it at the case's
|
|
31
|
+
* wall-clock deadline) uses this to hold the records produced before it let
|
|
32
|
+
* go, which the return value can no longer deliver.
|
|
33
|
+
*/
|
|
34
|
+
onRecord?: (record: AssertionRecord) => void;
|
|
35
|
+
}
|
|
36
|
+
export interface ExecuteCaseOutcome {
|
|
37
|
+
records: AssertionRecord[];
|
|
38
|
+
/**
|
|
39
|
+
* Set when the case terminated before its steps were exhausted: a failed
|
|
40
|
+
* `send`/`decide`, a refused start (bad trigger, failed seed,
|
|
41
|
+
* un-interceptable mocks). The records produced before that point are still
|
|
42
|
+
* returned alongside it — a case's failure is reported as data, not by
|
|
43
|
+
* throwing past what it already established.
|
|
44
|
+
*/
|
|
45
|
+
error?: string;
|
|
46
|
+
}
|
|
47
|
+
/**
|
|
48
|
+
* Describe every assertion step a case did not account for, as `not-executed`
|
|
49
|
+
* records. The one place un-run steps are built, so the three ways a case can
|
|
50
|
+
* stop early — a structural halt, a thrown action, and the runner's wall-clock
|
|
51
|
+
* timeout — cannot drift apart in what they report.
|
|
52
|
+
*/
|
|
53
|
+
export declare function notExecutedRecords(doc: CaseDocument, alreadyAccountedFor: (step: number) => boolean): AssertionRecord[];
|
|
54
|
+
/** Execute one case document against the agent under test. */
|
|
55
|
+
export declare function executeCase(opts: ExecuteCaseOptions): Promise<ExecuteCaseOutcome>;
|