@effect-agent/testing 0.1.0-beta.38 → 0.1.0-beta.40
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/certification.d.mts +102 -0
- package/dist/certification.mjs +561 -0
- package/dist/certification.mjs.map +1 -0
- package/dist/chaos.d.mts +124 -0
- package/dist/chaos.mjs +739 -0
- package/dist/chaos.mjs.map +1 -0
- package/dist/code-executor.d.mts +48 -0
- package/dist/code-executor.mjs +565 -0
- package/dist/code-executor.mjs.map +1 -0
- package/dist/deterministic-layers-CKyYxBhN.mjs +354 -0
- package/dist/deterministic-layers-CKyYxBhN.mjs.map +1 -0
- package/dist/docs-researcher.d.mts +254 -0
- package/dist/docs-researcher.mjs +479 -0
- package/dist/docs-researcher.mjs.map +1 -0
- package/dist/index.d.mts +2 -2947
- package/dist/index.mjs +2 -4913
- package/dist/scripted-model-C2y0ztuj.mjs +167 -0
- package/dist/scripted-model-C2y0ztuj.mjs.map +1 -0
- package/dist/scripted-model-dOa_e0-n.d.mts +677 -0
- package/dist/travel-planner.d.mts +1767 -0
- package/dist/travel-planner.mjs +2103 -0
- package/dist/travel-planner.mjs.map +1 -0
- package/package.json +34 -11
- package/src/certification.ts +71 -61
- package/src/chaos.ts +58 -71
- package/src/code-executor.ts +3 -0
- package/src/docs-researcher.ts +2 -0
- package/src/fixtures/docs-researcher/definition.ts +7 -7
- package/src/fixtures/docs-researcher/harness.ts +12 -14
- package/src/fixtures/docs-researcher/index.ts +1 -1
- package/src/fixtures/travel-planner/definition.ts +1 -1
- package/src/fixtures/travel-planner/deterministic-layers.ts +7 -4
- package/src/fixtures/travel-planner/phase2.ts +1 -1
- package/src/fixtures/travel-planner/phase3.ts +14 -18
- package/src/fixtures/travel-planner/phase4.ts +7 -7
- package/src/fixtures/travel-planner/phase5.ts +5 -5
- package/src/fixtures/travel-planner/phase6.ts +30 -29
- package/src/fixtures/travel-planner/subagents-durable.ts +6 -6
- package/src/fixtures/travel-planner/subagents.ts +4 -4
- package/src/index.ts +1 -10
- package/src/travel-planner.ts +2 -0
- package/dist/index.mjs.map +0 -1
- package/src/code-executor-conformance.d.ts +0 -30
- package/src/fixtures/warehouse/index.ts +0 -412
package/src/chaos.ts
CHANGED
|
@@ -7,7 +7,7 @@ import {
|
|
|
7
7
|
import {
|
|
8
8
|
Agent,
|
|
9
9
|
AgentPolicy,
|
|
10
|
-
|
|
10
|
+
ThreadId,
|
|
11
11
|
IdGenerator,
|
|
12
12
|
RunId,
|
|
13
13
|
ToolCallId,
|
|
@@ -17,17 +17,15 @@ import {
|
|
|
17
17
|
import { DurableStep, DurableStepError, ToolExecutionClass } from "@effect-agent/engine";
|
|
18
18
|
import {
|
|
19
19
|
AbortCommand,
|
|
20
|
-
AgentBindingResolver,
|
|
21
20
|
ApprovalDecisionCommand,
|
|
22
|
-
|
|
23
|
-
|
|
21
|
+
ThreadExportRequest,
|
|
22
|
+
ThreadStore,
|
|
24
23
|
DefinitionDigests,
|
|
25
24
|
Digest,
|
|
26
25
|
DurableAgentRuntime,
|
|
27
26
|
DurableRuntimeConfig,
|
|
28
27
|
DurableRuntimeFailpointError,
|
|
29
28
|
DurableRuntimeFailpointLocation,
|
|
30
|
-
DurableRuntimeFailpointTestControl,
|
|
31
29
|
DurableWorkerBinding,
|
|
32
30
|
IdempotencyKey,
|
|
33
31
|
ObligationThresholds,
|
|
@@ -38,8 +36,8 @@ import {
|
|
|
38
36
|
SubmissionLedger,
|
|
39
37
|
SubmissionLookupById,
|
|
40
38
|
UnknownResolutionCommand,
|
|
41
|
-
|
|
42
|
-
|
|
39
|
+
childThreadIdFor,
|
|
40
|
+
verifyThreadInvariants,
|
|
43
41
|
type CanonicalRecordEnvelope,
|
|
44
42
|
type BatchId,
|
|
45
43
|
type DurableBindingFailure,
|
|
@@ -51,7 +49,8 @@ import {
|
|
|
51
49
|
type Settlement,
|
|
52
50
|
type SubmissionSnapshot,
|
|
53
51
|
type UnknownResolution,
|
|
54
|
-
} from "@effect-agent/
|
|
52
|
+
} from "@effect-agent/thread";
|
|
53
|
+
import { DurableRuntimeFailpointTestControl } from "@effect-agent/thread/testing";
|
|
55
54
|
import { Cause, Effect, Exit, Layer, Option, Ref, Schema, Stream } from "effect";
|
|
56
55
|
import { FastCheck } from "effect/testing";
|
|
57
56
|
import {
|
|
@@ -69,7 +68,7 @@ import {
|
|
|
69
68
|
* deterministic runner that drives the durable coordinator over whatever adapter pair the test
|
|
70
69
|
* provides. Every plan ends in the SAME claims the crash matrices make:
|
|
71
70
|
*
|
|
72
|
-
* 1. `
|
|
71
|
+
* 1. `verifyThreadInvariants` in convergence mode over every touched Thread (the
|
|
73
72
|
* shared WP1 checker — one set of claims for admin verify, certification, chaos, and soak);
|
|
74
73
|
* 2. `scanObligations` returning ZERO entries (everything settled; nothing invisibly stuck);
|
|
75
74
|
* 3. supplier non-fabrication wherever the deterministic desk was in play (durability §10: no
|
|
@@ -152,10 +151,10 @@ export class ChaosPlan extends Schema.Class<ChaosPlan>("@effect-agent/testing/Ch
|
|
|
152
151
|
export class ChaosLaneReport extends Schema.Class<ChaosLaneReport>(
|
|
153
152
|
"@effect-agent/testing/ChaosLaneReport",
|
|
154
153
|
)({
|
|
155
|
-
|
|
154
|
+
threadId: ThreadId,
|
|
156
155
|
kind: ChaosScenarioKind,
|
|
157
156
|
submissionCount: Schema.Int.check(Schema.isGreaterThanOrEqualTo(0)),
|
|
158
|
-
/** Verdict of `
|
|
157
|
+
/** Verdict of `verifyThreadInvariants` in convergence mode. */
|
|
159
158
|
verified: Schema.Boolean,
|
|
160
159
|
}) {}
|
|
161
160
|
|
|
@@ -217,13 +216,12 @@ const laneArbitrary: FastCheck.Arbitrary<GeneratedLane> = FastCheck.constantFrom
|
|
|
217
216
|
"approval",
|
|
218
217
|
"join",
|
|
219
218
|
"delegation",
|
|
220
|
-
).chain(
|
|
221
|
-
|
|
222
|
-
kind
|
|
223
|
-
|
|
224
|
-
: kind
|
|
225
|
-
|
|
226
|
-
: FastCheck.constant<GeneratedLane>({ kind, depth: 1 }),
|
|
219
|
+
).chain((kind): FastCheck.Arbitrary<GeneratedLane> =>
|
|
220
|
+
kind === "join"
|
|
221
|
+
? FastCheck.integer({ min: 2, max: 3 }).map((depth): GeneratedLane => ({ kind, depth }))
|
|
222
|
+
: kind === "plain"
|
|
223
|
+
? FastCheck.integer({ min: 1, max: 2 }).map((depth): GeneratedLane => ({ kind, depth }))
|
|
224
|
+
: FastCheck.constant<GeneratedLane>({ kind, depth: 1 }),
|
|
227
225
|
);
|
|
228
226
|
|
|
229
227
|
interface ChaosPlanShape {
|
|
@@ -371,7 +369,7 @@ const policy = AgentPolicy.make({
|
|
|
371
369
|
const PlainInput = Schema.Struct({ question: Schema.String });
|
|
372
370
|
const PlainOutput = Schema.Struct({ answer: Schema.String });
|
|
373
371
|
|
|
374
|
-
const plainDefinition = Agent.
|
|
372
|
+
const plainDefinition = Agent.make("chaos-plain", {
|
|
375
373
|
input: PlainInput,
|
|
376
374
|
output: PlainOutput,
|
|
377
375
|
instructions: "Answer as JSON.",
|
|
@@ -385,7 +383,7 @@ const BookUncertain = Tool.make("book", {
|
|
|
385
383
|
success: Schema.Struct({ confirmation: Schema.String }),
|
|
386
384
|
});
|
|
387
385
|
const bookTools = Toolkit.make(BookUncertain);
|
|
388
|
-
const bookDefinition = Agent.
|
|
386
|
+
const bookDefinition = Agent.make("chaos-book", {
|
|
389
387
|
input: PlainInput,
|
|
390
388
|
output: PlainOutput,
|
|
391
389
|
instructions: "Book it.",
|
|
@@ -399,7 +397,7 @@ const BookApproval = Tool.make("book", {
|
|
|
399
397
|
needsApproval: true,
|
|
400
398
|
});
|
|
401
399
|
const approvalTools = Toolkit.make(BookApproval);
|
|
402
|
-
const approvalDefinition = Agent.
|
|
400
|
+
const approvalDefinition = Agent.make("chaos-approval", {
|
|
403
401
|
input: PlainInput,
|
|
404
402
|
output: PlainOutput,
|
|
405
403
|
instructions: "Book after approval.",
|
|
@@ -414,7 +412,7 @@ const Itinerary = Tool.make("itinerary", {
|
|
|
414
412
|
dependencies: [DurableStep],
|
|
415
413
|
}).annotate(ToolExecutionClass, "uncertain");
|
|
416
414
|
const itineraryTools = Toolkit.make(Itinerary);
|
|
417
|
-
const itineraryDefinition = Agent.
|
|
415
|
+
const itineraryDefinition = Agent.make("chaos-itinerary", {
|
|
418
416
|
input: PlainInput,
|
|
419
417
|
output: PlainOutput,
|
|
420
418
|
instructions: "Reserve the itinerary.",
|
|
@@ -422,7 +420,7 @@ const itineraryDefinition = Agent.define("chaos-itinerary", {
|
|
|
422
420
|
policy,
|
|
423
421
|
});
|
|
424
422
|
|
|
425
|
-
const childDefinition = Agent.
|
|
423
|
+
const childDefinition = Agent.make("chaos-child", {
|
|
426
424
|
input: PlainInput,
|
|
427
425
|
output: PlainOutput,
|
|
428
426
|
instructions: "Answer as JSON.",
|
|
@@ -457,7 +455,7 @@ const chaosDelegation = Subagent.define("delegate_chaos", {
|
|
|
457
455
|
}),
|
|
458
456
|
});
|
|
459
457
|
|
|
460
|
-
const coordinatorDefinition = Agent.
|
|
458
|
+
const coordinatorDefinition = Agent.make("chaos-coordinator", {
|
|
461
459
|
input: Schema.Struct({ mission: Schema.String }),
|
|
462
460
|
output: Schema.Struct({ report: Schema.String }),
|
|
463
461
|
instructions: "Delegate, then report as JSON.",
|
|
@@ -487,7 +485,7 @@ const childLaneDigests = (lane: number): DefinitionDigests => {
|
|
|
487
485
|
};
|
|
488
486
|
|
|
489
487
|
const CHAOS_PRINCIPAL = Schema.decodeSync(Principal)("principal-chaos");
|
|
490
|
-
const
|
|
488
|
+
const decodeThreadId = Schema.decodeSync(ThreadId);
|
|
491
489
|
const decodeIdempotencyKey = Schema.decodeSync(IdempotencyKey);
|
|
492
490
|
const decodeToolCallId = Schema.decodeSync(ToolCallId);
|
|
493
491
|
const decodeRunId = Schema.decodeSync(RunId);
|
|
@@ -503,7 +501,7 @@ const chaosIdentifiers = Layer.effect(
|
|
|
503
501
|
Effect.map((value) => decode(`${prefix}-${value}`)),
|
|
504
502
|
);
|
|
505
503
|
return {
|
|
506
|
-
|
|
504
|
+
nextThreadId: next(decodeThreadId, "chaos-fixture-thread"),
|
|
507
505
|
nextRunId: next(decodeRunId, "chaos-fixture-run"),
|
|
508
506
|
nextTurnId: next(decodeTurnId, "chaos-fixture-turn"),
|
|
509
507
|
};
|
|
@@ -573,7 +571,7 @@ const tolerateTyped = <A, E, R>(
|
|
|
573
571
|
interface LaneFixture {
|
|
574
572
|
readonly index: number;
|
|
575
573
|
readonly kind: ChaosScenarioKind;
|
|
576
|
-
readonly
|
|
574
|
+
readonly threadId: ThreadId;
|
|
577
575
|
readonly ref: string;
|
|
578
576
|
readonly deskInPlay: boolean;
|
|
579
577
|
readonly submissionIndexes: ReadonlyArray<number>;
|
|
@@ -583,7 +581,7 @@ interface LaneFixture {
|
|
|
583
581
|
) => ReadonlyArray<
|
|
584
582
|
Effect.Effect<ReadonlyArray<Settlement>, DurableWorkerFailure | DurableBindingFailure>
|
|
585
583
|
>;
|
|
586
|
-
readonly
|
|
584
|
+
readonly childThreadOf: (firstReceipt: Receipt) => ThreadId | undefined;
|
|
587
585
|
}
|
|
588
586
|
|
|
589
587
|
interface SubmissionState {
|
|
@@ -627,14 +625,14 @@ const makeLaneFixture = Effect.fn("Chaos.makeLaneFixture")(function* (
|
|
|
627
625
|
desk: ChaosDesk,
|
|
628
626
|
) {
|
|
629
627
|
const runtime = yield* DurableAgentRuntime;
|
|
630
|
-
const
|
|
628
|
+
const threadId = decodeThreadId(`chaos-${plan.seed}-lane-${laneIndex}`);
|
|
631
629
|
const ref = `ref-l${laneIndex}`;
|
|
632
630
|
const script = scriptFor(kind, ref);
|
|
633
631
|
const model = promptScriptedModel(`chaos-${kind}-${laneIndex}`, script);
|
|
634
632
|
const digests = laneDigests(laneIndex);
|
|
635
633
|
|
|
636
634
|
const submitOptionsFor = (flatIndex: number) => ({
|
|
637
|
-
|
|
635
|
+
threadId,
|
|
638
636
|
principal: CHAOS_PRINCIPAL,
|
|
639
637
|
idempotencyKey: decodeIdempotencyKey(`chaos-${plan.seed}-s${flatIndex}`),
|
|
640
638
|
definitions: digests,
|
|
@@ -655,33 +653,28 @@ const makeLaneFixture = Effect.fn("Chaos.makeLaneFixture")(function* (
|
|
|
655
653
|
): LaneFixture => ({
|
|
656
654
|
index: laneIndex,
|
|
657
655
|
kind,
|
|
658
|
-
|
|
656
|
+
threadId,
|
|
659
657
|
ref,
|
|
660
658
|
deskInPlay,
|
|
661
659
|
submissionIndexes,
|
|
662
660
|
submitOne,
|
|
663
661
|
drives: () => [drive],
|
|
664
|
-
|
|
662
|
+
childThreadOf: () => undefined,
|
|
665
663
|
});
|
|
666
664
|
|
|
667
665
|
switch (kind) {
|
|
668
666
|
case "plain":
|
|
669
667
|
case "join": {
|
|
670
668
|
const agent = Agent.withModel(plainDefinition, model);
|
|
671
|
-
return plainLaneFixture(
|
|
672
|
-
|
|
673
|
-
runtime.processConversation(agent, conversationId),
|
|
674
|
-
(flatIndex) =>
|
|
675
|
-
runtime.submit(agent, { question: `chaos ${flatIndex}` }, submitOptionsFor(flatIndex)),
|
|
669
|
+
return plainLaneFixture(false, runtime.processThread(agent, threadId), (flatIndex) =>
|
|
670
|
+
runtime.submit(agent, { question: `chaos ${flatIndex}` }, submitOptionsFor(flatIndex)),
|
|
676
671
|
);
|
|
677
672
|
}
|
|
678
673
|
case "uncertain-tool": {
|
|
679
674
|
const agent = Agent.withModel(bookDefinition, model);
|
|
680
675
|
return plainLaneFixture(
|
|
681
676
|
true,
|
|
682
|
-
runtime
|
|
683
|
-
.processConversation(agent, conversationId)
|
|
684
|
-
.pipe(Effect.provide(bookToolLayerFor(bookTools))),
|
|
677
|
+
runtime.processThread(agent, threadId).pipe(Effect.provide(bookToolLayerFor(bookTools))),
|
|
685
678
|
(flatIndex) =>
|
|
686
679
|
runtime.submit(agent, { question: `chaos ${flatIndex}` }, submitOptionsFor(flatIndex)),
|
|
687
680
|
);
|
|
@@ -691,7 +684,7 @@ const makeLaneFixture = Effect.fn("Chaos.makeLaneFixture")(function* (
|
|
|
691
684
|
return plainLaneFixture(
|
|
692
685
|
true,
|
|
693
686
|
runtime
|
|
694
|
-
.
|
|
687
|
+
.processThread(agent, threadId)
|
|
695
688
|
.pipe(Effect.provide(bookToolLayerFor(approvalTools))),
|
|
696
689
|
(flatIndex) =>
|
|
697
690
|
runtime.submit(agent, { question: `chaos ${flatIndex}` }, submitOptionsFor(flatIndex)),
|
|
@@ -718,7 +711,7 @@ const makeLaneFixture = Effect.fn("Chaos.makeLaneFixture")(function* (
|
|
|
718
711
|
});
|
|
719
712
|
return plainLaneFixture(
|
|
720
713
|
true,
|
|
721
|
-
runtime.
|
|
714
|
+
runtime.processThread(agent, threadId).pipe(Effect.provide(toolLayer)),
|
|
722
715
|
(flatIndex) =>
|
|
723
716
|
runtime.submit(agent, { question: `chaos ${flatIndex}` }, submitOptionsFor(flatIndex)),
|
|
724
717
|
);
|
|
@@ -741,15 +734,12 @@ const makeLaneFixture = Effect.fn("Chaos.makeLaneFixture")(function* (
|
|
|
741
734
|
childBinding,
|
|
742
735
|
childLaneDigests(laneIndex),
|
|
743
736
|
);
|
|
744
|
-
const
|
|
745
|
-
const driveResolved = (
|
|
746
|
-
runtime
|
|
747
|
-
.processConversationResolved(conversation)
|
|
748
|
-
.pipe(Effect.provideService(AgentBindingResolver, resolver));
|
|
737
|
+
const bindings = [parentResolved, childResolved];
|
|
738
|
+
const driveResolved = (thread: ThreadId) => runtime.processThreadResolved(thread, bindings);
|
|
749
739
|
const fixture: LaneFixture = {
|
|
750
740
|
index: laneIndex,
|
|
751
741
|
kind,
|
|
752
|
-
|
|
742
|
+
threadId,
|
|
753
743
|
ref,
|
|
754
744
|
deskInPlay: false,
|
|
755
745
|
submissionIndexes,
|
|
@@ -762,21 +752,18 @@ const makeLaneFixture = Effect.fn("Chaos.makeLaneFixture")(function* (
|
|
|
762
752
|
drives: (firstReceipt) => {
|
|
763
753
|
const drives: Array<
|
|
764
754
|
Effect.Effect<ReadonlyArray<Settlement>, DurableWorkerFailure | DurableBindingFailure>
|
|
765
|
-
> = [driveResolved(
|
|
755
|
+
> = [driveResolved(threadId)];
|
|
766
756
|
if (firstReceipt !== undefined) {
|
|
767
757
|
drives.push(
|
|
768
758
|
driveResolved(
|
|
769
|
-
|
|
770
|
-
firstReceipt.submissionId,
|
|
771
|
-
decodeToolCallId(DELEGATE_CALL_ID),
|
|
772
|
-
),
|
|
759
|
+
childThreadIdFor(firstReceipt.submissionId, decodeToolCallId(DELEGATE_CALL_ID)),
|
|
773
760
|
),
|
|
774
761
|
);
|
|
775
762
|
}
|
|
776
763
|
return drives;
|
|
777
764
|
},
|
|
778
|
-
|
|
779
|
-
|
|
765
|
+
childThreadOf: (firstReceipt) =>
|
|
766
|
+
childThreadIdFor(firstReceipt.submissionId, decodeToolCallId(DELEGATE_CALL_ID)),
|
|
780
767
|
};
|
|
781
768
|
return fixture;
|
|
782
769
|
}
|
|
@@ -902,7 +889,7 @@ const submissionIdsNamedBy = (
|
|
|
902
889
|
payload._tag === "SubmissionSettled" ||
|
|
903
890
|
payload._tag === "AbortRequested"
|
|
904
891
|
) {
|
|
905
|
-
named.add(payload.submissionId);
|
|
892
|
+
if (payload.submissionId !== undefined) named.add(payload.submissionId);
|
|
906
893
|
}
|
|
907
894
|
}
|
|
908
895
|
return named;
|
|
@@ -968,7 +955,7 @@ export const runChaosPlan = Effect.fn("Chaos.runChaosPlan")(function* (
|
|
|
968
955
|
) {
|
|
969
956
|
const runtime = yield* DurableAgentRuntime;
|
|
970
957
|
const ledger = yield* SubmissionLedger;
|
|
971
|
-
const store = yield*
|
|
958
|
+
const store = yield* ThreadStore;
|
|
972
959
|
const config = yield* DurableRuntimeConfig;
|
|
973
960
|
const failpoints = yield* DurableRuntimeFailpointTestControl;
|
|
974
961
|
const random = mulberry32(plan.seed);
|
|
@@ -1036,7 +1023,7 @@ export const runChaosPlan = Effect.fn("Chaos.runChaosPlan")(function* (
|
|
|
1036
1023
|
}
|
|
1037
1024
|
|
|
1038
1025
|
// Admission chaos: pending submissions retry under the active arm until a Receipt lands;
|
|
1039
|
-
// the identical (
|
|
1026
|
+
// the identical (thread, principal, key) triple reattaches, never duplicates.
|
|
1040
1027
|
for (const state of states) {
|
|
1041
1028
|
if (state.receipt !== undefined) continue;
|
|
1042
1029
|
const receipt = yield* tolerateTyped(state.lane.submitOne(state.flatIndex));
|
|
@@ -1106,20 +1093,20 @@ export const runChaosPlan = Effect.fn("Chaos.runChaosPlan")(function* (
|
|
|
1106
1093
|
});
|
|
1107
1094
|
}
|
|
1108
1095
|
|
|
1109
|
-
// Final claims: the shared invariant checker per touched
|
|
1096
|
+
// Final claims: the shared invariant checker per touched Thread, in convergence mode,
|
|
1110
1097
|
// with the full digest chain (single known producer), plus the desk non-fabrication sweep.
|
|
1111
1098
|
const produced = yield* desk.produced;
|
|
1112
1099
|
const laneReports: Array<ChaosLaneReport> = [];
|
|
1113
|
-
const
|
|
1114
|
-
|
|
1100
|
+
const verifyThread = Effect.fn("Chaos.verifyThread")(function* (
|
|
1101
|
+
threadId: ThreadId,
|
|
1115
1102
|
kind: ChaosScenarioKind,
|
|
1116
1103
|
deskInPlay: boolean,
|
|
1117
1104
|
) {
|
|
1118
|
-
const exported = yield* store.export(
|
|
1105
|
+
const exported = yield* store.export(ThreadExportRequest.make({ threadId })).pipe(
|
|
1119
1106
|
Effect.mapError((error) =>
|
|
1120
1107
|
ChaosConvergenceFailure.make({
|
|
1121
1108
|
seed: plan.seed,
|
|
1122
|
-
message: `export of ${
|
|
1109
|
+
message: `export of ${threadId} failed: ${String(error)}`,
|
|
1123
1110
|
}),
|
|
1124
1111
|
),
|
|
1125
1112
|
);
|
|
@@ -1138,7 +1125,7 @@ export const runChaosPlan = Effect.fn("Chaos.runChaosPlan")(function* (
|
|
|
1138
1125
|
const batchProducers = new Map<BatchId, ProducerId>(
|
|
1139
1126
|
exported.records.map((envelope) => [envelope.batchId, config.producerId]),
|
|
1140
1127
|
);
|
|
1141
|
-
const report = yield*
|
|
1128
|
+
const report = yield* verifyThreadInvariants({
|
|
1142
1129
|
export: exported,
|
|
1143
1130
|
submissions: rows,
|
|
1144
1131
|
batchProducers,
|
|
@@ -1151,7 +1138,7 @@ export const runChaosPlan = Effect.fn("Chaos.runChaosPlan")(function* (
|
|
|
1151
1138
|
.join("; ");
|
|
1152
1139
|
return yield* ChaosConvergenceFailure.make({
|
|
1153
1140
|
seed: plan.seed,
|
|
1154
|
-
message: `invariants failed for ${
|
|
1141
|
+
message: `invariants failed for ${threadId} (${kind}): ${failed}`,
|
|
1155
1142
|
});
|
|
1156
1143
|
}
|
|
1157
1144
|
if (deskInPlay) {
|
|
@@ -1159,7 +1146,7 @@ export const runChaosPlan = Effect.fn("Chaos.runChaosPlan")(function* (
|
|
|
1159
1146
|
}
|
|
1160
1147
|
laneReports.push(
|
|
1161
1148
|
ChaosLaneReport.make({
|
|
1162
|
-
|
|
1149
|
+
threadId,
|
|
1163
1150
|
kind,
|
|
1164
1151
|
submissionCount: rows.length,
|
|
1165
1152
|
verified: report.ok,
|
|
@@ -1168,18 +1155,18 @@ export const runChaosPlan = Effect.fn("Chaos.runChaosPlan")(function* (
|
|
|
1168
1155
|
});
|
|
1169
1156
|
|
|
1170
1157
|
for (const lane of lanes) {
|
|
1171
|
-
yield*
|
|
1172
|
-
// Delegation lanes: verify every materialized child
|
|
1158
|
+
yield* verifyThread(lane.threadId, lane.kind, lane.deskInPlay);
|
|
1159
|
+
// Delegation lanes: verify every materialized child Thread too.
|
|
1173
1160
|
for (const flatIndex of lane.submissionIndexes) {
|
|
1174
1161
|
const receipt = states[flatIndex]?.receipt;
|
|
1175
1162
|
if (receipt === undefined) continue;
|
|
1176
|
-
const child = lane.
|
|
1163
|
+
const child = lane.childThreadOf(receipt);
|
|
1177
1164
|
if (child === undefined) continue;
|
|
1178
1165
|
const childExport = yield* Effect.exit(
|
|
1179
|
-
store.export(
|
|
1166
|
+
store.export(ThreadExportRequest.make({ threadId: child })),
|
|
1180
1167
|
);
|
|
1181
1168
|
if (Exit.isSuccess(childExport) && childExport.value.records.length > 0) {
|
|
1182
|
-
yield*
|
|
1169
|
+
yield* verifyThread(child, "plain", false);
|
|
1183
1170
|
}
|
|
1184
1171
|
}
|
|
1185
1172
|
}
|
|
@@ -73,11 +73,11 @@ export const docContentToolkitLayer = DocContentToolkit.toLayer({
|
|
|
73
73
|
// ---------------------------------------------------------------------------
|
|
74
74
|
// Deterministic corpus. Every body deliberately embeds BOTH a secret marker
|
|
75
75
|
// and a distinctive body phrase: the tests assert that neither ever reaches
|
|
76
|
-
// the parent
|
|
76
|
+
// the parent Thread, the parent prompts, or a redacted preview — only
|
|
77
77
|
// the bounded summary crosses the delegation boundary (SUB-015, SEC-008).
|
|
78
78
|
// ---------------------------------------------------------------------------
|
|
79
79
|
|
|
80
|
-
/** Never allowed outside a child
|
|
80
|
+
/** Never allowed outside a child Thread or an unredacted fixture value. */
|
|
81
81
|
export const docsDocumentBodySecret = "docs-vault-secret-771";
|
|
82
82
|
|
|
83
83
|
const decodeDocumentId = Schema.decodeSync(ResearchDocumentId);
|
|
@@ -109,7 +109,7 @@ const corpusEntries = new Map<string, CorpusEntry>(
|
|
|
109
109
|
document: ResearchDocument.make({
|
|
110
110
|
documentId: decodeDocumentId(entry.documentId),
|
|
111
111
|
title: entry.title,
|
|
112
|
-
body: `${entry.bodyPhrase}: internal working notes. ${docsDocumentBodySecret}. ${entry.summary} Raw notes stay inside the child
|
|
112
|
+
body: `${entry.bodyPhrase}: internal working notes. ${docsDocumentBodySecret}. ${entry.summary} Raw notes stay inside the child Thread.`,
|
|
113
113
|
}),
|
|
114
114
|
bodyPhrase: entry.bodyPhrase,
|
|
115
115
|
summary: entry.summary,
|
|
@@ -180,7 +180,7 @@ export const documentSummaryFor = (documentId: string): DocumentSummary =>
|
|
|
180
180
|
export const encodedDocumentSummary = (documentId: string): string =>
|
|
181
181
|
JSON.stringify(Schema.encodeSync(DocumentSummary)(documentSummaryFor(documentId)));
|
|
182
182
|
|
|
183
|
-
export const DocSummarizer = Agent.
|
|
183
|
+
export const DocSummarizer = Agent.make("doc-summarizer", {
|
|
184
184
|
input: SummaryBrief,
|
|
185
185
|
output: DocumentSummary,
|
|
186
186
|
instructions:
|
|
@@ -200,7 +200,7 @@ export const DocSummarizer = Agent.define("doc-summarizer", {
|
|
|
200
200
|
// Delegation Definition: the coordinator sees exactly one Tool with explicit
|
|
201
201
|
// projections and finite bounds. `projectResult` is the declassification
|
|
202
202
|
// boundary (SUB-015): only the bounded summary crosses; the fetched body —
|
|
203
|
-
// secret marker included — stays in the child
|
|
203
|
+
// secret marker included — stays in the child Thread.
|
|
204
204
|
// ---------------------------------------------------------------------------
|
|
205
205
|
|
|
206
206
|
export class SummaryRequest extends Schema.Class<SummaryRequest>("SummaryRequest")({
|
|
@@ -300,7 +300,7 @@ export const docsMissionConfidentialMarker = "docs-mission-dossier-42f";
|
|
|
300
300
|
|
|
301
301
|
export const DocsResearcherToolkit = Toolkit.make(delegateDocumentSummary.tool);
|
|
302
302
|
|
|
303
|
-
export const DocsResearcher = Agent.
|
|
303
|
+
export const DocsResearcher = Agent.make("docs-researcher", {
|
|
304
304
|
input: ResearchRequest,
|
|
305
305
|
output: ResearchDigest,
|
|
306
306
|
instructions: [
|
|
@@ -322,7 +322,7 @@ export const DocsResearcher = Agent.define("docs-researcher", {
|
|
|
322
322
|
|
|
323
323
|
/** The default two-document research mission. */
|
|
324
324
|
export const researchMissionRequest = ResearchRequest.make({
|
|
325
|
-
question: `Summarize the durability and subagent notes; keep ${docsMissionConfidentialMarker} inside the coordinator
|
|
325
|
+
question: `Summarize the durability and subagent notes; keep ${docsMissionConfidentialMarker} inside the coordinator thread.`,
|
|
326
326
|
documentIds: researchCorpusDocumentIds,
|
|
327
327
|
});
|
|
328
328
|
|
|
@@ -6,7 +6,7 @@ import {
|
|
|
6
6
|
type RedactedPreview,
|
|
7
7
|
type RedactionError,
|
|
8
8
|
} from "@effect-agent/capabilities";
|
|
9
|
-
import { Agent, type
|
|
9
|
+
import { Agent, type ThreadId } from "@effect-agent/core";
|
|
10
10
|
import {
|
|
11
11
|
DefinitionDigests,
|
|
12
12
|
DeploymentId,
|
|
@@ -17,7 +17,7 @@ import {
|
|
|
17
17
|
type DurableSubmitOptions,
|
|
18
18
|
type IdempotencyKey,
|
|
19
19
|
type ResolvedBinding,
|
|
20
|
-
} from "@effect-agent/
|
|
20
|
+
} from "@effect-agent/thread";
|
|
21
21
|
import type { Crypto } from "effect";
|
|
22
22
|
import { Effect, Layer, Ref, Schema, Stream } from "effect";
|
|
23
23
|
import { LanguageModel, Model, type Response } from "effect/unstable/ai";
|
|
@@ -76,10 +76,10 @@ export const docsSummarizerDigests = DefinitionDigests.make({
|
|
|
76
76
|
|
|
77
77
|
/** Durable admission options for one docs-researcher Submission on one mission lane. */
|
|
78
78
|
export const docsResearcherSubmitOptions = (
|
|
79
|
-
|
|
79
|
+
threadId: ThreadId,
|
|
80
80
|
idempotencyKey: IdempotencyKey,
|
|
81
81
|
): DurableSubmitOptions => ({
|
|
82
|
-
|
|
82
|
+
threadId,
|
|
83
83
|
principal: docsResearcherPrincipal,
|
|
84
84
|
idempotencyKey,
|
|
85
85
|
definitions: docsCoordinatorDigests,
|
|
@@ -101,15 +101,13 @@ const scriptedUsage = { inputTokens: { total: 96 }, outputTokens: { total: 64 }
|
|
|
101
101
|
const summaryDelegationParts = (
|
|
102
102
|
documentIds: ReadonlyArray<string>,
|
|
103
103
|
): ReadonlyArray<Response.StreamPartEncoded> => [
|
|
104
|
-
...documentIds.map(
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
}),
|
|
112
|
-
),
|
|
104
|
+
...documentIds.map((documentId): Response.StreamPartEncoded => ({
|
|
105
|
+
type: "tool-call",
|
|
106
|
+
id: summarizeCallId(documentId),
|
|
107
|
+
name: "delegate_document_summary",
|
|
108
|
+
params: { documentId },
|
|
109
|
+
providerExecuted: false,
|
|
110
|
+
})),
|
|
113
111
|
{ type: "finish", reason: "tool-calls", usage: scriptedUsage },
|
|
114
112
|
];
|
|
115
113
|
|
|
@@ -298,7 +296,7 @@ const encodeResearchDocument = Schema.encodeEffect(ResearchDocument);
|
|
|
298
296
|
/**
|
|
299
297
|
* The audit-surface preview of one fetched document: the raw document —
|
|
300
298
|
* secret marker and all — passes through the configured structural `Redactor`
|
|
301
|
-
* before anything may quote it outside the child
|
|
299
|
+
* before anything may quote it outside the child Thread (SEC-008,
|
|
302
300
|
* CAP-013). Tests assert the preview keeps shape but no scalar content.
|
|
303
301
|
*/
|
|
304
302
|
export const redactedDocumentPreview = Effect.fn("DocsResearcher.redactedDocumentPreview")(
|
|
@@ -7,7 +7,7 @@
|
|
|
7
7
|
//
|
|
8
8
|
// 1. MCP discovery and Agent authoring do not meet in the type system. The
|
|
9
9
|
// connector's validated Toolkit arrives as `Toolkit.Any`, while
|
|
10
|
-
// `Agent.
|
|
10
|
+
// `Agent.make` needs the statically typed toolkit — so the binding
|
|
11
11
|
// between "what discovery served" and "what the child was authored
|
|
12
12
|
// against" had to be re-proved by hand (`assertDiscoveryMatchesAuthoredToolkit`
|
|
13
13
|
// re-deriving `Tool.getJsonSchema` on both sides). A framework helper that
|
|
@@ -150,7 +150,7 @@ export const TravelPlannerToolkitLayer = TravelPlannerToolkit.toLayer({
|
|
|
150
150
|
search_activities: (query) => Effect.flatMap(ActivityCatalog, (catalog) => catalog.search(query)),
|
|
151
151
|
});
|
|
152
152
|
|
|
153
|
-
export const TravelPlanner = Agent.
|
|
153
|
+
export const TravelPlanner = Agent.make("travel-planner", {
|
|
154
154
|
input: TripRequest,
|
|
155
155
|
output: TravelPlan,
|
|
156
156
|
instructions: (input) =>
|
|
@@ -1,4 +1,5 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { ThreadId, IdGenerator, RunId, TurnId } from "@effect-agent/core";
|
|
2
|
+
import { ThreadHistory, RunContextPreparationPassthrough } from "@effect-agent/engine";
|
|
2
3
|
import { Context, Deferred, Effect, Layer, Option, Ref, Schema } from "effect";
|
|
3
4
|
|
|
4
5
|
import {
|
|
@@ -405,12 +406,12 @@ export const TravelGuidanceLayer = Layer.succeed(
|
|
|
405
406
|
export const DeterministicIdGeneratorLayer = Layer.effect(
|
|
406
407
|
IdGenerator,
|
|
407
408
|
Effect.gen(function* () {
|
|
408
|
-
const
|
|
409
|
+
const thread = yield* Ref.make(0);
|
|
409
410
|
const run = yield* Ref.make(0);
|
|
410
411
|
const turn = yield* Ref.make(0);
|
|
411
412
|
return IdGenerator.of({
|
|
412
|
-
|
|
413
|
-
Effect.map((n) => Schema.decodeSync(
|
|
413
|
+
nextThreadId: Ref.updateAndGet(thread, (n) => n + 1).pipe(
|
|
414
|
+
Effect.map((n) => Schema.decodeSync(ThreadId)(`thread-${n}`)),
|
|
414
415
|
),
|
|
415
416
|
nextRunId: Ref.updateAndGet(run, (n) => n + 1).pipe(
|
|
416
417
|
Effect.map((n) => Schema.decodeSync(RunId)(`run-${n}`)),
|
|
@@ -422,6 +423,8 @@ export const DeterministicIdGeneratorLayer = Layer.effect(
|
|
|
422
423
|
}),
|
|
423
424
|
);
|
|
424
425
|
export const TravelPlannerRuntimeLayer = Layer.mergeAll(
|
|
426
|
+
RunContextPreparationPassthrough,
|
|
427
|
+
ThreadHistory.layerTransient,
|
|
425
428
|
TravelPlannerToolkitLayer,
|
|
426
429
|
FlightCatalogLayer,
|
|
427
430
|
LodgingCatalogLayer,
|
|
@@ -75,7 +75,7 @@ export const TravelPlannerPhase2ToolkitLayer = TravelPlannerPhase2Toolkit.toLaye
|
|
|
75
75
|
Effect.flatMap(ItineraryHoldGateway, (gateway) => gateway.hold(request)),
|
|
76
76
|
});
|
|
77
77
|
|
|
78
|
-
export const TravelPlannerPhase2 = Agent.
|
|
78
|
+
export const TravelPlannerPhase2 = Agent.make("travel-planner-phase-2", {
|
|
79
79
|
input: TripRequest,
|
|
80
80
|
output: TravelPlan,
|
|
81
81
|
instructions: (input) =>
|