effect-agent 0.1.0-beta.159 → 0.1.0-beta.160
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/{Admin-DWDYlvvZ.d.mts → Admin-Dirux8PR.d.mts} +9 -2
- package/dist/{DurableAgentRuntime-C7A5guvq.mjs → DurableAgentRuntime-pr7cCaZw.mjs} +216 -55
- package/dist/DurableAgentRuntime-pr7cCaZw.mjs.map +1 -0
- package/dist/{EventSource-BfNZBG4A.d.mts → EventSource-DOXB0RJR.d.mts} +2 -2
- package/dist/{PreparedInputAdmission-VStpn8u2.d.mts → PreparedInputAdmission-B7r401xJ.d.mts} +3 -3
- package/dist/{RunJournal-ixFRCyW3.mjs → RunJournal-CA2bsvHn.mjs} +8 -5
- package/dist/RunJournal-CA2bsvHn.mjs.map +1 -0
- package/dist/{Subscription-Bx1ZRRVS.d.mts → Subscription-B3ScCBaN.d.mts} +2 -2
- package/dist/{SubscriptionInput-B5k5vb1S.d.mts → SubscriptionInput-BP5WVsX_.d.mts} +2 -2
- package/dist/{WorkerAdmission-N9hZV85Q.mjs → WorkerAdmission-Cmcxd6nS.mjs} +2 -2
- package/dist/{WorkerAdmission-N9hZV85Q.mjs.map → WorkerAdmission-Cmcxd6nS.mjs.map} +1 -1
- package/dist/durable/Admin.d.mts +1 -1
- package/dist/durable/AgentRegistration.d.mts +1 -1
- package/dist/durable/Certification.d.mts +1 -1
- package/dist/durable/CommittedActivity.d.mts +1 -1
- package/dist/durable/DurableAgentRuntime.d.mts +1 -1
- package/dist/durable/DurableAgentRuntime.mjs +2 -2
- package/dist/durable/EventSource.d.mts +1 -1
- package/dist/durable/GitHubWorkflowSource.d.mts +3 -3
- package/dist/durable/LifecyclePublication.d.mts +1 -1
- package/dist/durable/MessageDelivery.d.mts +3 -3
- package/dist/durable/MessageDeliveryStoreConformance.d.mts +1 -1
- package/dist/durable/PersistentHistory.d.mts +1 -1
- package/dist/durable/PersistentHistory.mjs +1 -1
- package/dist/durable/PreparedInputAdmission.d.mts +1 -1
- package/dist/durable/Recovery.d.mts +1 -1
- package/dist/durable/RunJournal.d.mts +1 -1
- package/dist/durable/RunJournal.mjs +1 -1
- package/dist/durable/Scheduling.d.mts +1 -1
- package/dist/durable/SqlMemoryStore.d.mts +3 -0
- package/dist/durable/SqlMemoryStore.mjs +3 -2
- package/dist/durable/SqlMemoryStore.mjs.map +1 -1
- package/dist/durable/SubmissionLedger.d.mts +1 -1
- package/dist/durable/SubmissionLedgerConformance.d.mts +1 -1
- package/dist/durable/SubmissionLedgerConformance.mjs +1 -1
- package/dist/durable/SubmissionStatus.d.mts +1 -1
- package/dist/durable/Subscription.d.mts +1 -1
- package/dist/durable/SubscriptionInput.d.mts +1 -1
- package/dist/durable/SubscriptionStoreConformance.d.mts +1 -1
- package/dist/durable/SubscriptionTools.d.mts +2 -2
- package/dist/durable/SubscriptionTransition.d.mts +1 -1
- package/dist/durable/Subscriptions.d.mts +5 -5
- package/dist/durable/ThreadContextHistory.d.mts +1 -1
- package/dist/durable/ThreadInvariants.d.mts +1 -1
- package/dist/durable/ThreadInvariants.mjs +1 -1
- package/dist/durable/ThreadProjectionMaintenance.d.mts +1 -1
- package/dist/durable/ThreadStore.d.mts +1 -1
- package/dist/durable/ThreadStore.mjs +1 -1
- package/dist/durable/ThreadStoreConformance.d.mts +1 -1
- package/dist/durable/ThreadStoreConformance.mjs +1 -1
- package/dist/durable/WorkerAdmission.d.mts +1 -1
- package/dist/durable/WorkerAdmission.mjs +1 -1
- package/dist/durable/WorkerHost.d.mts +1 -1
- package/dist/index.d.mts +5 -5
- package/dist/index.mjs +3 -3
- package/package.json +1 -1
- package/src/durable/DurableAgentRuntime.ts +371 -84
- package/src/durable/RunJournal.ts +16 -4
- package/src/durable/SqlMemoryStore.ts +7 -3
- package/src/durable/internal/journal-checkpoint.ts +57 -3
- package/dist/DurableAgentRuntime-C7A5guvq.mjs.map +0 -1
- package/dist/RunJournal-ixFRCyW3.mjs.map +0 -1
|
@@ -159,8 +159,10 @@ import {
|
|
|
159
159
|
JournalCheckpointSeed,
|
|
160
160
|
RecoveryCheckpointState,
|
|
161
161
|
RecoveryCheckpointContents,
|
|
162
|
+
ThreadContextCheckpoint,
|
|
162
163
|
RECOVERY_ENGINE_VERSION,
|
|
163
164
|
checkpointSuffixCompatible,
|
|
165
|
+
makeThreadContextCertificate,
|
|
164
166
|
} from "./internal/journal-checkpoint.ts";
|
|
165
167
|
import { makeJournalMetadata, type JournalMetadata } from "./internal/journal-metadata.ts";
|
|
166
168
|
import { makeMessagingRuntime } from "./internal/messaging-host.ts";
|
|
@@ -359,6 +361,8 @@ import {
|
|
|
359
361
|
LoadCheckpointRequest,
|
|
360
362
|
ThreadCheckpoint,
|
|
361
363
|
SaveRecoveryCheckpointRequest,
|
|
364
|
+
getRecord,
|
|
365
|
+
getRunInput,
|
|
362
366
|
} from "./ThreadStore.ts";
|
|
363
367
|
import { PreparedToolCallEvidence, ToolReconciler } from "./ToolReconciler.ts";
|
|
364
368
|
import { WakeScheduler } from "./WakeScheduler.ts";
|
|
@@ -1277,6 +1281,7 @@ const make = Effect.fn("DurableAgentRuntime.make")(function* (
|
|
|
1277
1281
|
readonly through: CanonicalSequence;
|
|
1278
1282
|
readonly runId: RunId;
|
|
1279
1283
|
readonly seedThrough: CanonicalSequence | undefined;
|
|
1284
|
+
readonly contextThrough: CanonicalSequence | undefined;
|
|
1280
1285
|
readonly journal: RunJournalProjection;
|
|
1281
1286
|
readonly boundaries: ReadonlyArray<JournalBoundary>;
|
|
1282
1287
|
}
|
|
@@ -1563,54 +1568,41 @@ const make = Effect.fn("DurableAgentRuntime.make")(function* (
|
|
|
1563
1568
|
);
|
|
1564
1569
|
});
|
|
1565
1570
|
|
|
1566
|
-
|
|
1567
|
-
const recoveryView = Effect.fn("DurableAgentRuntime.recoveryView")(function* (
|
|
1571
|
+
const loadRecoveryCheckpoint = Effect.fn("DurableAgentRuntime.loadRecoveryCheckpoint")(function* (
|
|
1568
1572
|
threadId: ThreadId,
|
|
1569
1573
|
throughSequence: CanonicalSequence,
|
|
1570
|
-
|
|
1571
|
-
|
|
1572
|
-
): Effect.fn.Return<
|
|
1573
|
-
{
|
|
1574
|
-
readonly canonical: Stream.Stream<
|
|
1575
|
-
CanonicalRecordEnvelope,
|
|
1576
|
-
ThreadStoreError | ThreadNotMaterialized
|
|
1577
|
-
>;
|
|
1578
|
-
readonly seed?: JournalCheckpointSeed;
|
|
1579
|
-
},
|
|
1580
|
-
ThreadStoreError | ThreadNotMaterialized
|
|
1581
|
-
> {
|
|
1582
|
-
const full = { canonical: canonicalRange(threadId, throughSequence) };
|
|
1583
|
-
|
|
1584
|
-
if (store.recoveryCheckpoints === undefined) return full;
|
|
1574
|
+
) {
|
|
1575
|
+
if (store.recoveryCheckpoints === undefined) return Option.none();
|
|
1585
1576
|
|
|
1586
1577
|
const loaded = yield* store.recoveryCheckpoints
|
|
1587
1578
|
.load(LoadCheckpointRequest.make({ threadId, atOrBeforeSequence: throughSequence }))
|
|
1588
1579
|
.pipe(Effect.catchTag("CheckpointRejected", () => Effect.succeed(Option.none())));
|
|
1589
1580
|
|
|
1590
|
-
if (Option.isNone(loaded)) return
|
|
1581
|
+
if (Option.isNone(loaded)) return Option.none();
|
|
1591
1582
|
const checkpoint = loaded.value;
|
|
1592
1583
|
|
|
1593
1584
|
if (
|
|
1594
1585
|
checkpoint.engineVersion !== RECOVERY_ENGINE_VERSION ||
|
|
1595
1586
|
checkpoint.threadId !== threadId ||
|
|
1596
|
-
checkpoint.throughSequence > throughSequence
|
|
1597
|
-
throughSequence - checkpoint.throughSequence > 4_096
|
|
1587
|
+
checkpoint.throughSequence > throughSequence
|
|
1598
1588
|
)
|
|
1599
|
-
return
|
|
1589
|
+
return Option.none();
|
|
1600
1590
|
|
|
1601
1591
|
const decoded = yield* Schema.decodeUnknownEffect(RecoveryCheckpointContents)(
|
|
1602
1592
|
checkpoint.state,
|
|
1603
1593
|
).pipe(Effect.option);
|
|
1604
1594
|
|
|
1605
|
-
if (Option.isNone(decoded)) return
|
|
1595
|
+
if (Option.isNone(decoded)) return Option.none();
|
|
1606
1596
|
const { state, digest } = decoded.value;
|
|
1607
1597
|
|
|
1608
1598
|
if (
|
|
1609
|
-
|
|
1610
|
-
state.
|
|
1611
|
-
|
|
1599
|
+
(state.seed === undefined && state.context === undefined) ||
|
|
1600
|
+
state.records.some(
|
|
1601
|
+
(record) => record.threadId !== threadId || record.sequence > checkpoint.throughSequence,
|
|
1602
|
+
) ||
|
|
1603
|
+
(state.context !== undefined && state.context.throughSequence !== checkpoint.throughSequence)
|
|
1612
1604
|
)
|
|
1613
|
-
return
|
|
1605
|
+
return Option.none();
|
|
1614
1606
|
|
|
1615
1607
|
const encoded = yield* Schema.encodeEffect(RecoveryCheckpointState)(state).pipe(
|
|
1616
1608
|
Effect.mapError((cause) =>
|
|
@@ -1632,7 +1624,15 @@ const make = Effect.fn("DurableAgentRuntime.make")(function* (
|
|
|
1632
1624
|
),
|
|
1633
1625
|
);
|
|
1634
1626
|
|
|
1635
|
-
if (digest !== actualDigest) return
|
|
1627
|
+
if (digest !== actualDigest) return Option.none();
|
|
1628
|
+
|
|
1629
|
+
if (state.context !== undefined) {
|
|
1630
|
+
const prompt = yield* Schema.decodeUnknownEffect(Prompt.Prompt)(state.context.prompt).pipe(
|
|
1631
|
+
Effect.option,
|
|
1632
|
+
);
|
|
1633
|
+
|
|
1634
|
+
if (Option.isNone(prompt)) return Option.none();
|
|
1635
|
+
}
|
|
1636
1636
|
|
|
1637
1637
|
const owner = yield* ledger
|
|
1638
1638
|
.lookup(SubmissionLookupById.make({ submissionId: state.submissionId }))
|
|
@@ -1652,6 +1652,130 @@ const make = Effect.fn("DurableAgentRuntime.make")(function* (
|
|
|
1652
1652
|
owner.value.agentDigests.agent !== checkpoint.agentDefinitionDigest ||
|
|
1653
1653
|
owner.value.agentDigests.model !== checkpoint.modelDigest ||
|
|
1654
1654
|
owner.value.agentDigests.tools !== checkpoint.toolDigest
|
|
1655
|
+
)
|
|
1656
|
+
return Option.none();
|
|
1657
|
+
|
|
1658
|
+
return Option.some({ checkpoint, state });
|
|
1659
|
+
});
|
|
1660
|
+
|
|
1661
|
+
const retainThreadIdentity = ({ record }: CanonicalRecordEnvelope): boolean =>
|
|
1662
|
+
record.payload._tag === "ThreadCreated" ||
|
|
1663
|
+
record.payload._tag === "SubagentLineageRecorded" ||
|
|
1664
|
+
record.payload._tag === "WorkerOriginRecorded" ||
|
|
1665
|
+
record.recordId.startsWith("subagent-lineage:");
|
|
1666
|
+
|
|
1667
|
+
/** Late/foreign evidence and new compactions require the original canonical proof. */
|
|
1668
|
+
const contextSuffixCompatible = (
|
|
1669
|
+
records: ReadonlyArray<CanonicalRecordEnvelope>,
|
|
1670
|
+
submissionId: SubmissionId,
|
|
1671
|
+
): boolean => {
|
|
1672
|
+
const runId = runIdForSubmission(submissionId);
|
|
1673
|
+
const certificate = makeThreadContextCertificate();
|
|
1674
|
+
|
|
1675
|
+
for (const entry of records) {
|
|
1676
|
+
const { payload, recordId } = entry.record;
|
|
1677
|
+
|
|
1678
|
+
if (payload._tag === "CompactionCreated" || retainThreadIdentity(entry)) return false;
|
|
1679
|
+
if ("runId" in payload && payload.runId !== undefined) {
|
|
1680
|
+
if (payload.runId !== runId) return false;
|
|
1681
|
+
} else if (payload._tag === "AbortRequested") {
|
|
1682
|
+
if (
|
|
1683
|
+
payload.submissionId !== submissionId ||
|
|
1684
|
+
recordId !== submissionAbortRecordId(submissionId)
|
|
1685
|
+
)
|
|
1686
|
+
return false;
|
|
1687
|
+
} else if (payload._tag === "SubmissionSettled") {
|
|
1688
|
+
if (
|
|
1689
|
+
payload.submissionId !== submissionId ||
|
|
1690
|
+
recordId !== submissionSettlementRecordId(submissionId)
|
|
1691
|
+
)
|
|
1692
|
+
return false;
|
|
1693
|
+
} else return false;
|
|
1694
|
+
certificate.add(entry);
|
|
1695
|
+
}
|
|
1696
|
+
|
|
1697
|
+
return certificate.isValid();
|
|
1698
|
+
};
|
|
1699
|
+
|
|
1700
|
+
/** Run seeds retain their owner; certified Thread context serves only provably later Runs. */
|
|
1701
|
+
const recoveryView = Effect.fn("DurableAgentRuntime.recoveryView")(function* (
|
|
1702
|
+
threadId: ThreadId,
|
|
1703
|
+
throughSequence: CanonicalSequence,
|
|
1704
|
+
submissionIds: ReadonlyArray<SubmissionId>,
|
|
1705
|
+
journalOwner?: RunId,
|
|
1706
|
+
): Effect.fn.Return<
|
|
1707
|
+
{
|
|
1708
|
+
readonly canonical: Stream.Stream<
|
|
1709
|
+
CanonicalRecordEnvelope,
|
|
1710
|
+
ThreadStoreError | ThreadNotMaterialized
|
|
1711
|
+
>;
|
|
1712
|
+
readonly seed?: JournalCheckpointSeed;
|
|
1713
|
+
readonly context?: ThreadContextCheckpoint;
|
|
1714
|
+
},
|
|
1715
|
+
ThreadStoreError | ThreadNotMaterialized
|
|
1716
|
+
> {
|
|
1717
|
+
const full = { canonical: canonicalRange(threadId, throughSequence) };
|
|
1718
|
+
const loaded = yield* loadRecoveryCheckpoint(threadId, throughSequence);
|
|
1719
|
+
|
|
1720
|
+
if (Option.isNone(loaded)) return full;
|
|
1721
|
+
const { checkpoint, state } = loaded.value;
|
|
1722
|
+
|
|
1723
|
+
if (throughSequence - checkpoint.throughSequence > 4_096) return full;
|
|
1724
|
+
|
|
1725
|
+
const submissionId = submissionIds.length === 1 ? submissionIds[0] : undefined;
|
|
1726
|
+
|
|
1727
|
+
if (
|
|
1728
|
+
state.context !== undefined &&
|
|
1729
|
+
submissionId !== undefined &&
|
|
1730
|
+
!submissionId.includes(":") &&
|
|
1731
|
+
(journalOwner === undefined || journalOwner === runIdForSubmission(submissionId))
|
|
1732
|
+
) {
|
|
1733
|
+
const runId = runIdForSubmission(submissionId);
|
|
1734
|
+
|
|
1735
|
+
const input = yield* getRunInput({ threadId, runId }).pipe(
|
|
1736
|
+
Effect.provideService(ThreadStore, store),
|
|
1737
|
+
);
|
|
1738
|
+
|
|
1739
|
+
const controls = yield* Effect.forEach(
|
|
1740
|
+
[
|
|
1741
|
+
submissionInputRecordId(submissionId),
|
|
1742
|
+
submissionAbortRecordId(submissionId),
|
|
1743
|
+
submissionSettlementRecordId(submissionId),
|
|
1744
|
+
runStartedRecordId(runId),
|
|
1745
|
+
runCompletedRecordId(runId),
|
|
1746
|
+
],
|
|
1747
|
+
(recordId) =>
|
|
1748
|
+
getRecord({ threadId, recordId }).pipe(Effect.provideService(ThreadStore, store)),
|
|
1749
|
+
);
|
|
1750
|
+
|
|
1751
|
+
const fresh = [input, ...controls].every(
|
|
1752
|
+
(record) =>
|
|
1753
|
+
Option.isNone(record) ||
|
|
1754
|
+
(record.value.sequence > checkpoint.throughSequence &&
|
|
1755
|
+
record.value.sequence <= throughSequence),
|
|
1756
|
+
);
|
|
1757
|
+
|
|
1758
|
+
if (fresh) {
|
|
1759
|
+
const suffix = yield* Stream.runCollect(
|
|
1760
|
+
canonicalRange(threadId, throughSequence, checkpoint.throughSequence),
|
|
1761
|
+
);
|
|
1762
|
+
|
|
1763
|
+
if (contextSuffixCompatible(suffix, submissionId))
|
|
1764
|
+
return {
|
|
1765
|
+
canonical: Stream.fromIterable([
|
|
1766
|
+
...state.records.filter(retainThreadIdentity),
|
|
1767
|
+
...suffix,
|
|
1768
|
+
]),
|
|
1769
|
+
context: state.context,
|
|
1770
|
+
};
|
|
1771
|
+
}
|
|
1772
|
+
}
|
|
1773
|
+
|
|
1774
|
+
if (
|
|
1775
|
+
state.seed === undefined ||
|
|
1776
|
+
!submissionIds.every((id) => state.submissionIds.includes(id)) ||
|
|
1777
|
+
state.seed.runId !== runIdForSubmission(state.submissionId) ||
|
|
1778
|
+
(journalOwner !== undefined && state.seed.runId !== journalOwner)
|
|
1655
1779
|
)
|
|
1656
1780
|
return full;
|
|
1657
1781
|
const replacement = state.seed.compaction;
|
|
@@ -1663,9 +1787,6 @@ const make = Effect.fn("DurableAgentRuntime.make")(function* (
|
|
|
1663
1787
|
replacement.record.payload.kind === "clear-tool-results" ||
|
|
1664
1788
|
replacement.record.payload.coversThrough < state.seed.throughSequence ||
|
|
1665
1789
|
replacement.record.payload.coversThrough >= replacement.sequence ||
|
|
1666
|
-
state.records.some(
|
|
1667
|
-
(record) => record.threadId !== threadId || record.sequence > checkpoint.throughSequence,
|
|
1668
|
-
) ||
|
|
1669
1790
|
!state.records.some(
|
|
1670
1791
|
(record) =>
|
|
1671
1792
|
record.sequence === replacement.sequence &&
|
|
@@ -1683,6 +1804,165 @@ const make = Effect.fn("DurableAgentRuntime.make")(function* (
|
|
|
1683
1804
|
return { canonical: Stream.fromIterable([...state.records, ...suffix]), seed: state.seed };
|
|
1684
1805
|
});
|
|
1685
1806
|
|
|
1807
|
+
const persistRecoveryCheckpoint = Effect.fn("DurableAgentRuntime.persistRecoveryCheckpoint")(
|
|
1808
|
+
function* (
|
|
1809
|
+
ctx: AttemptAppendContext,
|
|
1810
|
+
submission: SubmissionSnapshot,
|
|
1811
|
+
state: RecoveryCheckpointState,
|
|
1812
|
+
tail: { readonly sequence: CanonicalSequence; readonly digest: Digest },
|
|
1813
|
+
createdAt: DateTime.Utc,
|
|
1814
|
+
): Effect.fn.Return<void, DurableWorkerFailure> {
|
|
1815
|
+
if (store.recoveryCheckpoints === undefined) return;
|
|
1816
|
+
|
|
1817
|
+
const encoded = yield* Schema.encodeEffect(RecoveryCheckpointState)(state).pipe(
|
|
1818
|
+
Effect.option,
|
|
1819
|
+
);
|
|
1820
|
+
|
|
1821
|
+
if (Option.isNone(encoded)) return;
|
|
1822
|
+
const digest = yield* withCrypto(digestJson(encoded.value));
|
|
1823
|
+
|
|
1824
|
+
// Serialization removes shared in-memory references before applying persisted JSON bounds.
|
|
1825
|
+
const contents = yield* Schema.encodeEffect(
|
|
1826
|
+
Schema.fromJsonString(RecoveryCheckpointContents),
|
|
1827
|
+
)(RecoveryCheckpointContents.make({ state, digest })).pipe(
|
|
1828
|
+
Effect.flatMap(Schema.decodeUnknownEffect(Schema.fromJsonString(PersistedJson))),
|
|
1829
|
+
Effect.option,
|
|
1830
|
+
);
|
|
1831
|
+
|
|
1832
|
+
if (Option.isNone(contents)) return;
|
|
1833
|
+
|
|
1834
|
+
const checkpoint = ThreadCheckpoint.make({
|
|
1835
|
+
schemaVersion: 1,
|
|
1836
|
+
threadId: ctx.threadId,
|
|
1837
|
+
throughSequence: tail.sequence,
|
|
1838
|
+
tailDigest: tail.digest,
|
|
1839
|
+
engineVersion: RECOVERY_ENGINE_VERSION,
|
|
1840
|
+
agentDefinitionDigest: submission.agentDigests.agent,
|
|
1841
|
+
modelDigest: submission.agentDigests.model,
|
|
1842
|
+
toolDigest: submission.agentDigests.tools,
|
|
1843
|
+
state: contents.value,
|
|
1844
|
+
createdAt,
|
|
1845
|
+
});
|
|
1846
|
+
|
|
1847
|
+
yield* hit("checkpoint:before-save");
|
|
1848
|
+
yield* store.recoveryCheckpoints
|
|
1849
|
+
.save(SaveRecoveryCheckpointRequest.make({ checkpoint, producerEpoch: ctx.producerEpoch }))
|
|
1850
|
+
.pipe(Effect.catchTag("CheckpointRejected", () => Effect.void));
|
|
1851
|
+
yield* hit("checkpoint:after-save");
|
|
1852
|
+
},
|
|
1853
|
+
);
|
|
1854
|
+
|
|
1855
|
+
/** Refresh before settlement finalization, so completed processing includes the cache's cost. */
|
|
1856
|
+
const saveThreadContext = Effect.fn("DurableAgentRuntime.saveThreadContext")(function* (
|
|
1857
|
+
ctx: AttemptAppendContext,
|
|
1858
|
+
submission: SubmissionSnapshot,
|
|
1859
|
+
priorContext?: ThreadContextCheckpoint,
|
|
1860
|
+
): Effect.fn.Return<void, DurableWorkerFailure> {
|
|
1861
|
+
if (store.recoveryCheckpoints === undefined) return;
|
|
1862
|
+
const tail = yield* Ref.get(ctx.tailRef);
|
|
1863
|
+
const loaded = yield* loadRecoveryCheckpoint(ctx.threadId, tail.sequence);
|
|
1864
|
+
|
|
1865
|
+
// Start certification only after compaction has produced an eligible runtime checkpoint.
|
|
1866
|
+
if (Option.isNone(loaded)) return;
|
|
1867
|
+
const previous = loaded.value;
|
|
1868
|
+
|
|
1869
|
+
let identities: ReadonlyArray<CanonicalRecordEnvelope> =
|
|
1870
|
+
previous.state.records.filter(retainThreadIdentity);
|
|
1871
|
+
|
|
1872
|
+
let projection: RunJournalProjection | undefined;
|
|
1873
|
+
|
|
1874
|
+
if (priorContext !== undefined && tail.sequence - priorContext.throughSequence <= 4_096) {
|
|
1875
|
+
const suffix = yield* Stream.runCollect(
|
|
1876
|
+
canonicalRange(ctx.threadId, tail.sequence, priorContext.throughSequence),
|
|
1877
|
+
);
|
|
1878
|
+
|
|
1879
|
+
if (contextSuffixCompatible(suffix, submission.submissionId)) {
|
|
1880
|
+
projection = yield* projectRunJournalStream(
|
|
1881
|
+
Stream.fromIterable(suffix),
|
|
1882
|
+
undefined,
|
|
1883
|
+
undefined,
|
|
1884
|
+
undefined,
|
|
1885
|
+
undefined,
|
|
1886
|
+
priorContext,
|
|
1887
|
+
).pipe(Effect.catchTag("RunJournalError", () => Effect.succeed(undefined)));
|
|
1888
|
+
}
|
|
1889
|
+
}
|
|
1890
|
+
|
|
1891
|
+
if (projection === undefined) {
|
|
1892
|
+
const source = canonicalRange(ctx.threadId, tail.sequence);
|
|
1893
|
+
const certificate = makeThreadContextCertificate();
|
|
1894
|
+
const metadata = makeJournalMetadata(undefined);
|
|
1895
|
+
const retained: Array<CanonicalRecordEnvelope> = [];
|
|
1896
|
+
|
|
1897
|
+
yield* Stream.runForEach(source, (entry) =>
|
|
1898
|
+
Effect.sync(() => {
|
|
1899
|
+
certificate.add(entry);
|
|
1900
|
+
metadata.add(entry);
|
|
1901
|
+
if (retainThreadIdentity(entry) && retained.length <= 4_096) retained.push(entry);
|
|
1902
|
+
}),
|
|
1903
|
+
);
|
|
1904
|
+
if (!certificate.isValid() || retained.length > 4_096) return;
|
|
1905
|
+
identities = retained;
|
|
1906
|
+
projection = yield* projectRunJournalStream(
|
|
1907
|
+
source,
|
|
1908
|
+
undefined,
|
|
1909
|
+
undefined,
|
|
1910
|
+
undefined,
|
|
1911
|
+
metadata.snapshot(),
|
|
1912
|
+
).pipe(Effect.catchTag("RunJournalError", () => Effect.succeed(undefined)));
|
|
1913
|
+
}
|
|
1914
|
+
|
|
1915
|
+
if (projection === undefined) return;
|
|
1916
|
+
|
|
1917
|
+
const prompt = yield* Schema.encodeEffect(Prompt.Prompt)(projection.prompt).pipe(
|
|
1918
|
+
Effect.flatMap(decodePersisted),
|
|
1919
|
+
Effect.option,
|
|
1920
|
+
);
|
|
1921
|
+
|
|
1922
|
+
if (Option.isNone(prompt)) return;
|
|
1923
|
+
let seed: JournalCheckpointSeed | undefined;
|
|
1924
|
+
let records = identities;
|
|
1925
|
+
let submissionIds: ReadonlyArray<SubmissionId> = [submission.submissionId];
|
|
1926
|
+
|
|
1927
|
+
if (
|
|
1928
|
+
previous.state.submissionId === submission.submissionId &&
|
|
1929
|
+
previous.state.seed !== undefined &&
|
|
1930
|
+
tail.sequence - previous.checkpoint.throughSequence + previous.state.records.length <= 4_096
|
|
1931
|
+
) {
|
|
1932
|
+
const suffix = yield* Stream.runCollect(
|
|
1933
|
+
canonicalRange(ctx.threadId, tail.sequence, previous.checkpoint.throughSequence),
|
|
1934
|
+
);
|
|
1935
|
+
|
|
1936
|
+
if (checkpointSuffixCompatible(previous.state.seed, suffix, previous.state.records)) {
|
|
1937
|
+
seed = previous.state.seed;
|
|
1938
|
+
records = [...previous.state.records, ...suffix];
|
|
1939
|
+
submissionIds = previous.state.submissionIds;
|
|
1940
|
+
}
|
|
1941
|
+
}
|
|
1942
|
+
|
|
1943
|
+
yield* persistRecoveryCheckpoint(
|
|
1944
|
+
ctx,
|
|
1945
|
+
submission,
|
|
1946
|
+
RecoveryCheckpointState.make({
|
|
1947
|
+
schemaVersion: 2,
|
|
1948
|
+
policyAccountingVersion: 1,
|
|
1949
|
+
submissionId: submission.submissionId,
|
|
1950
|
+
submissionIds,
|
|
1951
|
+
...(seed === undefined ? {} : { seed }),
|
|
1952
|
+
context: ThreadContextCheckpoint.make({
|
|
1953
|
+
throughSequence: tail.sequence,
|
|
1954
|
+
prompt: prompt.value,
|
|
1955
|
+
...(projection.contextWindowId === undefined
|
|
1956
|
+
? {}
|
|
1957
|
+
: { contextWindowId: projection.contextWindowId }),
|
|
1958
|
+
}),
|
|
1959
|
+
records,
|
|
1960
|
+
}),
|
|
1961
|
+
tail,
|
|
1962
|
+
yield* DateTime.now,
|
|
1963
|
+
);
|
|
1964
|
+
});
|
|
1965
|
+
|
|
1686
1966
|
const readAllTolerant = Effect.fn("DurableAgentRuntime.readAllTolerant")(
|
|
1687
1967
|
(
|
|
1688
1968
|
threadId: ThreadId,
|
|
@@ -3353,6 +3633,7 @@ const make = Effect.fn("DurableAgentRuntime.make")(function* (
|
|
|
3353
3633
|
tokenRef: Ref.Ref<OwnershipToken>,
|
|
3354
3634
|
outcome: AttemptOutcome,
|
|
3355
3635
|
includeRunId: boolean,
|
|
3636
|
+
afterCanonical?: Effect.Effect<void, DurableWorkerFailure>,
|
|
3356
3637
|
): Effect.fn.Return<Settlement, DurableWorkerFailure> {
|
|
3357
3638
|
const submissionId = submission.submissionId;
|
|
3358
3639
|
const settlementId = submissionSettlementId(submissionId);
|
|
@@ -3420,6 +3701,7 @@ const make = Effect.fn("DurableAgentRuntime.make")(function* (
|
|
|
3420
3701
|
);
|
|
3421
3702
|
yield* hit("terminalize:after-canonical-append");
|
|
3422
3703
|
yield* notifyParentOfChildSettlement(submission, reserved.record);
|
|
3704
|
+
if (afterCanonical !== undefined) yield* afterCanonical;
|
|
3423
3705
|
|
|
3424
3706
|
const settlement = yield* ledger.finalizeSettlement(
|
|
3425
3707
|
SettlementFinalization.make({ submissionId, settlementId }),
|
|
@@ -4634,6 +4916,7 @@ const make = Effect.fn("DurableAgentRuntime.make")(function* (
|
|
|
4634
4916
|
canonical: Stream.Stream<CanonicalRecordEnvelope, ThreadStoreError | ThreadNotMaterialized>,
|
|
4635
4917
|
canonicalThrough: CanonicalSequence,
|
|
4636
4918
|
journalSeed: JournalCheckpointSeed | undefined,
|
|
4919
|
+
priorContext: ThreadContextCheckpoint | undefined,
|
|
4637
4920
|
journalMetadata: JournalMetadata | undefined,
|
|
4638
4921
|
lineage: AttemptLineage,
|
|
4639
4922
|
approvalDecisions: ReadonlyArray<ApprovalDecisionIntent>,
|
|
@@ -4654,7 +4937,8 @@ const make = Effect.fn("DurableAgentRuntime.make")(function* (
|
|
|
4654
4937
|
cached.threadId === ctx.threadId &&
|
|
4655
4938
|
cached.through === canonicalThrough &&
|
|
4656
4939
|
cached.runId === runId &&
|
|
4657
|
-
cached.seedThrough === journalSeed?.throughSequence
|
|
4940
|
+
cached.seedThrough === journalSeed?.throughSequence &&
|
|
4941
|
+
cached.contextThrough === priorContext?.throughSequence
|
|
4658
4942
|
? (boundaries.push(...cached.boundaries), cached.journal)
|
|
4659
4943
|
: yield* projectRunJournalStream(
|
|
4660
4944
|
canonical,
|
|
@@ -4662,6 +4946,7 @@ const make = Effect.fn("DurableAgentRuntime.make")(function* (
|
|
|
4662
4946
|
(boundary) => boundaries.push(boundary),
|
|
4663
4947
|
journalSeed,
|
|
4664
4948
|
journalMetadata,
|
|
4949
|
+
priorContext,
|
|
4665
4950
|
);
|
|
4666
4951
|
|
|
4667
4952
|
// Do not retain the metadata snapshot across model or Tool waits, including cache hits.
|
|
@@ -4673,6 +4958,7 @@ const make = Effect.fn("DurableAgentRuntime.make")(function* (
|
|
|
4673
4958
|
through: canonicalThrough,
|
|
4674
4959
|
runId,
|
|
4675
4960
|
seedThrough: journalSeed?.throughSequence,
|
|
4961
|
+
contextThrough: priorContext?.throughSequence,
|
|
4676
4962
|
journal,
|
|
4677
4963
|
boundaries,
|
|
4678
4964
|
};
|
|
@@ -4682,10 +4968,13 @@ const make = Effect.fn("DurableAgentRuntime.make")(function* (
|
|
|
4682
4968
|
if (store.recoveryCheckpoints === undefined) return;
|
|
4683
4969
|
const tail = yield* Ref.get(ctx.tailRef);
|
|
4684
4970
|
|
|
4685
|
-
const source =
|
|
4686
|
-
|
|
4687
|
-
|
|
4688
|
-
|
|
4971
|
+
const source =
|
|
4972
|
+
priorContext === undefined
|
|
4973
|
+
? Stream.concat(
|
|
4974
|
+
canonical,
|
|
4975
|
+
canonicalRange(ctx.threadId, tail.sequence, canonicalThrough),
|
|
4976
|
+
)
|
|
4977
|
+
: canonicalRange(ctx.threadId, tail.sequence);
|
|
4689
4978
|
|
|
4690
4979
|
let compaction: CanonicalRecordEnvelope | undefined;
|
|
4691
4980
|
let latestResponse: CanonicalSequence | undefined;
|
|
@@ -4802,7 +5091,7 @@ const make = Effect.fn("DurableAgentRuntime.make")(function* (
|
|
|
4802
5091
|
const validated = yield* Effect.try({
|
|
4803
5092
|
try: () =>
|
|
4804
5093
|
RecoveryCheckpointState.make({
|
|
4805
|
-
schemaVersion:
|
|
5094
|
+
schemaVersion: 2,
|
|
4806
5095
|
policyAccountingVersion: 1,
|
|
4807
5096
|
submissionId,
|
|
4808
5097
|
submissionIds: [...ids],
|
|
@@ -4894,44 +5183,13 @@ const make = Effect.fn("DurableAgentRuntime.make")(function* (
|
|
|
4894
5183
|
)
|
|
4895
5184
|
return;
|
|
4896
5185
|
|
|
4897
|
-
|
|
4898
|
-
|
|
4899
|
-
|
|
4900
|
-
|
|
4901
|
-
|
|
4902
|
-
|
|
4903
|
-
|
|
4904
|
-
// JSON serialization duplicates shared in-memory references without weakening the
|
|
4905
|
-
// persisted depth/node/byte guard (which rejects cyclic/shared object graphs).
|
|
4906
|
-
const contents = yield* Schema.encodeEffect(
|
|
4907
|
-
Schema.fromJsonString(RecoveryCheckpointContents),
|
|
4908
|
-
)(RecoveryCheckpointContents.make({ state: validated.value, digest })).pipe(
|
|
4909
|
-
Effect.flatMap(Schema.decodeUnknownEffect(Schema.fromJsonString(PersistedJson))),
|
|
4910
|
-
Effect.option,
|
|
5186
|
+
yield* persistRecoveryCheckpoint(
|
|
5187
|
+
ctx,
|
|
5188
|
+
submission,
|
|
5189
|
+
validated.value,
|
|
5190
|
+
tail,
|
|
5191
|
+
replacement.record.createdAt,
|
|
4911
5192
|
);
|
|
4912
|
-
|
|
4913
|
-
if (Option.isNone(contents)) return;
|
|
4914
|
-
|
|
4915
|
-
const checkpoint = ThreadCheckpoint.make({
|
|
4916
|
-
schemaVersion: 1,
|
|
4917
|
-
threadId: ctx.threadId,
|
|
4918
|
-
throughSequence: tail.sequence,
|
|
4919
|
-
tailDigest: tail.digest,
|
|
4920
|
-
engineVersion: RECOVERY_ENGINE_VERSION,
|
|
4921
|
-
agentDefinitionDigest: submission.agentDigests.agent,
|
|
4922
|
-
modelDigest: submission.agentDigests.model,
|
|
4923
|
-
toolDigest: submission.agentDigests.tools,
|
|
4924
|
-
state: contents.value,
|
|
4925
|
-
createdAt: replacement.record.createdAt,
|
|
4926
|
-
});
|
|
4927
|
-
|
|
4928
|
-
yield* hit("checkpoint:before-save");
|
|
4929
|
-
yield* store.recoveryCheckpoints
|
|
4930
|
-
.save(
|
|
4931
|
-
SaveRecoveryCheckpointRequest.make({ checkpoint, producerEpoch: ctx.producerEpoch }),
|
|
4932
|
-
)
|
|
4933
|
-
.pipe(Effect.catchTag("CheckpointRejected", () => Effect.void));
|
|
4934
|
-
yield* hit("checkpoint:after-save");
|
|
4935
5193
|
},
|
|
4936
5194
|
);
|
|
4937
5195
|
|
|
@@ -4980,6 +5238,8 @@ const make = Effect.fn("DurableAgentRuntime.make")(function* (
|
|
|
4980
5238
|
runId,
|
|
4981
5239
|
undefined,
|
|
4982
5240
|
journalSeed,
|
|
5241
|
+
undefined,
|
|
5242
|
+
priorContext,
|
|
4983
5243
|
);
|
|
4984
5244
|
|
|
4985
5245
|
const rolloverOperation =
|
|
@@ -5952,19 +6212,25 @@ const make = Effect.fn("DurableAgentRuntime.make")(function* (
|
|
|
5952
6212
|
let sourceJournal = journal;
|
|
5953
6213
|
let sourceBoundaries = boundaries;
|
|
5954
6214
|
|
|
5955
|
-
if (commit.kind !== "summarize") {
|
|
6215
|
+
if (commit.kind !== "summarize" || priorContext !== undefined) {
|
|
5956
6216
|
// Results must be canonical before pruning or rollover can cover them. Newly committed
|
|
5957
6217
|
// compactions remain overlays on this Attempt's append-only source, so omit those
|
|
5958
6218
|
// overlays while reconstructing the exact source-to-record mapping.
|
|
5959
|
-
yield* recordHalt(commitPendingTurn);
|
|
6219
|
+
if (commit.kind !== "summarize") yield* recordHalt(commitPendingTurn);
|
|
5960
6220
|
const tail = yield* Ref.get(ctx.tailRef);
|
|
5961
6221
|
|
|
5962
6222
|
sourceBoundaries = [];
|
|
5963
6223
|
sourceJournal = yield* recordHalt(
|
|
5964
6224
|
projectRunJournalStream(
|
|
5965
|
-
|
|
5966
|
-
|
|
5967
|
-
|
|
6225
|
+
(priorContext === undefined
|
|
6226
|
+
? Stream.concat(
|
|
6227
|
+
canonical,
|
|
6228
|
+
canonicalRange(ctx.threadId, tail.sequence, canonicalThrough),
|
|
6229
|
+
)
|
|
6230
|
+
: canonicalRange(
|
|
6231
|
+
ctx.threadId,
|
|
6232
|
+
commit.kind === "summarize" ? canonicalThrough : tail.sequence,
|
|
6233
|
+
)
|
|
5968
6234
|
).pipe(
|
|
5969
6235
|
Stream.filter(
|
|
5970
6236
|
(envelope) =>
|
|
@@ -8552,14 +8818,21 @@ const make = Effect.fn("DurableAgentRuntime.make")(function* (
|
|
|
8552
8818
|
};
|
|
8553
8819
|
|
|
8554
8820
|
let approvalDecisionIntents = snapshot.approvalDecisions;
|
|
8821
|
+
let priorContext = initialView.context;
|
|
8555
8822
|
|
|
8556
8823
|
while (true) {
|
|
8557
8824
|
const tail = yield* store.inspectTail(ThreadTailRequest.make({ threadId }));
|
|
8558
8825
|
|
|
8559
|
-
|
|
8560
|
-
|
|
8561
|
-
|
|
8562
|
-
|
|
8826
|
+
// An immediate resume may include a newly committed compaction. Rebuild its canonical
|
|
8827
|
+
// proof rather than carrying the initial Thread context across that boundary.
|
|
8828
|
+
const fullContextReplay = initialView.context !== undefined && priorContext === undefined;
|
|
8829
|
+
|
|
8830
|
+
const canonical = fullContextReplay
|
|
8831
|
+
? canonicalRange(threadId, tail.tailSequence)
|
|
8832
|
+
: Stream.concat(
|
|
8833
|
+
initialView.canonical,
|
|
8834
|
+
canonicalRange(threadId, tail.tailSequence, initialThrough),
|
|
8835
|
+
);
|
|
8563
8836
|
|
|
8564
8837
|
const currentRecords = yield* refreshControl(tail.tailSequence);
|
|
8565
8838
|
|
|
@@ -8573,7 +8846,8 @@ const make = Effect.fn("DurableAgentRuntime.make")(function* (
|
|
|
8573
8846
|
canonical,
|
|
8574
8847
|
tail.tailSequence,
|
|
8575
8848
|
initialView.seed,
|
|
8576
|
-
|
|
8849
|
+
priorContext,
|
|
8850
|
+
fullContextReplay ? undefined : takeJournalMetadata(),
|
|
8577
8851
|
lineage,
|
|
8578
8852
|
approvalDecisionIntents,
|
|
8579
8853
|
currentContracts,
|
|
@@ -8581,6 +8855,8 @@ const make = Effect.fn("DurableAgentRuntime.make")(function* (
|
|
|
8581
8855
|
yieldAfter,
|
|
8582
8856
|
);
|
|
8583
8857
|
|
|
8858
|
+
priorContext = undefined;
|
|
8859
|
+
|
|
8584
8860
|
if (outcome._tag === "yielded") {
|
|
8585
8861
|
if (outcome.nextSubmissionId !== undefined) onHandoff(outcome.nextSubmissionId);
|
|
8586
8862
|
|
|
@@ -8736,7 +9012,18 @@ const make = Effect.fn("DurableAgentRuntime.make")(function* (
|
|
|
8736
9012
|
return Option.none<Settlement>();
|
|
8737
9013
|
}
|
|
8738
9014
|
|
|
8739
|
-
return Option.some(
|
|
9015
|
+
return Option.some(
|
|
9016
|
+
yield* terminalize(
|
|
9017
|
+
ctx,
|
|
9018
|
+
submission,
|
|
9019
|
+
tokenRef,
|
|
9020
|
+
outcome,
|
|
9021
|
+
true,
|
|
9022
|
+
outcome._tag === "completed"
|
|
9023
|
+
? saveThreadContext(ctx, submission, initialView.context)
|
|
9024
|
+
: undefined,
|
|
9025
|
+
),
|
|
9026
|
+
);
|
|
8740
9027
|
}
|
|
8741
9028
|
}).pipe(Effect.scoped);
|
|
8742
9029
|
|