@sema-agent/core 5.35.0 → 5.37.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/CHANGELOG.md +115 -0
- package/dist/agents/subagent.d.ts +10 -0
- package/dist/agents/subagent.js +29 -2
- package/dist/core/auto-compaction.d.ts +23 -0
- package/dist/core/auto-compaction.js +8 -0
- package/dist/core/checkpoint-store.d.ts +16 -0
- package/dist/core/context-guard.d.ts +41 -0
- package/dist/core/context-guard.js +76 -0
- package/dist/core/governance-codes.js +4 -0
- package/dist/core/memory-engine/engine.d.ts +142 -0
- package/dist/core/memory-engine/engine.js +265 -3
- package/dist/core/memory-engine/file-backend.d.ts +490 -16
- package/dist/core/memory-engine/file-backend.js +1099 -36
- package/dist/core/memory-engine/index.d.ts +2 -2
- package/dist/core/memory-engine/index.js +1 -1
- package/dist/core/memory-engine/layout.d.ts +42 -2
- package/dist/core/memory-engine/layout.js +76 -12
- package/dist/core/memory-engine/memory-backend-contract.d.ts +13 -0
- package/dist/core/memory-engine/memory-backend-contract.js +89 -0
- package/dist/core/park-selfcheck.d.ts +5 -0
- package/dist/core/protocol-table.d.ts +4 -4
- package/dist/core/runner/assemble-result.d.ts +8 -0
- package/dist/core/runner/assemble-result.js +4 -1
- package/dist/core/runner/git-status-frame.d.ts +219 -0
- package/dist/core/runner/git-status-frame.js +212 -0
- package/dist/core/runner/prepare-memory.d.ts +11 -1
- package/dist/core/runner/prepare-memory.js +48 -2
- package/dist/core/runner/prepare-task.d.ts +21 -0
- package/dist/core/runner/prepare-task.js +28 -35
- package/dist/core/runner/runtask.js +270 -5
- package/dist/core/task-registry-agent.d.ts +15 -0
- package/dist/core/task-registry-agent.js +9 -0
- package/dist/core/task-registry.d.ts +3 -0
- package/dist/core/task-registry.js +4 -1
- package/dist/core/types.d.ts +122 -7
- package/dist/engine/harness/types.d.ts +65 -1
- package/dist/engine/harness/types.js +20 -0
- package/dist/engine/session/import-validate.js +10 -1
- package/dist/engine/session/session.d.ts +37 -1
- package/dist/engine/session/session.js +56 -1
- package/dist/index.d.ts +2 -2
- package/dist/index.js +1 -1
- package/dist/internal/harness-types.d.ts +1 -0
- package/dist/internal/harness.d.ts +2 -0
- package/dist/internal/harness.js +2 -0
- package/dist/prompt-assembly/epoch.js +1 -1
- package/dist/prompt-assembly/event-registry.js +1 -0
- package/dist/prompts/default.d.ts +20 -7
- package/dist/prompts/default.js +2 -7
- package/package.json +1 -1
- package/test/export-surface.snapshot.json +13 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,120 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 5.37.0 — 2026-08-16
|
|
4
|
+
|
|
5
|
+
No BREAKING changes. Narrowings disclosed below (fail-closed forms replacing silent tolerance).
|
|
6
|
+
|
|
7
|
+
### Added
|
|
8
|
+
|
|
9
|
+
- design/178 v2-a — the provenance/audit read family (part 2): `MemoryEngine.provenanceOf(id)`
|
|
10
|
+
returns a versioned envelope joining the entry's custody chain, lineage account and committed
|
|
11
|
+
binding; backend faces `committedSnapshotOf` / `committedSnapshotsOfScopes` / `custodyOf` answer
|
|
12
|
+
the committed store state and per-id custody without touching live planes. `TransferEvidence` and
|
|
13
|
+
the custody/snapshot report types are exported; the contract kit gains optional clauses for the
|
|
14
|
+
new faces.
|
|
15
|
+
- design/178 v2-a — the memory-visibility observation seat (part 1): `TaskResult.effectiveMemoryScopes`
|
|
16
|
+
(exported union `EffectiveMemoryScopes`) reports the leg's EFFECTIVE memory posture — mounted
|
|
17
|
+
(visibility rows in effective service order, with per-scope admission origin), memoryless
|
|
18
|
+
(mount-failed with residue, or no-backend), or none (no-spec / disabled). Minted after the
|
|
19
|
+
materialize outcome on every terminal that completed prepare, including the stream-layer backstop
|
|
20
|
+
terminal; delegated child legs each mint their own.
|
|
21
|
+
- design/178 v2-b — the erasure evidence protocol: `MemoryEngine.eraseMemoryEntries` /
|
|
22
|
+
backend `eraseWithEvidence` perform evidence-bearing erasure — store-level anchor plus per-scope
|
|
23
|
+
request projections in one journaled transaction, delete rows causally bound to the physical
|
|
24
|
+
deletes, a chain-level resurrection backstop (an erased id's replanted bytes are refused on BOTH
|
|
25
|
+
the adoption and retrieval faces), a census-backed scope selector (unledgered-live ghost files are
|
|
26
|
+
members), an erase-time MEMORY.md index sweep (residue is swept or disclosed on
|
|
27
|
+
`residuals.indexUncleared`, never silently left — including on §4.6 replays, where the evidence
|
|
28
|
+
row's recorded binding drives the sweep under an occupancy check so a freed slug taken by a live
|
|
29
|
+
entry is never touched), and an unevidenced degradation lane behind an explicit `allowUnevidenced`
|
|
30
|
+
opt-in. `MemoryErasureAttestation` rows are re-checkable against the evidence chain.
|
|
31
|
+
- `EntryCustodyReport.reason` (optional): a custody chain that carries one event id with two
|
|
32
|
+
different payloads answers `state: "damaged"` with the contradiction named, instead of a clean
|
|
33
|
+
"complete" over a spliced chain; byte-equal duplicate rows collapse instead of double-counting.
|
|
34
|
+
- Erasure host-API codes registered: `memory.erasure_evidence_unavailable`,
|
|
35
|
+
`memory.erasure_selector_mismatch`, `memory.erasure_census_incomplete`,
|
|
36
|
+
`memory.erasure_index_residue` (all non-governance caller/state-shape verdicts).
|
|
37
|
+
|
|
38
|
+
### Fixed
|
|
39
|
+
|
|
40
|
+
- Erasure selectors resolve prototype-member keys as data, never as phantom sets: a
|
|
41
|
+
`sessionId: "constructor"` selector answers the empty resolution instead of treating every
|
|
42
|
+
lineaged entry as a match (class-swept across the lineage/scope/cursor keyed reads).
|
|
43
|
+
- `provenanceOf` names the face that is actually missing: the unknown-binding reason literal is now
|
|
44
|
+
`audit-snapshot-capability-absent` (was minted as `custody-capability-absent` even while the
|
|
45
|
+
custody face was answering).
|
|
46
|
+
- The retrieval face's delete-evidence guard is loud on an account-read fault: the documented
|
|
47
|
+
conservative-withhold direction is real (ids carrying delete evidence are withheld during the
|
|
48
|
+
fault window, entries without delete evidence keep serving), and the fault is announced once per
|
|
49
|
+
fault code per mount instead of a silent vanish.
|
|
50
|
+
|
|
51
|
+
### Narrowed (disclosed)
|
|
52
|
+
|
|
53
|
+
- Transfer-evidence journal replay: a journal row whose event id matches an existing chain row with
|
|
54
|
+
a DIFFERENT payload is now fail-closed corruption (loud refusal naming the ev), replacing the
|
|
55
|
+
pre-v2b silent skip/overwrite. One ev is one identity.
|
|
56
|
+
- `{scope}` erasure membership is bound rows ∪ the census's unledgered-live inhabitants — a ghost
|
|
57
|
+
file no longer survives a scope erase under a clean attestation.
|
|
58
|
+
|
|
59
|
+
|
|
60
|
+
## 5.36.0 — 2026-08-15
|
|
61
|
+
|
|
62
|
+
No BREAKING changes. Upgrade note: the system-prompt shape change resets provider prompt caches
|
|
63
|
+
ONCE on upgrade — the point of the feature is that they stop resetting after that.
|
|
64
|
+
|
|
65
|
+
### Added
|
|
66
|
+
|
|
67
|
+
- Env-tail prefix stability (#254): the turn-dynamic git facts (branch line, working-tree line,
|
|
68
|
+
status snapshot) leave the system prompt for a value-keyed `git_status` turn frame. The system
|
|
69
|
+
prompt stays byte-stable across legs while the working tree moves, so provider prefix caches
|
|
70
|
+
survive leg/resume/fork boundaries (live-verified cache hits past the static-section watermark).
|
|
71
|
+
The frame re-sends only when its `(kind, hash)` changes; kind flips announce a tombstone once
|
|
72
|
+
("may be stale" / "no longer a git repository"); recovery re-asserts loudly. Compaction re-asserts
|
|
73
|
+
the current frame beside the summary on all three landing paths (boundary / PTL recovery /
|
|
74
|
+
end-of-task, one single-source hook). Announcement mirrors are branch-authoritative with a
|
|
75
|
+
transcript-side newest-frame arbiter: the transcript is the truth, a mirror that cannot be audited
|
|
76
|
+
degrades to one conservative re-announcement.
|
|
77
|
+
- Wire/durable additions for #254: `steering_injected.source` gains `git_status` (preview is a
|
|
78
|
+
CONSTANT phrase per frame kind — repo text never enters the event face); durable session entry
|
|
79
|
+
`git_announcement`, compaction `details.gitAnnouncement`, `CheckpointState.gitAnnouncement` (all
|
|
80
|
+
additive, no version bump — absent fields resume with one conservative re-announcement); `Session`
|
|
81
|
+
gains two OPTIONAL members (`appendGitAnnouncement`/`getGitAnnouncement`); event-registry row
|
|
82
|
+
`git_status`. Existing sessions record one `legacy_migration` pin on first resume (env probe
|
|
83
|
+
sentinel `probe-env@2`).
|
|
84
|
+
- New `TaskResult.errorCode`: `irreducible_core_over_budget` — the request budget cannot fit even
|
|
85
|
+
the compaction summaries plus the DEGRADED git frame; the run fails loudly instead of shipping a
|
|
86
|
+
dishonest request. The frame has a trim-protected replace-by-key slot with exactly one
|
|
87
|
+
deterministic degrade shrink; H4 probe failures degrade to a two-line branch+dirty frame instead
|
|
88
|
+
of silently omitting the section; the probe sentinel mis-split arm is loud.
|
|
89
|
+
- Fleet row generation out-line (#258): `task_progress` ticks and `BackgroundChildEvent` spawn/tick
|
|
90
|
+
frames from background-agent rows carry `seq` — the row's stop-cycle generation (fresh spawn = 1,
|
|
91
|
+
every launched revival bumps it), one axis with `TaskNotificationPayload.seq` — so a fleet
|
|
92
|
+
consumer can tell a LATE first frame from a REVIVED cycle's frame. BCE terminal frames fall back
|
|
93
|
+
to the registry generation when the notify-mirror carrier is absent (one run speaks one number;
|
|
94
|
+
the notification payload's design/144 seq contract is unchanged). Absence stays a fact: sync
|
|
95
|
+
children, workflow agents and top-level runs carry nothing — never read absence as "cycle 1".
|
|
96
|
+
Hardening shipped with it: a parked-born resume speaks ONE generation end to end (the consume
|
|
97
|
+
flip's target), a retained wake realigns forward past a foreign tier-3 cycle's durable advance,
|
|
98
|
+
and BCE ticks always stamp the ENCLOSING row's generation.
|
|
99
|
+
|
|
100
|
+
### Changed
|
|
101
|
+
|
|
102
|
+
- `buildEnvironmentContext` no longer renders gitBranch/gitDirty/gitSnapshot (fields remain as
|
|
103
|
+
data; the snapshot rides the `git_status` frame). Pre-call budget estimates now include the frame
|
|
104
|
+
bytes (an informed tightening). Frame bodies clamp astral-safe (U+FFFD at a split code point).
|
|
105
|
+
- Merged-code rescan hardening (18 findings across two rounds, all verified then disposed): the
|
|
106
|
+
degraded-shrink replacement inserts via thunk (a branch named with `$&`/`$$` — legal ref bytes —
|
|
107
|
+
no longer expands match context into the frame or silently renames itself; same class swept to
|
|
108
|
+
`buildMemoryInstruction`); the irreducible core charges the frame segment + summaries only, and a
|
|
109
|
+
trim-dropped carrier is never resurrected — the guard re-inserts the frame SEGMENT as its own
|
|
110
|
+
engine-minted request-view message, so the trim's adjudication stands and the request stays
|
|
111
|
+
servable; the listing-replay scan strips positive git frames before parsing (a commit subject
|
|
112
|
+
spelling a roster header can no longer reset the announced set); the degraded frame joins the
|
|
113
|
+
request-lossy disclosure; old-checkpoint resume compat re-pinned honestly (one conservative
|
|
114
|
+
re-announcement); the PTL-recovery landing path gained real coverage.
|
|
115
|
+
- `probeParkRoundTrip` JSDoc names its write side loudly (diagnostic probe, not a health-poll
|
|
116
|
+
body).
|
|
117
|
+
|
|
3
118
|
## 5.35.0 — 2026-08-15
|
|
4
119
|
|
|
5
120
|
No BREAKING changes. One deliberate fail-closed tighten and one loosening-direction fix are called
|
|
@@ -492,6 +492,16 @@ export interface SubagentToolOptions {
|
|
|
492
492
|
/** β 批 A-2 (BREAKING 1.365.0): REQUIRED — `"default"` is the single-tenant spelling (explicit,
|
|
493
493
|
* matching the engine chain's `principal ?? "default"`), never implied by omission. */
|
|
494
494
|
scope: string;
|
|
495
|
+
/**
|
|
496
|
+
* Deployment-level completion-push FALLBACK — not an always-on tap. The settle notification is
|
|
497
|
+
* delivered through `ctx.onTaskNotification ?? background.notify`: inside an engine-driven run
|
|
498
|
+
* the tool-execution context carries the run's own notification injector (the completion lands
|
|
499
|
+
* in the PARENT's live injection queue at its next turn boundary), and this sink is then never
|
|
500
|
+
* called — one completion, one channel, no double-send. It fires only when the tool is executed
|
|
501
|
+
* WITHOUT an engine notification channel (direct `execute()` harnesses, minimal mounts). A
|
|
502
|
+
* deployment that wants an unconditional process-level completion tap should use
|
|
503
|
+
* `RunnerDeps.onBackgroundChildEvent` (the BCE terminal frame) instead.
|
|
504
|
+
*/
|
|
495
505
|
notify?: (n: import("../core/task-notification.js").TaskNotificationPayload, opts?: {
|
|
496
506
|
priority?: "now" | "next" | "later";
|
|
497
507
|
}) => void;
|
package/dist/agents/subagent.js
CHANGED
|
@@ -544,6 +544,11 @@ export function createSubagentResume(deps) {
|
|
|
544
544
|
: "resume unavailable: the agent's registry row no longer exists (terminal GC) — relaunch a new agent instead.", revived.reason === "still_running" ? "steering.still_running" : revived.reason === "recycling" ? "resume.row_recycling" : "resume.row_gone");
|
|
545
545
|
}
|
|
546
546
|
reviveCycle = revived.cycle;
|
|
547
|
+
if (deps.registry !== undefined && deps.taskId !== undefined) {
|
|
548
|
+
const settledSeq = deps.registry.backgroundAgentCycleSeq(deps.taskId);
|
|
549
|
+
if (settledSeq !== undefined && settledSeq > entry.cycleSeq)
|
|
550
|
+
entry.cycleSeq = settledSeq;
|
|
551
|
+
}
|
|
547
552
|
if (deps.bgSink !== undefined) {
|
|
548
553
|
reviveEmit = (event) => {
|
|
549
554
|
try {
|
|
@@ -560,6 +565,7 @@ export function createSubagentResume(deps) {
|
|
|
560
565
|
...(deps.rowScope !== undefined ? { scope: deps.rowScope } : {}),
|
|
561
566
|
description: `${deps.rowDescription ?? "sub-agent"} (resumed ${marker})`,
|
|
562
567
|
...(deps.rowAgentType !== undefined ? { agentType: deps.rowAgentType } : {}),
|
|
568
|
+
seq: entry.cycleSeq,
|
|
563
569
|
...(deps.rowName !== undefined ? { name: deps.rowName } : {}),
|
|
564
570
|
sessionId: entry.childSessionId,
|
|
565
571
|
transcriptId: entry.childSessionId,
|
|
@@ -585,6 +591,7 @@ export function createSubagentResume(deps) {
|
|
|
585
591
|
transcriptId: entry.childSessionId,
|
|
586
592
|
sessionId: entry.childSessionId,
|
|
587
593
|
...(deps.parentToolCallId !== undefined ? { parentToolCallId: deps.parentToolCallId } : {}),
|
|
594
|
+
seq: entry.cycleSeq,
|
|
588
595
|
progressTaskId: entry.childSessionId,
|
|
589
596
|
...(currentAction !== undefined ? { currentAction } : {}),
|
|
590
597
|
...(currentTool !== undefined ? { currentTool } : {}),
|
|
@@ -593,6 +600,7 @@ export function createSubagentResume(deps) {
|
|
|
593
600
|
}, deps.registry !== undefined && deps.taskId !== undefined ? () => deps.registry.noteBackgroundAgentActivity(deps.taskId) : undefined);
|
|
594
601
|
stream = childRunner.runTaskStream(resumeSpec, undefined, {
|
|
595
602
|
...entry.internalsSnapshot,
|
|
603
|
+
cycleSeq: entry.cycleSeq,
|
|
596
604
|
...(true
|
|
597
605
|
? {
|
|
598
606
|
onForwardEvent: (e) => {
|
|
@@ -612,6 +620,7 @@ export function createSubagentResume(deps) {
|
|
|
612
620
|
sessionId: entry.childSessionId,
|
|
613
621
|
transcriptId: entry.childSessionId,
|
|
614
622
|
...(deps.parentToolCallId !== undefined ? { parentToolCallId: deps.parentToolCallId } : {}),
|
|
623
|
+
seq: entry.cycleSeq,
|
|
615
624
|
progressTaskId: e.taskId,
|
|
616
625
|
...(e.parentTaskId !== undefined ? { progressParentTaskId: e.parentTaskId } : {}),
|
|
617
626
|
...(e.name !== undefined ? { name: e.name } : {}),
|
|
@@ -1955,6 +1964,7 @@ function createSubagentToolNode(opts, depth, excluded, extraToolsBudget) {
|
|
|
1955
1964
|
bg.registry.attachAgentTerminalNotifier(taskId, () => {
|
|
1956
1965
|
const stoppedByReap = bg.registry.getStopAttribution(taskId) ?? "system";
|
|
1957
1966
|
const completionIdReap = bg.registry.getCompletionId(taskId);
|
|
1967
|
+
const seqReap = bg.registry.backgroundAgentCycleSeq(taskId);
|
|
1958
1968
|
const summaryReap = ccCompletionText(shortDesc, "killed", "killed", Date.now() - forkBgHangAt);
|
|
1959
1969
|
sinkEmit({
|
|
1960
1970
|
kind: "terminal",
|
|
@@ -1964,6 +1974,7 @@ function createSubagentToolNode(opts, depth, excluded, extraToolsBudget) {
|
|
|
1964
1974
|
transcriptId: forkedId,
|
|
1965
1975
|
parentToolCallId: ctx.toolCallId,
|
|
1966
1976
|
status: "killed",
|
|
1977
|
+
...(seqReap !== undefined ? { seq: seqReap } : {}),
|
|
1967
1978
|
stoppedBy: stoppedByReap,
|
|
1968
1979
|
summary: summaryReap,
|
|
1969
1980
|
resumable: false,
|
|
@@ -1992,6 +2003,7 @@ function createSubagentToolNode(opts, depth, excluded, extraToolsBudget) {
|
|
|
1992
2003
|
catch {
|
|
1993
2004
|
}
|
|
1994
2005
|
};
|
|
2006
|
+
const bgForkCycleSeq = bg.registry.backgroundAgentCycleSeq(taskId);
|
|
1995
2007
|
sinkEmit({
|
|
1996
2008
|
kind: "spawn",
|
|
1997
2009
|
taskId,
|
|
@@ -2000,6 +2012,7 @@ function createSubagentToolNode(opts, depth, excluded, extraToolsBudget) {
|
|
|
2000
2012
|
...(bgScope !== undefined ? { scope: bgScope } : {}),
|
|
2001
2013
|
description: shortDesc,
|
|
2002
2014
|
agentType: spawnAgentType,
|
|
2015
|
+
...(bgForkCycleSeq !== undefined ? { seq: bgForkCycleSeq } : {}),
|
|
2003
2016
|
...(agentName !== undefined ? { name: agentName } : {}),
|
|
2004
2017
|
sessionId: forkedId,
|
|
2005
2018
|
transcriptId: forkedId,
|
|
@@ -2028,6 +2041,7 @@ function createSubagentToolNode(opts, depth, excluded, extraToolsBudget) {
|
|
|
2028
2041
|
parentToolCallId: ctx.toolCallId,
|
|
2029
2042
|
progressTaskId: forkedId,
|
|
2030
2043
|
agentType: spawnAgentType,
|
|
2044
|
+
...(bgForkCycleSeq !== undefined ? { seq: bgForkCycleSeq } : {}),
|
|
2031
2045
|
...(currentAction !== undefined ? { currentAction } : {}),
|
|
2032
2046
|
...(currentTool !== undefined ? { currentTool } : {}),
|
|
2033
2047
|
usage: { toolUses: toolStarts },
|
|
@@ -2039,6 +2053,7 @@ function createSubagentToolNode(opts, depth, excluded, extraToolsBudget) {
|
|
|
2039
2053
|
...(forkQuestionStrip !== undefined ? forkQuestionStrip.internalsFlag : {}),
|
|
2040
2054
|
onNotifyInjectorReady: s2ForkNotifyReady,
|
|
2041
2055
|
delegationTaskType: "background_agent",
|
|
2056
|
+
...(bgForkCycleSeq !== undefined ? { cycleSeq: bgForkCycleSeq } : {}),
|
|
2042
2057
|
onForwardEvent: (e) => {
|
|
2043
2058
|
try {
|
|
2044
2059
|
recordingForward(e, { bgAgentId: taskId });
|
|
@@ -2056,6 +2071,7 @@ function createSubagentToolNode(opts, depth, excluded, extraToolsBudget) {
|
|
|
2056
2071
|
sessionId: forkedId,
|
|
2057
2072
|
transcriptId: forkedId,
|
|
2058
2073
|
parentToolCallId: ctx.toolCallId,
|
|
2074
|
+
...(bgForkCycleSeq !== undefined ? { seq: bgForkCycleSeq } : {}),
|
|
2059
2075
|
progressTaskId: e.taskId,
|
|
2060
2076
|
...(e.parentTaskId !== undefined ? { progressParentTaskId: e.parentTaskId } : {}),
|
|
2061
2077
|
agentType: spawnAgentType,
|
|
@@ -2072,6 +2088,7 @@ function createSubagentToolNode(opts, depth, excluded, extraToolsBudget) {
|
|
|
2072
2088
|
...(forkQuestionStrip !== undefined ? forkQuestionStrip.internalsFlag : {}),
|
|
2073
2089
|
onNotifyInjectorReady: s2ForkNotifyReady,
|
|
2074
2090
|
delegationTaskType: "background_agent",
|
|
2091
|
+
...(bgForkCycleSeq !== undefined ? { cycleSeq: bgForkCycleSeq } : {}),
|
|
2075
2092
|
onForwardEvent: (e) => {
|
|
2076
2093
|
try {
|
|
2077
2094
|
recordingForward(e, { bgAgentId: taskId });
|
|
@@ -2147,6 +2164,7 @@ function createSubagentToolNode(opts, depth, excluded, extraToolsBudget) {
|
|
|
2147
2164
|
...(stillbornFork ? {} : { transcriptId: forkTranscriptId }),
|
|
2148
2165
|
parentToolCallId: ctx.toolCallId,
|
|
2149
2166
|
status: settledBg,
|
|
2167
|
+
...(bgForkCycleSeq !== undefined ? { seq: bgForkCycleSeq } : {}),
|
|
2150
2168
|
...(stoppedByBg !== undefined ? { stoppedBy: stoppedByBg } : {}),
|
|
2151
2169
|
summary: forkTerminalSummary,
|
|
2152
2170
|
...residualFork,
|
|
@@ -2204,6 +2222,7 @@ function createSubagentToolNode(opts, depth, excluded, extraToolsBudget) {
|
|
|
2204
2222
|
transcriptId: forkedId,
|
|
2205
2223
|
parentToolCallId: ctx.toolCallId,
|
|
2206
2224
|
status: settledBg,
|
|
2225
|
+
...(bgForkCycleSeq !== undefined ? { seq: bgForkCycleSeq } : {}),
|
|
2207
2226
|
...(stoppedByBg !== undefined ? { stoppedBy: stoppedByBg } : {}),
|
|
2208
2227
|
summary: summaryBg,
|
|
2209
2228
|
...(completionIdForkReject !== undefined ? { completionId: completionIdForkReject } : {}),
|
|
@@ -2420,9 +2439,11 @@ function createSubagentToolNode(opts, depth, excluded, extraToolsBudget) {
|
|
|
2420
2439
|
const bgStartedAt = Date.now();
|
|
2421
2440
|
stepRecorder.lockTo(bgChildSessionId);
|
|
2422
2441
|
bg.registry.bindBackgroundAgentSession(taskId, bgChildSessionId);
|
|
2442
|
+
const bgCycleSeq = bg.registry.backgroundAgentCycleSeq(taskId);
|
|
2423
2443
|
bg.registry.attachAgentTerminalNotifier(taskId, () => {
|
|
2424
2444
|
const stoppedByReap = bg.registry.getStopAttribution(taskId) ?? "system";
|
|
2425
2445
|
const completionIdReap = bg.registry.getCompletionId(taskId);
|
|
2446
|
+
const seqReap = bg.registry.backgroundAgentCycleSeq(taskId);
|
|
2426
2447
|
const summaryReap = ccCompletionText(shortDesc, "killed", "killed", Date.now() - bgStartedAt);
|
|
2427
2448
|
sinkEmit({
|
|
2428
2449
|
kind: "terminal",
|
|
@@ -2432,6 +2453,7 @@ function createSubagentToolNode(opts, depth, excluded, extraToolsBudget) {
|
|
|
2432
2453
|
transcriptId: bgChildSessionId,
|
|
2433
2454
|
parentToolCallId: ctx.toolCallId,
|
|
2434
2455
|
status: "killed",
|
|
2456
|
+
...(seqReap !== undefined ? { seq: seqReap } : {}),
|
|
2435
2457
|
stoppedBy: stoppedByReap,
|
|
2436
2458
|
summary: summaryReap,
|
|
2437
2459
|
resumable: false,
|
|
@@ -2459,6 +2481,7 @@ function createSubagentToolNode(opts, depth, excluded, extraToolsBudget) {
|
|
|
2459
2481
|
...(bgScope !== undefined ? { scope: bgScope } : {}),
|
|
2460
2482
|
description: reviveRow !== undefined ? `${shortDesc} (revived)` : shortDesc,
|
|
2461
2483
|
agentType: spawnAgentType,
|
|
2484
|
+
...(bgCycleSeq !== undefined ? { seq: bgCycleSeq } : {}),
|
|
2462
2485
|
...(agentName !== undefined ? { name: agentName } : {}),
|
|
2463
2486
|
sessionId: bgChildSessionId,
|
|
2464
2487
|
transcriptId: bgChildSessionId,
|
|
@@ -2531,6 +2554,7 @@ function createSubagentToolNode(opts, depth, excluded, extraToolsBudget) {
|
|
|
2531
2554
|
parentToolCallId: ctx.toolCallId,
|
|
2532
2555
|
progressTaskId: bgChildSessionId,
|
|
2533
2556
|
agentType: spawnAgentType,
|
|
2557
|
+
...(bgCycleSeq !== undefined ? { seq: bgCycleSeq } : {}),
|
|
2534
2558
|
...(currentAction !== undefined ? { currentAction } : {}),
|
|
2535
2559
|
...(currentTool !== undefined ? { currentTool } : {}),
|
|
2536
2560
|
usage: { toolUses: toolStarts },
|
|
@@ -2542,6 +2566,7 @@ function createSubagentToolNode(opts, depth, excluded, extraToolsBudget) {
|
|
|
2542
2566
|
...(bgQuestionStrip !== undefined ? bgQuestionStrip.internalsFlag : {}),
|
|
2543
2567
|
onNotifyInjectorReady: s2NotifyReady,
|
|
2544
2568
|
delegationTaskType: "background_agent",
|
|
2569
|
+
...(bgCycleSeq !== undefined ? { cycleSeq: bgCycleSeq } : {}),
|
|
2545
2570
|
onForwardEvent: (e) => {
|
|
2546
2571
|
try {
|
|
2547
2572
|
recordingForward(e, { bgAgentId: taskId });
|
|
@@ -2559,6 +2584,7 @@ function createSubagentToolNode(opts, depth, excluded, extraToolsBudget) {
|
|
|
2559
2584
|
sessionId: bgChildSessionId,
|
|
2560
2585
|
transcriptId: bgChildSessionId,
|
|
2561
2586
|
parentToolCallId: ctx.toolCallId,
|
|
2587
|
+
...(bgCycleSeq !== undefined ? { seq: bgCycleSeq } : {}),
|
|
2562
2588
|
progressTaskId: e.taskId,
|
|
2563
2589
|
...(e.parentTaskId !== undefined ? { progressParentTaskId: e.parentTaskId } : {}),
|
|
2564
2590
|
agentType: spawnAgentType,
|
|
@@ -2575,6 +2601,7 @@ function createSubagentToolNode(opts, depth, excluded, extraToolsBudget) {
|
|
|
2575
2601
|
...(bgQuestionStrip !== undefined ? bgQuestionStrip.internalsFlag : {}),
|
|
2576
2602
|
onNotifyInjectorReady: s2NotifyReady,
|
|
2577
2603
|
delegationTaskType: "background_agent",
|
|
2604
|
+
...(bgCycleSeq !== undefined ? { cycleSeq: bgCycleSeq } : {}),
|
|
2578
2605
|
onForwardEvent: (e) => {
|
|
2579
2606
|
try {
|
|
2580
2607
|
recordingForward(e, { bgAgentId: taskId });
|
|
@@ -2858,7 +2885,7 @@ function createSubagentToolNode(opts, depth, excluded, extraToolsBudget) {
|
|
|
2858
2885
|
...(child.sessionId ? { sessionId: child.sessionId, transcriptId: child.sessionId } : {}),
|
|
2859
2886
|
parentToolCallId: ctx.toolCallId,
|
|
2860
2887
|
status: settled,
|
|
2861
|
-
...(seqAtSettle !== undefined ? { seq: seqAtSettle } : {}),
|
|
2888
|
+
...(seqAtSettle !== undefined ? { seq: seqAtSettle } : bgCycleSeq !== undefined ? { seq: bgCycleSeq } : {}),
|
|
2862
2889
|
...(stoppedBy !== undefined ? { stoppedBy } : {}),
|
|
2863
2890
|
summary: bgTerminalSummary,
|
|
2864
2891
|
...residual,
|
|
@@ -2948,7 +2975,7 @@ function createSubagentToolNode(opts, depth, excluded, extraToolsBudget) {
|
|
|
2948
2975
|
transcriptId: bgChildSessionId,
|
|
2949
2976
|
parentToolCallId: ctx.toolCallId,
|
|
2950
2977
|
status: settled,
|
|
2951
|
-
...(seqAtSettle !== undefined ? { seq: seqAtSettle } : {}),
|
|
2978
|
+
...(seqAtSettle !== undefined ? { seq: seqAtSettle } : bgCycleSeq !== undefined ? { seq: bgCycleSeq } : {}),
|
|
2952
2979
|
...(stoppedBy !== undefined ? { stoppedBy } : {}),
|
|
2953
2980
|
summary: `${settled === "failed" ? `Agent "${shortDesc}" failed: ${msg}${ccElapsedTag(Date.now() - bgStartedAt)}` : ccCompletionText(shortDesc, settled, settled, Date.now() - bgStartedAt)}${observerNote}`.slice(0, 300) + (settled === "failed" ? errorKindClause(errClassBgReject) : ""),
|
|
2954
2981
|
...(completionIdBgReject !== undefined ? { completionId: completionIdBgReject } : {}),
|
|
@@ -323,6 +323,29 @@ export interface MaybeCompactOptions {
|
|
|
323
323
|
path: string;
|
|
324
324
|
content: string;
|
|
325
325
|
}>, preserveReadState?: ReadonlyArray<string>) => void;
|
|
326
|
+
/**
|
|
327
|
+
* env-tail migration — the git-status lane's compaction interaction (two-phase receipt). The git
|
|
328
|
+
* frame lives on the MESSAGE plane, does not enter the summary, and the projection strips it from
|
|
329
|
+
* serialized history — so a landed compaction would silently lose the model's git view while the
|
|
330
|
+
* announced hash keeps suppressing every re-send. When present:
|
|
331
|
+
* - `pending` is restated on the compaction entry's `details.gitAnnouncement` with
|
|
332
|
+
* `pending: true` in the SAME CAS as the new baseline (the promptEpoch/announcedListings
|
|
333
|
+
* restatement posture) — any reader between the landing and the re-assert receipt sees an
|
|
334
|
+
* OWED announcement, never a false "announced";
|
|
335
|
+
* - `land()` is invoked immediately after the landed baseline, BEFORE any context rebuild or
|
|
336
|
+
* retry (all three routes — turn-boundary auto, prompt-too-long recovery, end-of-task finish
|
|
337
|
+
* — funnel through this one append): it re-appends the current frame and writes the
|
|
338
|
+
* announced mirror once the append receipt is in hand. `land` contains its own failures (a
|
|
339
|
+
* failed re-assert leaves the pending restatement authoritative — the next boundary/leg
|
|
340
|
+
* re-announces); a throw here must never fail a LANDED compaction, so the call is belted.
|
|
341
|
+
*/
|
|
342
|
+
gitRestate?: {
|
|
343
|
+
pending: {
|
|
344
|
+
kind: "full" | "degraded" | "unavailable" | "non-repo";
|
|
345
|
+
hash: string;
|
|
346
|
+
};
|
|
347
|
+
land: () => Promise<void>;
|
|
348
|
+
};
|
|
326
349
|
/**
|
|
327
350
|
* Fixed per-request prompt overhead (system prompt + tool schemas, in tokens), applied ONLY in the
|
|
328
351
|
* anchor-less regime (design/64 §26.7, search [91] fourth candidate): when no assistant in the
|
|
@@ -446,7 +446,15 @@ export async function maybeCompact(opts) {
|
|
|
446
446
|
promptEpoch: restatedEpoch,
|
|
447
447
|
...(restatedListings !== undefined ? { announcedListings: restatedListings } : {}),
|
|
448
448
|
...(opts.activeTools !== undefined && opts.activeTools.length > 0 ? { activeTools: [...opts.activeTools] } : {}),
|
|
449
|
+
...(opts.gitRestate !== undefined ? { gitAnnouncement: { kind: opts.gitRestate.pending.kind, hash: opts.gitRestate.pending.hash, pending: true } } : {}),
|
|
449
450
|
}, false);
|
|
451
|
+
if (opts.gitRestate !== undefined) {
|
|
452
|
+
try {
|
|
453
|
+
await opts.gitRestate.land();
|
|
454
|
+
}
|
|
455
|
+
catch {
|
|
456
|
+
}
|
|
457
|
+
}
|
|
450
458
|
try {
|
|
451
459
|
opts.onApplied?.(attachedComplete, excludedReadStatePreserveKeys);
|
|
452
460
|
}
|
|
@@ -1008,6 +1008,22 @@ export interface CheckpointState {
|
|
|
1008
1008
|
skills?: string[];
|
|
1009
1009
|
models?: string[];
|
|
1010
1010
|
};
|
|
1011
|
+
/**
|
|
1012
|
+
* The git-status frame's announced `(kind, hash)` state at suspend (env-tail migration: the
|
|
1013
|
+
* run-loop-maintained mirror, advanced at every frame receipt). The LOWEST rung of the resume
|
|
1014
|
+
* read ladder — the session branch walk is the authority (a nearest PENDING mirror there, or an
|
|
1015
|
+
* announced mirror whose frame entry is off the active branch, forces a re-announce regardless of
|
|
1016
|
+
* this field); the checkpoint seeds only when the branch carries no mirror at all, and its
|
|
1017
|
+
* `entryId` must itself sit on the active branch to count as announced. Schema ADDITION
|
|
1018
|
+
* (additive, old readers ignore it — the Q5 posture): an older checkpoint without the field re-announces
|
|
1019
|
+
* conservatively — the frame is a tail append, so a duplicate costs bytes, never a prefix break.
|
|
1020
|
+
*/
|
|
1021
|
+
gitAnnouncement?: {
|
|
1022
|
+
kind: "full" | "degraded" | "unavailable" | "non-repo";
|
|
1023
|
+
hash: string;
|
|
1024
|
+
entryId?: string;
|
|
1025
|
+
pending?: true;
|
|
1026
|
+
};
|
|
1011
1027
|
/**
|
|
1012
1028
|
* design/180 R-4 (R-2 cross-process variant) — the armed delegation child's MONOTONIC runtime-provenance
|
|
1013
1029
|
* aggregate at suspend ({@link import("./memory-engine/delegation-provenance.js").DelegationProvenanceAggregate}:
|
|
@@ -53,6 +53,47 @@ export declare function dropOrphanToolResults(messages: AgentMessage[]): {
|
|
|
53
53
|
nextRole: string;
|
|
54
54
|
}>;
|
|
55
55
|
};
|
|
56
|
+
/** R2-3 (falsification round 2) — does the message's ENGINE-AUTHORED region cover [start, start+len)?
|
|
57
|
+
* The carrier scan must never crown a counterfeit: a tool result or a user paste can reproduce the
|
|
58
|
+
* wrapped frame bytes verbatim, but only ENGINE metadata (engineMinted whole-message, the
|
|
59
|
+
* enginePrefixChars head, or an engineSegments range) proves the engine put them there. */
|
|
60
|
+
export declare function engineRegionCovers(m: AgentMessage, start: number, len: number): boolean;
|
|
61
|
+
/** Outcome of {@link protectGitFrame} — `over_budget` means the irreducible core (summaries + the
|
|
62
|
+
* protected frame carrier) exceeds the budget even after the one-shot degraded shrink: the caller
|
|
63
|
+
* must fail the request LOUDLY (`irreducible_core_over_budget`) instead of pretending the trim
|
|
64
|
+
* succeeded or looping compaction/re-assertion. */
|
|
65
|
+
export type GitFrameGuardOutcome = {
|
|
66
|
+
messages: AgentMessage[];
|
|
67
|
+
action: "kept" | "absent" | "reinserted" | "shrunk";
|
|
68
|
+
} | {
|
|
69
|
+
action: "over_budget";
|
|
70
|
+
};
|
|
71
|
+
/**
|
|
72
|
+
* env-tail migration — the git frame's UNCLIPPABLE-CORE protection over a trimmed request view.
|
|
73
|
+
* The frame's "value unchanged ⇒ never re-sent" contract makes a request-only drop uniquely
|
|
74
|
+
* dangerous for it: the commit receipt has already landed, so a trim that cuts the carrier makes
|
|
75
|
+
* the announced hash suppress every future re-send while the model never saw the frame. Rules:
|
|
76
|
+
* - REPLACE-BY-KEY SLOT: exactly ONE carrier is protected — the newest message whose flat text CONTAINS
|
|
77
|
+
* `protectedText` (the ENGINE-HELD wrapped frame segment; set at delivery/re-assert AND
|
|
78
|
+
* re-seeded on value-unchanged legs, so the protection survives legs that send nothing).
|
|
79
|
+
* Older frames trim like ordinary history.
|
|
80
|
+
* - Re-insert a dropped carrier after the leading summaries/trim-notice (its original relative
|
|
81
|
+
* position is always ≥ there — the carrier predates every kept tail message).
|
|
82
|
+
* - IRREDUCIBLE-CORE arc: when the core (always-kept summaries + the carrier) alone exceeds the
|
|
83
|
+
* budget, substitute the carrier's frame segment with the deterministic DEGRADED rendering —
|
|
84
|
+
* exactly once per run (`alreadyShrunk` latch, request-view only; the run loop re-announces the
|
|
85
|
+
* shrunk view through its own receipt machinery). Still over after the shrink ⇒ `over_budget`.
|
|
86
|
+
* - A total overshoot whose core FITS is left alone (turn-alignment overshoot is a pre-existing,
|
|
87
|
+
* tolerated trade — validity beats budget; this guard must not convert it into a failure).
|
|
88
|
+
*/
|
|
89
|
+
export declare function protectGitFrame(before: AgentMessage[], trimmed: AgentMessage[], budgetTokens: number, frame: {
|
|
90
|
+
protectedText: string;
|
|
91
|
+
substitute?: {
|
|
92
|
+
find: string;
|
|
93
|
+
replace: string;
|
|
94
|
+
};
|
|
95
|
+
}, // protectedText = the wrapped frame SEGMENT (see the carrier scan below)
|
|
96
|
+
charsPerToken?: number): GitFrameGuardOutcome;
|
|
56
97
|
/** Budget the in-task guard targets for a model — CC-aligned headroom numbers with a 0.85
|
|
57
98
|
* small-window floor, sema-owned drop-oldest action (see {@link GUARD_HEADROOM_TOKENS}); always
|
|
58
99
|
* above the compaction/clearStale frontier. Dual-window semantics: deliberately the PHYSICAL
|
|
@@ -114,6 +114,82 @@ export function dropOrphanToolResults(messages) {
|
|
|
114
114
|
}
|
|
115
115
|
return { messages: out, dropped };
|
|
116
116
|
}
|
|
117
|
+
export function engineRegionCovers(m, start, len) {
|
|
118
|
+
const meta = m;
|
|
119
|
+
if (meta.engineMinted === true)
|
|
120
|
+
return true;
|
|
121
|
+
if (Array.isArray(meta.engineSegments))
|
|
122
|
+
return meta.engineSegments.some((seg) => seg.start <= start && start + len <= seg.end);
|
|
123
|
+
if (typeof meta.enginePrefixChars === "number")
|
|
124
|
+
return start + len <= meta.enginePrefixChars;
|
|
125
|
+
return false;
|
|
126
|
+
}
|
|
127
|
+
function singleText(m) {
|
|
128
|
+
const c = m.content;
|
|
129
|
+
if (typeof c === "string")
|
|
130
|
+
return c;
|
|
131
|
+
if (Array.isArray(c) && c.length === 1) {
|
|
132
|
+
const b = c[0];
|
|
133
|
+
if (b?.type === "text" && typeof b.text === "string")
|
|
134
|
+
return b.text;
|
|
135
|
+
}
|
|
136
|
+
return undefined;
|
|
137
|
+
}
|
|
138
|
+
export function protectGitFrame(before, trimmed, budgetTokens, frame, charsPerToken) {
|
|
139
|
+
const cpt = charsPerToken ?? DEFAULT_CHARS_PER_TOKEN;
|
|
140
|
+
let carrier;
|
|
141
|
+
for (let i = before.length - 1; i >= 0; i--) {
|
|
142
|
+
const candidate = before[i];
|
|
143
|
+
if (candidate === undefined || messageRole(candidate) !== "user")
|
|
144
|
+
continue;
|
|
145
|
+
const text = singleText(candidate);
|
|
146
|
+
if (text === undefined)
|
|
147
|
+
continue;
|
|
148
|
+
const at = text.indexOf(frame.protectedText);
|
|
149
|
+
if (at === -1 || !engineRegionCovers(candidate, at, frame.protectedText.length))
|
|
150
|
+
continue;
|
|
151
|
+
carrier = candidate;
|
|
152
|
+
break;
|
|
153
|
+
}
|
|
154
|
+
if (carrier === undefined)
|
|
155
|
+
return { messages: trimmed, action: "absent" };
|
|
156
|
+
let out = trimmed;
|
|
157
|
+
let action = "kept";
|
|
158
|
+
if (!out.includes(carrier)) {
|
|
159
|
+
carrier = { role: "user", content: [{ type: "text", text: frame.protectedText }], timestamp: carrier.timestamp ?? Date.now(), engineMinted: true };
|
|
160
|
+
let at = 0;
|
|
161
|
+
for (; at < out.length; at++) {
|
|
162
|
+
const head = out[at];
|
|
163
|
+
if (head === undefined || (!isSummary(head) && singleText(head) !== CONTEXT_TRIM_NOTICE))
|
|
164
|
+
break;
|
|
165
|
+
}
|
|
166
|
+
out = [...out.slice(0, at), carrier, ...out.slice(at)];
|
|
167
|
+
action = "reinserted";
|
|
168
|
+
}
|
|
169
|
+
const segTokens = (text) => estimateTokens({ role: "user", content: [{ type: "text", text }] }, cpt);
|
|
170
|
+
const coreTokens = (view, frameText) => {
|
|
171
|
+
let t = segTokens(frameText);
|
|
172
|
+
for (const m of view)
|
|
173
|
+
if (isSummary(m))
|
|
174
|
+
t += estimateTokens(m, cpt);
|
|
175
|
+
return t;
|
|
176
|
+
};
|
|
177
|
+
if (coreTokens(out, frame.protectedText) <= budgetTokens)
|
|
178
|
+
return { messages: out, action };
|
|
179
|
+
if (frame.substitute !== undefined) {
|
|
180
|
+
const text = singleText(carrier);
|
|
181
|
+
const shrunkText = text.replace(frame.substitute.find, () => frame.substitute.replace);
|
|
182
|
+
if (shrunkText !== text) {
|
|
183
|
+
const shrunkCarrier = { ...carrier, content: [{ type: "text", text: shrunkText }] };
|
|
184
|
+
const idx = out.indexOf(carrier);
|
|
185
|
+
out = [...out.slice(0, idx), shrunkCarrier, ...out.slice(idx + 1)];
|
|
186
|
+
if (coreTokens(out, frame.substitute.replace) <= budgetTokens)
|
|
187
|
+
return { messages: out, action: "shrunk" };
|
|
188
|
+
return { action: "over_budget" };
|
|
189
|
+
}
|
|
190
|
+
}
|
|
191
|
+
return { action: "over_budget" };
|
|
192
|
+
}
|
|
117
193
|
export function guardBudget(model) {
|
|
118
194
|
const window = model.contextTokens ?? model.contextWindow;
|
|
119
195
|
if (!Number.isFinite(window) || window <= 0) {
|
|
@@ -20,6 +20,10 @@ export const NON_GOVERNANCE_MEMORY_CODES = new Set([
|
|
|
20
20
|
"memory.challenge_ledger_oversize",
|
|
21
21
|
"memory.control_plane_rebuilt",
|
|
22
22
|
"memory.control_plane_not_corrupt",
|
|
23
|
+
"memory.erasure_evidence_unavailable",
|
|
24
|
+
"memory.erasure_selector_mismatch",
|
|
25
|
+
"memory.erasure_census_incomplete",
|
|
26
|
+
"memory.erasure_index_residue",
|
|
23
27
|
]);
|
|
24
28
|
export function governanceRetryClass(code) {
|
|
25
29
|
if (Object.prototype.hasOwnProperty.call(GOVERNANCE_CODES, code)) {
|