🤖 feat: RLM Mode — kernel-first exclusive PTC posture with persistent kernel, context isolation, and continual-harness features - #3900
🤖 feat: RLM Mode — kernel-first exclusive PTC posture with persistent kernel, context isolation, and continual-harness features#3900ThomasK33 wants to merge 234 commits into
Conversation
|
Preview deployment for your docs. Learn more about Mintlify Previews.
💡 Tip: Enable Workflows to automatically generate PRs for you. |
|
@codex review |
…inement journal, RLM mode experiment) Conductor for implementing prime-agent-inspired RLM/continual-harness features behind an opt-in RLM sub-experiment of PTC. Mirrors workflows/track1-implementation.js: per-phase implement -> gate+adversarial-review -> fix rounds -> dogfood.
… code_execution RLM Mode is an opt-in sub-experiment of Programmatic Tool Calling (flat flag, gated on the PTC parent at call sites, nested under the PTC toggle in Settings, mirroring the Memory Hot Set precedent). When enabled with PTC and sandbox context, code_execution runs on the persistent per-workspace kernel mount: the guest vars namespace survives across calls/turns and restarts via snapshots, and the tool description advertises those kernel semantics. MUX_SANDBOX_PERSISTENT_MOUNTS=1 remains a dev/test override with unchanged behavior. With the experiment off (and env unset) behavior is byte-identical to before: fresh runtime per call and today's description. The rlm flag plumbs through the experiments path end to end: ExperimentsSchema (send options) -> aiService.streamMessage -> applyToolPolicyAndExperiments. Signed-off-by: Thomas Kosiewski <tk@coder.com>
…verified curl recipe) and note r1 landed
Every mutating memory command (create/str_replace/insert/delete/rename) and
every agent_skill_write/agent_skill_delete now appends exactly one
'refinement' durable event to the acting workspace's session journal
(sharedDurableEventJournal), carrying an inverse payload that byte-exactly
restores the prior file state. Prior contents over 4KB are offloaded to the
session blob store (BlobRef), mirroring hook-context. Evidence records
{workspaceId, toolName, toolCallId?, actor?}.
Always-on and purely additive: journaling failures never fail the tool
(log.debug + continue), read-only ops and failed mutations write no rows.
Cross-workspace caveat (v1): memory/skill files are global/project-scoped
while the journal is per-session; rows land in the acting workspace's log.
Signed-off-by: Thomas Kosiewski <tk@coder.com>
Standalone, always-on-by-usage gate memoizer: 'fingerprint' hashes HEAD sha + 'git diff HEAD' + sorted untracked-not-ignored files with content hashes; 'record <gate> <pass|fail>' and 'check <gate>' store/look up results in a JSON file inside the worktree-local git dir (git rev-parse --git-path), so records are never committed and never invalidate themselves. wait_pr_ready.sh integration was skipped intentionally: it has no local validation step (it only orchestrates remote Codex/review/CI gates), per the phase brief's conditional. Tests spawn the real script against hermetic temp git repos and cover stability, pass/fail round-trip, tracked-edit / untracked-file / staged-change invalidation, and corrupt-store self-healing. Signed-off-by: Thomas Kosiewski <tk@coder.com>
…-handle events
Under an RLM persistent mount, nested mux.* results and code_execution
return values whose JSON serialization exceeds 16KB stop entering the
model context: the model-visible record becomes {handle, preview, size}
(plus a follow-up hint for return values) while the full value stays in
the guest at vars.__hN (monotonic per scope via vars.__handleSeq, so it
snapshots/restores with vars), in the content-addressed blob store, and
in one result-handle durable event whose preview mirrors the
model-visible string exactly. Handle bytes retained in vars are capped
with oldest-first eviction (never the newest handle); the blob remains
the durable copy. RLM off / ephemeral runtimes are byte-identical to
today.
Signed-off-by: Thomas Kosiewski <tk@coder.com>
Signed-off-by: Thomas Kosiewski <tk@coder.com>
Signed-off-by: Thomas Kosiewski <tk@coder.com>
Signed-off-by: Thomas Kosiewski <tk@coder.com>
…escription) Signed-off-by: Thomas Kosiewski <tk@coder.com>
…ervice finalizeAgentTaskReport now invokes sandboxHostService.postTaskTerminalEvent (fire-and-forget, gated on no foreground waiters) so spawned-task completions reach the guest host-event queue in production — previously the hook had zero production callsites and mux.events() always drained empty. Regression tests cover both the posted-event and waiter-suppression branches. Signed-off-by: Thomas Kosiewski <tk@coder.com>
…h lineage listRefinements/rollbackRefinement make the r2 journal actionable: rollbacks apply the recorded inverse (inline or blob-backed) through atomic writes, journal their own refinement row with rollbackOf (so double inversion works), refuse already-rolled-back targets, refuse divergence (later overlapping rows, deleted/recreated files, content drift for rollback rows) unless forced, and confine every touched path to memory scope roots / skill directories with lexical + symlink escape checks that force can never override. Signed-off-by: Thomas Kosiewski <tk@coder.com>
…orce Signed-off-by: Thomas Kosiewski <tk@coder.com>
Assembled in toolAssembly from the sandbox context inside the PTC branch, so the tool only exists when RLM mode is on (nested under the PTC parent); with the experiment off the toolset — and thus every provider request — stays byte-identical. Force stays CLI-only: divergence overrides are a human call. Signed-off-by: Thomas Kosiewski <tk@coder.com>
…refinement_rollback Signed-off-by: Thomas Kosiewski <tk@coder.com>
… memory to current session - P1: the later-rows divergence check now nets out rollback lineage: rows whose effect was itself rolled back are skipped, and live rollback chains conflict only when their parity re-applies an edit or rewinds past the target — so LIFO multi-edit unrolling works for model tool calls without force, while re-applied edits (rollback-of-rollback) still refuse. - P2: workspace-scope memory confinement resolves strictly to the current session's memory root (<sessionDir>/memory) instead of any session subdir under sessionsDir, closing the cross-workspace write leak. Signed-off-by: Thomas Kosiewski <tk@coder.com>
…tCode on undefined assignment) Signed-off-by: Thomas Kosiewski <tk@coder.com>
…action) Signed-off-by: Thomas Kosiewski <tk@coder.com>
…oundary copies, read-file tracking Signed-off-by: Thomas Kosiewski <tk@coder.com>
…rendering Signed-off-by: Thomas Kosiewski <tk@coder.com>
Signed-off-by: Thomas Kosiewski <tk@coder.com>
…est metadata, optional chain Signed-off-by: Thomas Kosiewski <tk@coder.com>
…tch + staleness guard) Signed-off-by: Thomas Kosiewski <tk@coder.com>
Signed-off-by: Thomas Kosiewski <tk@coder.com>
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 9b78f66e94
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
…, kernel types - Memory inserts: staged refine inserts carry target fingerprints (numeric line positions have no content anchor), re-verified inside the memory mutation lock at apply — generalized from the r55 delete guard (fingerprintMutationTarget, expectedTargetFingerprints). - refineRunner: the post-cancellation pendingToolRuns drain is bounded by the shared drain window once the pass signal aborts; wedged runs are handed to the usage-write registry for removal's bounded second chance. - typeGenerator: LoadResult declares hookResult so TypeScript-analyzed kernel programs can branch on load annotations.
|
@codex review |
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 60fdb76b61
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
…l eval fixtures - fileLock: live holders renew the lockfile mtime every lease/4 (stop-and-join at release), so lease-based reclaim on birth-less platforms can never displace a live-but-slow holder mid-mutation (double entry on the same target). Displaced holders never refresh a successor's lease. - memoryService: mutations accept an abortSignal re-checked INSIDE the target mutation lock immediately before the first durable write; consolidation tool checks it at entry and threads it via executeMemoryCommand. Wired from runMemoryConsolidation (live) and refineRunner (defense in depth) so a detached wedged execution cannot commit durable memory or journal into a deleted session directory after teardown. - rlm-eval: one fixture dir per (scenario, config, seed) cell, wiped before setup, so model-written intermediate files cannot leak across cells.
|
@codex review Round 59 fixes (e9eebc8):
Validation: |
This comment has been minimized.
This comment has been minimized.
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: e9eebc8775
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
…olidation Consolidation runs rode only a hard timeout: removal drained branch-summary and refine producers but never aborted MemoryConsolidationService.inFlight, so the r59 entry/pre-commit checks stayed false during removal and a detached run could mutate global/project memory and append its refinement row into the deleted session directory, recreating it. - Each dream/harvest run now registers a per-workspace removal controller; its stream signal is AbortSignal.any(timeout, removal). - cancelInFlightConsolidation aborts controllers and awaits runs bounded by USAGE_WRITE_DRAIN_WINDOW_MS; residuals are handed to the shared usage-write registry for the branch-summary drain's second chance, and anything beyond that refuses pre-commit via the aborted signal. - Removal flows call it before clearPendingBranchSummary (both the main and phantom-metadata paths). - Harvest inbox writes (workspace-scope saveFile/deletePath) now carry the signal into MemoryService's pre-commit check.
|
@codex review Round 60 fix (4659c9c) — workspace teardown now feeds consolidation cancellation:
Behavioral test: an in-flight run on a hanging provider stream is aborted and drained by Validation: |
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 4659c9c4f3
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
This comment has been minimized.
This comment has been minimized.
…-serialized session deletion - New workspaceRemoval.ts: a durable removal tombstone under <xumHome>/locks/ published inside the memory target mutation locks immediately before the session directory is deleted (removeSessionDirUnderMemoryLocks). Deletion serializes with the workspace store root AND the coarse shared-memory key (whose mutations journal into this session dir); fail-closed on a wedged writer — the caller keeps the directory as a recoverable orphan rather than deleting it out from under a live commit. - MemoryService: pre-commit checks (r59) upgraded to assertMutationCommittable, which also refuses when the durable tombstone exists — foreign backends in multi-instance mode observe removal at commit time. resolveStore refuses to materialize scope roots for removed workspaces so ensureRoot cannot recreate the session dir before the in-lock check. - MemoryConsolidationService: teardown marks the workspace in a local cancelled set before aborting; maybeRun/maybeHarvestThenSweep refuse when teardown began (locally or via a foreign tombstone), and controllers registered afterwards start pre-aborted — post-harvest sweeps and retryable-harvest recovery can no longer start fresh un-aborted runs. - WorkspaceService removal routes session-dir deletion through the locked helper.
|
@codex review Round 61 fixes (24a5f39) — explicit durable teardown tombstone + lock-serialized deletion, replacing reliance on the process-local registry:
Validation: |
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 24a5f39393
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
This comment has been minimized.
This comment has been minimized.
- removeSessionDirUnderMemoryLocks publishes the tombstone even when target lock acquisition fails: the caller deregisters the workspace regardless, so the terminal marker must become durable or a foreign backend would keep mutating the retained orphan session forever. Safe outside the locks on this path because the directory is NOT deleted. - isWorkspaceRemovalTombstoned fails closed: only ENOENT means not-removed; other access errors refuse the commit (property-based error-code check). - Root materialization moved INSIDE the target mutation lock: create/saveFile now run assertMutationCommittable then store.ensureRoot() in-lock, so an out-of-lock mkdir can no longer recreate a removed session dir as an empty orphan after removal's serialized deletion (resolveStore no longer takes createRoot). - recordHeadlessUsage gates on the removal tombstone before its sidecar mkdir/append and ledger write — a foreign run's late usage callback cannot recreate the removed session directory. - If config.removeWorkspace fails AFTER tombstone+session deletion, the tombstone is removed so the surviving registered workspace is not permanently refused memory mutations (missing session state self-heals); removal stays retryable. - workspaceRemoval lock test uses an entry signal instead of a fixed sleep.
|
@codex review Round 62 fixes (a1ed863) — all six findings addressed:
Validation: |
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: a1ed863f48
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
This comment has been minimized.
This comment has been minimized.
…le tombstones, startup self-heal - History write lock relocated to <xumHome>/locks/history-<sha>.lock (was inside the session dir, so removal deleted it out from under a foreign backend's in-flight append). Removal's critical section now also holds this lock, and withCrossProcessWriteLock checks the removal tombstone IN-LOCK before ensurePrivateDir — a late append fails instead of recreating the deleted session directory. - recordHeadlessUsage runs its tombstone gate AND its sidecar/ledger commits inside the session-dir target mutation lock that removal's tombstone+delete section also holds, closing the check-then-write window. - removeSessionDirUnderMemoryLocks throws TombstoneNotDurableError when no marker can be published on either attempt (e.g. ENOSPC); WorkspaceService aborts the removal in that case (workspace stays registered + retryable) instead of deregistering a workspace whose orphan session would become writable again. - Tombstone rollback failure after config deregistration failure is logged loudly, and a startup self-heal (healRemovalTombstonesForRegisteredWorkspaces, age-gated 10min) reclaims tombstones for still-registered workspaces so that failure residue cannot brick a workspace across restarts. - workspaceRemoval lock test uses an entry signal instead of a fixed sleep.
|
@codex review Round 63 fixes (a5074c2) — all four findings addressed:
Validation: |
# Conflicts: # src/node/services/workspaceService.ts
The desktop-cleanup removal tests' Partial<Config> stub predates r61-63, where removal derives lock/tombstone paths from config.rootDir — the locked deletion threw on the undefined rootDir and the test's session dir survived. Also assert a non-empty rootDir in removeSessionDirUnderMemoryLocks so a malformed config crashes clearly instead of surfacing as a path.resolve TypeError inside the lock-key derivation.
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: c51aeb2b61
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
…al, and DevTools writes (Codex r64) - History truncation recovery now runs INSIDE the cross-process write lock: an unlocked recovery could mistake another backend's live truncation for a crash and roll it back mid-flight, resurrecting discarded history with mismatched archive/chat state. Read paths probe for recovery artifacts without the lock and only lock when artifacts exist, so clean reads stay lock-free; removal-tombstoned workspaces skip recovery. - /refine apply now requires the hash of the proposal the CALLER'S renderer displayed. The shared transcript alone cannot prove what this user saw: a foreign backend's /refine can replace refine-staged.json and append a newer proposal row only its own renderer displayed, letting the existing staged-file/transcript checks pass against unseen bytes. - DevTools devtools.jsonl commits (append + clear) run inside the sessionDir target mutation lock with an in-lock removal-tombstone recheck, so a foreign backend's step finalization cannot recreate a removed session directory via mkdir.
|
@codex review |
This comment has been minimized.
This comment has been minimized.
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: da47046aa6
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
| collapsed.map(async (edit) => { | ||
| if (edit.tool !== "agent_skill_write") return edit; | ||
| const targetContentHash = await this.fingerprintSkillWriteTarget(projectRoot, edit.input); | ||
| return targetContentHash === undefined ? edit : { ...edit, targetContentHash }; |
There was a problem hiding this comment.
Drop skill edits when fingerprinting fails
When staging an agent_skill_write against a target that cannot be fingerprinted, such as during a transient EACCES or read error, this branch retains the edit without targetContentHash. If access recovers and the file changes before /refine apply, the staged writer receives no expected hash and overwrites the newer contents. Drop or refuse the skill edit when its fingerprint cannot be produced, matching the fail-closed memory-edit path.
Useful? React with 👍 / 👎.
| if (Date.now() - parsed.removedAt < REMOVAL_TOMBSTONE_HEAL_MIN_AGE_MS) continue; | ||
| if (config.findWorkspace(parsed.workspaceId) == null) continue; | ||
| await fsPromises.rm(filePath, { force: true }); |
There was a problem hiding this comment.
Preserve tombstones for removals that are still active
With XUM_ALLOW_MULTIPLE_INSTANCES=1, if removal remains between session deletion and config.removeWorkspace() for over ten minutes—for example while MCPServerManager.stopServers() awaits a wedged instance.close()—a newly started backend sees an old tombstone plus a still-registered workspace and deletes the marker here even though removal is still active. Foreign history or sidecar writers can then pass their durable removal gate and recreate the session directory before deregistration completes. Tombstone age is not proof of a failed removal; distinguish rollback residue explicitly or coordinate healing with the live removal/config transition.
Useful? React with 👍 / 👎.
Summary
Adds RLM Mode — an opt-in, kernel-first execution posture for PTC inspired by PrimeIntellect's prime-agent architecture — plus the continual-harness features around it (refinement journal with rollback,
/refinetrajectory distillation, family messaging, branch summarization, compaction improvements) and a measurement harness (shux rlm-eval) that every major design decision in this PR was validated against.With the RLM experiment off, behavior is byte-identical to main (pinned by composition tests and
replay-verifyon live sessions). With it on,code_executionbecomes the primary tool backed by a persistent per-workspace QuickJS kernel.Background
Research into prime-agent (which posted strong vendor-reported eval results) identified two core ideas worth porting: a single persistent code kernel where in-kernel data never transits model context, and a self-modifying harness with journaled, reversible edits. Mux's Track 1 foundation (journal kit, durable events, sandbox host, replay harness — #3865/#3872) provided the substrate; this PR is "Track 2" built on it, implemented via the phased conductor workflow in
workflows/track2-rlm-implementation.js(per-phase quality gates, adversarial review, live dogfooding).Implementation
RLM kernel (phases r1, r4, r5, r12):
rlm-modeexperiment, nested under PTC; exclusive-only — enabling it forces the kernel-first narrowed toolset (supplement-mode RLM measured ~2x flat cost and was removed)varssurvives calls/turns/restarts via journaled snapshotsshux.*results never enter model context (compact{tool, ok, bytes}summaries); the model's channels are its return value (offloaded via handles >16KB), capped console output, andvarsshux.load({path, key}): host-side bulk file ingestion straight intovars(record shows{key, bytes, lines, preview}only)shux.task_spawn+shux.events(): fire-and-forget sub-agents with admission handles, asyncify-safe event drainContinual harness (r2, r6, r11):
refinementdurable event (blob-backed inverses)rollbackOflineage:shux run debug refinementsCLI + RLM-gatedrefinement_rollbacktool/refine: bounded trajectory-distillation pass (dream-agent machinery) applying smallest evidence-backed edits, journaled and reversibleAgent ops (r3, r7, r8, r9):
task_message_parent/task_message_sibling(RLM stamped on task records at spawn; strict same-parent scoping; server-side labels)scripts/gate_fingerprint.shverification-loop memoizerMeasurement (
scripts/rlm-eval/,make rlm-eval): scenario x config x seed A/B runner extracting mechanical metrics (tokens, cost, wall time, peak context, vars adoption, batch factor, compactions) from session artifacts.Validation
varsadoption 15/16replay-verifyPASS (evidence in the workflow run reports)mux.*alias intactRisks
toolAssembly,code_execution, compaction paths (RLM-gated), task spawn paths (flag stamping).shux.loadmaterializes large files (latent pressure on multi-MB corpora), and one sonnet seed still fragments batching./refineauto-applies edits (no approval UI in v1) — mitigated by journal + rollback + immutable-base guard rails.Pains
mux->shuxrename on main required conflict resolution across the kernel commits (namespace, type-generator identifiers, description text).Generated with
mux• Model:anthropic:claude-fable-5• Thinking:xhigh• Cost:$763.80