Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
202 commits
Select commit Hold shift + click to select a range
bca3764
feat: add Track 2 RLM implementation workflow (persistent kernel, ref…
ThomasK33 Aug 19, 2026
77989e6
r1: add RLM Mode experiment and graduate persistent kernel mounts for…
ThomasK33 Aug 19, 2026
cd87075
workflow: harden track2 dogfood harness (foreground sandbox polling, …
ThomasK33 Aug 19, 2026
ea7fe8b
r2: journal invertible refinement events for memory and skill mutations
ThomasK33 Aug 19, 2026
ca9db79
r3: add gate_fingerprint.sh verification-loop memoizer + bun tests
ThomasK33 Aug 19, 2026
dcaea98
r4: offload oversized kernel results to vars handles + blobs + result…
ThomasK33 Aug 19, 2026
5ff654c
workflow: require incremental commits so timeouts cannot drop a whole…
ThomasK33 Aug 19, 2026
b47073a
r5: registerObject sync methods for post-await-safe namespace members
ThomasK33 Aug 19, 2026
b316aca
r5: sandbox host task-terminal events with r4 handle offload + queue cap
ThomasK33 Aug 19, 2026
8a3ad4b
r5: ToolBridge kernel namespace (task_spawn/events) + kernel type defs
ThomasK33 Aug 19, 2026
25603a3
r5: wire kernel spawn/events into code_execution (RLM-gated types + d…
ThomasK33 Aug 19, 2026
124689a
r5: deliver terminal reports to the parent's sandbox mount from taskS…
ThomasK33 Aug 19, 2026
c4e5595
r6: refinement rollback engine β€” ID-addressed inverse application wit…
ThomasK33 Aug 19, 2026
6f54156
r6: 'bun run debug refinements' CLI β€” list rows, --rollback <id>, --f…
ThomasK33 Aug 19, 2026
b9b603d
r6: RLM-gated refinement_rollback model tool (id + reason, no force)
ThomasK33 Aug 19, 2026
d10c757
r6: fix await-thenable lint in tests + regenerate tool-hook docs for …
ThomasK33 Aug 19, 2026
c2804ae
r6: fix rollback divergence netting (LIFO unroll) + confine workspace…
ThomasK33 Aug 19, 2026
aa30eec
r6: fix exit-code leak in refinements CLI test (Bun keeps nonzero exi…
ThomasK33 Aug 19, 2026
5cdf750
r7: keep-recent tail selection + read-file extraction utils (RLM comp…
ThomasK33 Aug 19, 2026
5ec3d80
r7: RLM keep-recent floor β€” stamped requests, tail exclusion + post-b…
ThomasK33 Aug 19, 2026
544459c
r7: RLM tests β€” summarization tail exclusion + read-files attachment …
ThomasK33 Aug 19, 2026
0bc8624
r7: agentSession test β€” RLM stamp gating for on-send auto-compaction
ThomasK33 Aug 19, 2026
ea1cacb
r7: fix lint β€” avoid Array.isArray readonly-narrowing poison, typed t…
ThomasK33 Aug 19, 2026
03f81e5
r7: startup-recovery tests for preserved-tail follow-up branch (dispa…
ThomasK33 Aug 19, 2026
592640d
r8: persist rlm experiment flag on task records at spawn
ThomasK33 Aug 19, 2026
9b81d4b
r8: taskService family messaging β€” child->parent wake + nuclear-famil…
ThomasK33 Aug 19, 2026
5db8891
r8: task_message_parent/task_message_sibling tools, RLM-gated toolset…
ThomasK33 Aug 19, 2026
77123c1
r8: family messaging tests β€” labeling, dispatch, nuclear-family scopi…
ThomasK33 Aug 19, 2026
1f48314
r8: regenerate synced tool docs for family messaging tools
ThomasK33 Aug 19, 2026
d456608
r9: branch-summary metadata type, constants, truncateAfterMessage ret…
ThomasK33 Aug 19, 2026
bfae09a
r9: branchSummary module + fork/edit-resend wiring (RLM-gated abandon…
ThomasK33 Aug 19, 2026
d73b826
r9: branch-summary tests + defensive generation deadline race
ThomasK33 Aug 19, 2026
a86eb6c
r9: fix type errors in branch-summary test fakes; harden generation d…
ThomasK33 Aug 19, 2026
3451a2c
r9: explicit send-option experiments win over machine overrides; clam…
ThomasK33 Aug 19, 2026
33f70a3
r9: fix dogfood findings β€” word target below token cap, 6s deadline, …
ThomasK33 Aug 19, 2026
6b877c3
r9: tests for budget invariants, boundary trim, deadline salvage, tai…
ThomasK33 Aug 19, 2026
ed2fc4f
r10: kernel-first exclusive posture β€” RLM+exclusive description pream…
ThomasK33 Aug 19, 2026
28cd26e
r10: fix description type narrowing + drop unnecessary assertions in …
ThomasK33 Aug 19, 2026
6fd907d
r11: refine bounds constants + injectable mutation budget/toolCallId …
ThomasK33 Aug 19, 2026
97f42f7
r11: refine runner + RefineService (bounded trajectory-distillation p…
ThomasK33 Aug 19, 2026
081e124
r11: refinements.run oRPC route + RefineService container wiring
ThomasK33 Aug 19, 2026
81663a2
r11: /refine slash command β€” RLM-gated visibility, workspace-only dis…
ThomasK33 Aug 19, 2026
13032a7
r11: refine tests β€” gating, concurrency, no-op, journaled inverses + …
ThomasK33 Aug 19, 2026
f3c6b8b
r11: fix lint in refineService.test.ts β€” pathExists helper instead of…
ThomasK33 Aug 19, 2026
3638137
feat: add RLM lever-eval harness (scenario x config x seed A/B runs w…
ThomasK33 Aug 20, 2026
ab74226
fix(rlm-eval): add --thinking flag; settle turns only after final ass…
ThomasK33 Aug 20, 2026
1013464
workflow: add r12 phase β€” kernel context isolation (close nested-resu…
ThomasK33 Aug 20, 2026
ef98b59
r12: kernel-mode nested-record suppression + console cap β€” nested res…
ThomasK33 Aug 20, 2026
e3c050b
r12: mux.load β€” host-side bulk file ingestion into kernel vars
ThomasK33 Aug 20, 2026
6e0c93b
r12: loads count toward the r4 vars retention cap
ThomasK33 Aug 20, 2026
f477f17
r12: reload rendering of kernel compact records β€” surface bounded sum…
ThomasK33 Aug 20, 2026
816aaf0
r12: fix require-await lint β€” loadFile mock returns Promise.resolve i…
ThomasK33 Aug 20, 2026
d4cfa11
r12: rlm-eval harness β€” orders-filter scenario (504KiB seeded JSONL) …
ThomasK33 Aug 20, 2026
0add780
feat: make RLM Mode exclusive-only β€” rlm implies the kernel-first nar…
ThomasK33 Aug 20, 2026
65360e9
feat(rlm-eval): add wall-time, tool-exec, peak-context, nested-call, …
ThomasK33 Aug 20, 2026
b20e61a
feat(rlm-eval): shard-pipeline scenario + rlm-batch config for measur…
ThomasK33 Aug 20, 2026
ed8a476
feat: promote batching guidance into the kernel-first preamble
ThomasK33 Aug 20, 2026
38a832a
fixup: align r4/r5/r12 kernel surfaces with the Shux rename (shux-pri…
ThomasK33 Aug 20, 2026
2fab208
docs: regenerate tool docs for renamed SHUX_TOOL_INPUT_* env vars
ThomasK33 Aug 20, 2026
f74d7fa
chore: adopt Xum naming in kernel surfaces after rebase
ThomasK33 Aug 21, 2026
4a77f06
fix: bound compact kernel record args + bind gate records to pre-gate…
ThomasK33 Aug 21, 2026
175c4ce
fix: apply tool policy to refinement_rollback + track nested kernel r…
ThomasK33 Aug 21, 2026
f1da111
fix: bound refinement inverse capture in agent_skill_delete (Codex P2)
ThomasK33 Aug 21, 2026
a49161e
fix: serialize rollback + verify post-edit content before restoring (…
ThomasK33 Aug 21, 2026
51c520d
fix: record failed rlm-eval cells and exit nonzero (Codex P2)
ThomasK33 Aug 21, 2026
323b16d
fix: bound load keys, cap vars snapshots, reclaim superseded blobs, m…
ThomasK33 Aug 21, 2026
72082cc
fix: cross-process rollback lock via PID lockfile (Codex P2)
ThomasK33 Aug 21, 2026
d9a00f3
fix: make skill-delete inverse capture lossless and fully bounded (Co…
ThomasK33 Aug 21, 2026
389e65b
fix: make multi-file rollback apply atomic via two-phase staging (Cod…
ThomasK33 Aug 21, 2026
8296a8d
fix: retain settled branch summaries until the first send consumes them
ThomasK33 Aug 21, 2026
7bd7742
fix: terminate the losing branch-summary consumer at the deadline
ThomasK33 Aug 21, 2026
dda1b81
fix: creation-time kernel record bounding, snapshot-safe handles, eva…
ThomasK33 Aug 21, 2026
92a414a
fix: ownership-verified rollback lock reclamation and release (Codex P2)
ThomasK33 Aug 21, 2026
80cb8ef
fix: bound skill-write refinement inverse capture (Codex P2)
ThomasK33 Aug 21, 2026
d229d3f
fix: cancel and drain branch-summary writers before deleting the session
ThomasK33 Aug 21, 2026
8a39969
fix: enforce the /refine deadline independently of provider aborts
ThomasK33 Aug 21, 2026
dc74a20
fix: release the refine model after every pass
ThomasK33 Aug 21, 2026
2cf5ee7
fix: truncate unretainable returns, rewrite failed handles, preassign…
ThomasK33 Aug 21, 2026
ce44fb0
fix: keep the canonical rollback lock occupied during reclamation (Co…
ThomasK33 Aug 21, 2026
db9a26e
fix: retain branch-summary cancellation ownership during the first-se…
ThomasK33 Aug 21, 2026
ea74390
fix: cap RLM family messages + quota result-handle blobs (Codex round 5)
ThomasK33 Aug 21, 2026
42d1505
fix: atomic boundary+tail commit, aggregate family-message budget, fu…
ThomasK33 Aug 21, 2026
5d791ba
fix: verify rollback lock ownership at every commit point (Codex P2)
ThomasK33 Aug 21, 2026
8ee8817
fix: accept the global skills root in rollback confinement (Codex P2)
ThomasK33 Aug 21, 2026
bc0a5e0
fix: mark remote-runtime skill refinement rows non-rollbackable (Code…
ThomasK33 Aug 21, 2026
5493c8c
fix: split existence from readability in runtime skill-write capture …
ThomasK33 Aug 21, 2026
c3f52fe
fix: bail out of a send parked on the branch-summary await once disposed
ThomasK33 Aug 21, 2026
f5fa21e
fix: cancel and drain running /refine passes before workspace removal
ThomasK33 Aug 21, 2026
26b03d9
fix: serialize blob publication against reclamation via a journal blo…
ThomasK33 Aug 21, 2026
bb831d9
fix: make sandbox blob reclamation incremental with a recovery sweep
ThomasK33 Aug 21, 2026
e515941
refactor: extract shared blob-reclamation kit from the sandbox host
ThomasK33 Aug 21, 2026
77de569
fix: quota refinement inverse blobs to a per-session rollback horizon
ThomasK33 Aug 21, 2026
a23885d
fix: reject link-substituted confinement roots during rollback (Codex…
ThomasK33 Aug 21, 2026
7834ba4
fix: add a receiver-side ceiling to family-message budgets (Codex P2)
ThomasK33 Aug 21, 2026
deac9dc
fix: reject partial memory-directory delete inverses (Codex P2)
ThomasK33 Aug 21, 2026
2be0d63
fix: serialize journal sequence assignment across processes
ThomasK33 Aug 21, 2026
86b51f2
fix: quota-manage small refinement inverse captures (no inline immunity)
ThomasK33 Aug 21, 2026
909675c
fix: NUL-terminate untracked manifest records in gate fingerprints (C…
ThomasK33 Aug 21, 2026
1a77095
fix: record branch-summary model usage against the target workspace
ThomasK33 Aug 21, 2026
c7f1e9c
test: satisfy require-await in branch-summary usage-recorder fakes
ThomasK33 Aug 21, 2026
bec7002
fix: joint cross-quota blob retention (delete once every retainer rel…
ThomasK33 Aug 21, 2026
51d1515
refactor: extract the cross-process file-lock protocol from the journal
ThomasK33 Aug 21, 2026
7bd7e57
fix: serialize blob publication and reclamation across processes
ThomasK33 Aug 21, 2026
70bce36
style: use optional chaining in the snapshot-latest resolver seed check
ThomasK33 Aug 21, 2026
e0501aa
fix: bound kernel record errors, traverse nested reads newest-first, …
ThomasK33 Aug 21, 2026
4aa6dad
fix: distinguish stale journal locks after PID reuse
ThomasK33 Aug 21, 2026
6a5fd0d
fix: make context reset durably invalidate old kernel snapshots
ThomasK33 Aug 21, 2026
2077c6c
fix: await cancellation and tool settlement before releasing a refine…
ThomasK33 Aug 21, 2026
172010f
fix: record usage from partially completed refine streams
ThomasK33 Aug 21, 2026
d7c34f0
fix: report applied-but-unjournaled refine edits instead of a no-op
ThomasK33 Aug 21, 2026
abfb5df
fix: correct mux.load line counts and index-zero snapshot boundaries …
ThomasK33 Aug 21, 2026
d34eaea
fix: preserve fresh lock owners against stale-read reclaimers
ThomasK33 Aug 21, 2026
5724aad
fix: re-verify lock ownership before irreversible journal mutations
ThomasK33 Aug 21, 2026
bbb69a7
fix: start the fork branch summary only after failure-prone setup com…
ThomasK33 Aug 21, 2026
0efb027
fix: store branch summaries as assistant rows, not user rows
ThomasK33 Aug 21, 2026
e87bccc
fix: stage refine edits for explicit approval instead of auto-applying
ThomasK33 Aug 21, 2026
712141a
fix: backfill persisted PTC/RLM overrides in assembly, escape read pa…
ThomasK33 Aug 21, 2026
bc9a53e
fix: make concurrent first sends wait for the branch summary
ThomasK33 Aug 21, 2026
2b71b1b
fix: filter compaction artifacts from abandoned-branch summary input
ThomasK33 Aug 21, 2026
5fb7667
fix: point the refine rollback hint at commands that actually exist
ThomasK33 Aug 21, 2026
0ccc3c7
fix: type the PTC experiment backfill against the concrete experiment…
ThomasK33 Aug 21, 2026
5043c67
fix: collision-free synthetic toolCallIds for bridged PTC executions
ThomasK33 Aug 21, 2026
b0736b3
fix: store refine summaries as assistant rows, not user rows
ThomasK33 Aug 21, 2026
4914ca2
fix: drain background usage producers before the child usage rollup
ThomasK33 Aug 21, 2026
9f122fd
fix: run admitted /refine applies to completion under removal
ThomasK33 Aug 21, 2026
df65e03
fix: preserve whitespace in tracked read-file path identity
ThomasK33 Aug 21, 2026
8502152
fix: reject unknown /refine arguments instead of starting a fresh run
ThomasK33 Aug 21, 2026
57940aa
fix: propagate kernel cancellation into mux.load I/O
ThomasK33 Aug 21, 2026
8a4613b
fix: never keep handle-tier return values inline on store failure
ThomasK33 Aug 21, 2026
aff0c02
fix: re-derive quota retention when foreign appends move the journal
ThomasK33 Aug 21, 2026
02b46fc
fix: rewrite load records as failures when the snapshot rejects them
ThomasK33 Aug 21, 2026
79d3479
fix: bound console retention at capture time inside the QuickJS runtime
ThomasK33 Aug 21, 2026
1737580
fix: drain refine/branch-summary producers before checkout deletion
ThomasK33 Aug 21, 2026
6834542
fix: deliver family-message payloads as assistant rows, not user rows
ThomasK33 Aug 21, 2026
9689c9a
fix: render staged refine edits in full and bind apply to their hash
ThomasK33 Aug 21, 2026
a113e27
fix: serialize rollback verify+apply with ordinary writers (Codex P2)
ThomasK33 Aug 21, 2026
8b7eb3e
tests: resolve blob-offloaded capture contents in the runtime-path de…
ThomasK33 Aug 21, 2026
409a6f2
fix: enforce the mux.load byte ceiling while consuming the stream
ThomasK33 Aug 21, 2026
68df09f
fix: treat unserializable console records as over budget
ThomasK33 Aug 21, 2026
ba7bf15
fix: forward readFile stream cancellation to the underlying source
ThomasK33 Aug 21, 2026
87a36bd
fix: retain the family-message budget charge once the payload persists
ThomasK33 Aug 21, 2026
acd56b5
fix: validate staged memory mutations against the real write rules
ThomasK33 Aug 21, 2026
4d5d1c5
fix: journal apply progress so a crash cannot replay refine edits
ThomasK33 Aug 21, 2026
d46427d
fix: extend target mutation locks across processes
ThomasK33 Aug 21, 2026
5f29885
fix: migrate the rollback lockfile to the birth-token file-lock protocol
ThomasK33 Aug 21, 2026
5aea082
fix: forward caller aborts into local readFile streams
ThomasK33 Aug 21, 2026
bf0acc3
fix: journal the rollback row before releasing the target locks
ThomasK33 Aug 21, 2026
5b933ff
test: avoid closure-narrowing lint error in the durable-order test
ThomasK33 Aug 21, 2026
a00817f
fix: deliver sibling family-message payloads as assistant rows too
ThomasK33 Aug 21, 2026
ebf41f4
fix: validate staged memory mutations against the RESULTING file size
ThomasK33 Aug 21, 2026
9721fdf
fix: validate staged skill writes with the real tool's checks
ThomasK33 Aug 21, 2026
5a42435
fix: normalize staged skill paths with the write path's own resolver
ThomasK33 Aug 21, 2026
0d8d153
fix: validate staged delete/rename proposals against the real handlers
ThomasK33 Aug 21, 2026
674792b
fix: mirror the scope file-count cap in create staging validation
ThomasK33 Aug 21, 2026
ee8bd16
fix: cap family-message titles and charge the rendered payload length
ThomasK33 Aug 21, 2026
820acf1
fix: charge family-message trigger rows against the send budgets
ThomasK33 Aug 21, 2026
00ef06a
fix: reject renaming a memory directory into its own subtree
ThomasK33 Aug 21, 2026
09e23e2
fix: slice branch-summary deltas to the accumulation cap before appen…
ThomasK33 Aug 21, 2026
d2d7e42
fix: exclude unsuccessful nested file reads from tracked paths
ThomasK33 Aug 21, 2026
c15994e
fix: resolve missing RLM flags through backend experiment overrides
ThomasK33 Aug 21, 2026
efcf6c2
fix: charge the queue-join separator in family-message budgets
ThomasK33 Aug 21, 2026
553f516
fix: compare physical identities in the own-subtree rename guard
ThomasK33 Aug 21, 2026
1e45093
fix: truncate unserializable kernel return values instead of inlining
ThomasK33 Aug 21, 2026
d640867
fix: confine branch-summary side-channel models to workspace providers
ThomasK33 Aug 21, 2026
c5a1cc2
Merge origin/main (.xum canonical metadata rename) into research-qr9r
ThomasK33 Aug 21, 2026
7cbee86
fix: stage refine edits in model proposal order, not completion order
ThomasK33 Aug 21, 2026
65312fc
chore: fix formatting in agent_skill_delete
ThomasK33 Aug 21, 2026
d93ae33
test: park the dispose-race send via workspace-provider metadata
ThomasK33 Aug 22, 2026
cc06f37
fix: measure vars retention budgets in UTF-8 bytes, not UTF-16 units
ThomasK33 Aug 22, 2026
7f4907a
fix: derive handle sequence from live keys, surviving counter clobbers
ThomasK33 Aug 22, 2026
00aa74d
fix: truncate kernel previews by UTF-8 bytes, not UTF-16 code units
ThomasK33 Aug 22, 2026
d1b18ab
fix: preserve whitespace when merging read-file paths
ThomasK33 Aug 22, 2026
e0f2107
fix: register family-message result schemas for kernel type generation
ThomasK33 Aug 22, 2026
d51b3d2
fix: recover journaled refine edits into the attempted set before replay
ThomasK33 Aug 22, 2026
dba5e3e
fix: reclaim newly-created blobs when journal publication fails
ThomasK33 Aug 22, 2026
a4d9be5
fix: address Codex r24 findings in branch summary side channel
ThomasK33 Aug 22, 2026
d185e07
fix: serialize family-message delivery per target and defer removal r…
ThomasK33 Aug 22, 2026
78bae8a
fix: address Codex r25 findings
ThomasK33 Aug 22, 2026
d0f2ce5
fix: name family-message payloads by ID in triggers, not adjacency
ThomasK33 Aug 22, 2026
5aa1727
fix: stop double-counting cached tokens in eval peak-context metric
ThomasK33 Aug 22, 2026
41051b1
fix: recheck target existence under the task-tree lifecycle lock befo…
ThomasK33 Aug 22, 2026
ee88030
fix: truncate kernel capture previews by UTF-8 bytes (Codex r27)
ThomasK33 Aug 22, 2026
e2527dd
fix: keep handle sequencing collision-free at the safe-integer ceilin…
ThomasK33 Aug 22, 2026
edccebf
fix: share refinement capture budgets across both project skill dirs
ThomasK33 Aug 22, 2026
5bbb5fa
fix: run legacy skill migration under the target mutation lock
ThomasK33 Aug 22, 2026
68bf664
fix: clear staged refine set only after the audit summary is appended
ThomasK33 Aug 22, 2026
078fb0b
fix: capture the legacy manifest in canonical SKILL.md delete inverses
ThomasK33 Aug 22, 2026
52e03dd
fix: normalize a guest-primitive vars namespace before storing a hand…
ThomasK33 Aug 22, 2026
2b2adff
fix: publish result-handle events only after the vars snapshot commit…
ThomasK33 Aug 22, 2026
9ab4377
fix: fail context reset when the sandbox invalidation is not durable
ThomasK33 Aug 22, 2026
50158ff
fix: clear post-compaction read carryover when a new context segment …
ThomasK33 Aug 22, 2026
5c86f01
fix: require token boundaries in eval verifier answer matching
ThomasK33 Aug 22, 2026
40e1aaa
fix: verify mux.load actually stores the requested vars key (Codex r29)
ThomasK33 Aug 22, 2026
cd51f2c
fix: make the context-boundary post-compaction discard durable-or-fail
ThomasK33 Aug 22, 2026
59b6d5f
fix: deliver family payloads atomically with their triggers (Codex r30)
ThomasK33 Aug 22, 2026
a5f825c
fix: keep the /refine and dream fallback on the workspace's selected …
ThomasK33 Aug 22, 2026
b1ec624
fix: address Codex r32 findings across refine, sandbox, and session a…
ThomasK33 Aug 22, 2026
9066a7a
fix: address Codex round-33 findings (refine failure reporting, audit…
ThomasK33 Aug 22, 2026
c24bfe7
fix: persist per-edit success outcomes so apply recovery reconstructs…
ThomasK33 Aug 22, 2026
b141504
fix: address Codex round-34 findings (persist failed apply outcomes, …
ThomasK33 Aug 22, 2026
48392e0
fix: filter empty text parts from both sides of the Anthropic assista…
ThomasK33 Aug 22, 2026
6db1250
fix: re-attempt pending durable cleanup on the context-reset no-op pa…
ThomasK33 Aug 22, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -213,6 +213,9 @@ dev-desktop-sandbox: ## Start an isolated Electron dev instance (fresh XUM_ROOT
dev-server-sandbox: ## Start an isolated dev-server instance (fresh XUM_ROOT + free ports)
@bun scripts/dev-server-sandbox.ts $(DEV_SERVER_SANDBOX_ARGS)

rlm-eval: ## Run the RLM lever eval against a running dev-server sandbox (see scripts/rlm-eval/run.ts header)
@bun run scripts/rlm-eval/run.ts $(RLM_EVAL_ARGS)

start: node_modules/.installed build-main build-preload build-static ## Build and start Electron app
@NODE_ENV=development XUM_PROFILE_REACT=$(XUM_PROFILE_REACT) bunx electron --remote-debugging-port=9222 .

Expand Down
29 changes: 29 additions & 0 deletions docs/hooks/tools.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -612,6 +612,16 @@ If a value is too large for the environment, it may be omitted (not set). Xum al

</details>

<details>
<summary>refinement_rollback (2)</summary>

| Env var | JSON path | Type | Description |
| ----------------------- | --------- | ------ | ------------------------------------------------------------------ |
| `XUM_TOOL_INPUT_ID` | `id` | string | Refinement row id (envelope id) to roll back |
| `XUM_TOOL_INPUT_REASON` | `reason` | string | Why this refinement is being rolled back (recorded in the journal) |

</details>

<details>
<summary>review_pane_update (4)</summary>

Expand Down Expand Up @@ -721,6 +731,25 @@ If a value is too large for the environment, it may be omitted (not set). Xum al

</details>

<details>
<summary>task_message_parent (1)</summary>

| Env var | JSON path | Type | Description |
| ------------------------ | --------- | ------ | ------------------------------------------- |
| `XUM_TOOL_INPUT_MESSAGE` | `message` | string | Message to queue for your parent workspace. |

</details>

<details>
<summary>task_message_sibling (2)</summary>

| Env var | JSON path | Type | Description |
| ------------------------ | --------- | ------ | ------------------------------------------------------------ |
| `XUM_TOOL_INPUT_MESSAGE` | `message` | string | Message to deliver to the sibling task. |
| `XUM_TOOL_INPUT_TASK_ID` | `task_id` | string | Sibling task ID; it must share your direct parent workspace. |

</details>

<details>
<summary>task_remove (2)</summary>

Expand Down
241 changes: 241 additions & 0 deletions scripts/gate_fingerprint.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,241 @@
#!/usr/bin/env bash
#
# gate_fingerprint.sh β€” memoize expensive verification gates (e.g. `make
# static-check`) against a content fingerprint of the current worktree.
#
# Why: agent validation loops often re-run identical gates against identical
# trees. Recording each gate outcome keyed by a worktree fingerprint lets
# callers skip a re-run when nothing has changed since the last run.
#
# Usage:
# scripts/gate_fingerprint.sh fingerprint
# Print the current worktree fingerprint (sha256 hex) and exit 0.
#
# scripts/gate_fingerprint.sh record <gate> <pass|fail> <fingerprint>
# Store the result for <gate> keyed by <fingerprint>, which MUST be the
# fingerprint captured BEFORE the gate ran. Recording is refused when the
# worktree no longer matches it: the gate's outcome describes the tree it
# actually tested, and binding the record to a tree that changed mid-run
# would let later `check` calls skip validation of untested changes.
#
# scripts/gate_fingerprint.sh check <gate>
# Exit 0 and print the cached result (pass|fail) when the recorded
# fingerprint for <gate> matches the current worktree fingerprint.
# Exit 1 when there is no record or it is stale: the caller must re-run
# the gate and `record` the fresh outcome.
#
# Example fast path around a gate:
# if result=$(scripts/gate_fingerprint.sh check static-check); then
# [ "$result" = pass ] || exit 1 # cached fail
# else
# fp=$(scripts/gate_fingerprint.sh fingerprint)
# if make static-check; then
# scripts/gate_fingerprint.sh record static-check pass "$fp"
# else
# scripts/gate_fingerprint.sh record static-check fail "$fp"
# exit 1
# fi
# fi
#
# Fingerprint = sha256 over:
# - HEAD commit sha
# - `git diff HEAD` (tracked changes, staged and unstaged; binary edits are
# still captured via the blob hashes on `index` lines)
# - sorted untracked-not-ignored file list with per-file content hashes
#
# Results live in a JSON file inside the worktree-local git dir (resolved via
# `git rev-parse --git-path`), so they are never committed, never fingerprint
# themselves, and do not leak across worktrees.
set -euo pipefail

STORE_BASENAME=gate_fingerprints.json

die() {
# Plain-text prefix: the repo bans emoji status indicators (inconsistent
# rendering across platforms/fonts).
echo "error: $*" >&2
exit 1
}

usage() {
cat >&2 <<'EOF'
Usage: gate_fingerprint.sh <subcommand>
fingerprint Print the current worktree fingerprint.
record <gate> <pass|fail> <fingerprint>
Store a gate result for <fingerprint> (captured
via `fingerprint` BEFORE the gate ran). Refused
when the worktree changed since then.
check <gate> Print cached result and exit 0 when fresh;
exit 1 when stale or missing (caller re-runs).
EOF
exit 1
}

sha256_stream() {
if command -v sha256sum >/dev/null 2>&1; then
sha256sum | awk '{print $1}'
else
# macOS ships shasum but not always coreutils sha256sum.
shasum -a 256 | awk '{print $1}'
fi
}

# Keep gate keys shell/JSON/filename-friendly so callers can't smuggle in
# surprising strings (defensive: crash early on typos like an empty name).
assert_gate_name() {
[[ "$1" =~ ^[A-Za-z0-9][A-Za-z0-9._-]*$ ]] \
|| die "invalid gate name '$1' (expected [A-Za-z0-9._-], starting alphanumeric)"
}

resolve_store_path() {
# For a custom (non-shared) filename, --git-path resolves inside the
# worktree-local git dir, e.g. .git/worktrees/<name>/ for linked worktrees.
git rev-parse --path-format=absolute --git-path "$STORE_BASENAME"
}

# Untracked-not-ignored manifest: sorted paths with per-file content hashes
# plus the metadata a gate outcome can depend on β€” the executable bit (a
# chmod +x changes how builds/tests run the file) and symlink identity (a
# symlink must fingerprint as its target STRING, not the referent's content,
# and must never collide with a regular file of the same bytes).
# NUL-delimited plumbing AND NUL-terminated records: paths are the final
# field and may legally contain newlines, so a newline-terminated record
# would be ambiguous (one crafted filename could encode the same bytes as
# two separate records, letting a cached gate be reused for a different
# worktree). Paths can never contain NUL, so a NUL terminator keeps every
# record boundary unambiguous.
emit_untracked_manifest() {
git status --porcelain=v1 -z -uall --no-renames \
| while IFS= read -r -d '' entry; do
if [ "${entry:0:2}" = '??' ]; then
printf '%s\0' "${entry:3}"
fi
done \
| LC_ALL=C sort -z \
| while IFS= read -r -d '' path; do
if [ -h "$path" ]; then
# Hash the link target text (targets may contain arbitrary bytes).
# `--` terminates option parsing: a root-level symlink named like
# `-n` or `--help` is a legal Git path and must be an operand.
printf 'symlink %s %s\0' "$(readlink -- "$path" | sha256_stream)" "$path"
elif [ -f "$path" ] && [ -r "$path" ]; then
if [ -x "$path" ]; then mode=x; else mode=-; fi
printf '%s %s %s\0' "$(sha256_stream <"$path")" "$mode" "$path"
else
# Unreadable/special entries still perturb the fingerprint
# deterministically instead of aborting.
printf 'unhashable %s\0' "$path"
fi
done
}

compute_fingerprint() {
# Section markers keep the concatenation unambiguous (a diff line can never
# be confused with an untracked-manifest line).
{
printf 'head %s\n' "$(git rev-parse HEAD)"
printf '%s\n' '== tracked diff =='
# --no-ext-diff/--no-color pin the output to stable builtin rendering
# regardless of user diff config.
git diff --no-ext-diff --no-color HEAD --
printf '%s\n' '== untracked =='
emit_untracked_manifest
} | sha256_stream
}

# Load the store as a JSON object, self-healing: a missing or corrupt store
# resets to '{}' (worst case we re-run a gate; never fail the caller on it).
load_store() {
local store="$1" current
if [ -f "$store" ] \
&& current=$(jq -ce 'if type == "object" then . else error("not an object") end' "$store" 2>/dev/null); then
printf '%s' "$current"
else
printf '{}'
fi
}

cmd_fingerprint() {
compute_fingerprint
}

cmd_record() {
local gate="$1" result="$2" fp="$3" current store tmp
assert_gate_name "$gate"
case "$result" in
pass | fail) ;;
*) die "result must be 'pass' or 'fail', got '$result'" ;;
esac
[[ "$fp" =~ ^[0-9a-f]{64}$ ]] || die "fingerprint must be a sha256 hex string (capture it via 'fingerprint' before running the gate)"

# Bind the record to the tree the gate actually tested: if the worktree
# changed while the gate ran, the outcome does not describe the current
# tree and caching it would let `check` skip validating untested changes.
current=$(compute_fingerprint)
[ "$current" = "$fp" ] \
|| die "worktree changed while the gate ran (fingerprint $fp -> $current); re-run the gate on the current tree"

store=$(resolve_store_path)
# Write via temp file + rename so a crash cannot leave a torn store.
tmp=$(mktemp "${store}.tmp.XXXXXX")
load_store "$store" \
| jq --arg gate "$gate" --arg fp "$fp" --arg result "$result" \
'.[$gate] = {fingerprint: $fp, result: $result, recorded_at: (now | floor)}' \
>"$tmp"
mv -f "$tmp" "$store"
}

cmd_check() {
local gate="$1" fp store cached
assert_gate_name "$gate"

store=$(resolve_store_path)
fp=$(compute_fingerprint)
cached=$(load_store "$store" \
| jq -r --arg gate "$gate" --arg fp "$fp" \
'.[$gate] // empty | select(.fingerprint == $fp) | .result // empty')
case "$cached" in
pass | fail)
printf '%s\n' "$cached"
;;
'')
echo "no fresh record for gate '$gate' (stale or never recorded); re-run the gate" >&2
exit 1
;;
*)
# A record whose result is neither pass nor fail is corrupt: treat as
# stale rather than propagating garbage to the caller.
echo "corrupt record for gate '$gate'; re-run the gate" >&2
exit 1
;;
esac
}

command -v jq >/dev/null 2>&1 || die "missing required command: jq"
git rev-parse --git-dir >/dev/null 2>&1 || die "not inside a git repository"
# `git status --porcelain` paths are toplevel-relative; run there so the
# untracked hashing works no matter where the caller invoked us from.
cd "$(git rev-parse --show-toplevel)"
git rev-parse -q --verify HEAD >/dev/null || die "repository has no HEAD commit"

[ $# -ge 1 ] || usage
SUBCOMMAND="$1"
shift

case "$SUBCOMMAND" in
fingerprint)
[ $# -eq 0 ] || usage
cmd_fingerprint
;;
record)
[ $# -eq 3 ] || usage
cmd_record "$1" "$2" "$3"
;;
check)
[ $# -eq 1 ] || usage
cmd_check "$1"
;;
*)
usage
;;
esac
Loading
Loading