Uh oh!
There was an error while loading. Please reload this page.
🤖 feat: make provider requests a pure function of the session log + sandboxed plugin hooks - #3872
Conversation
…ppend time Log purity: the provider request must be a pure function of chat.jsonl (MODEL-VISIBLE implies LOGGED). Remove the two remaining request-time injectors from the message pipeline: - File-change notifications: AgentSession.streamWithHistory now detects external edits after commitPartial and appends the <system-file-update> row durably to history BEFORE the history read that builds the request. FileChangeTracker state updates on detection, so retries cannot append duplicate rows. injectFileChangeNotifications is deleted; the message builder moved to fileChangeTracker.ts (createFileChangeNotificationMessage). - @file mentions: the send-time snapshot materialization path (materializeFileAtMentionsSnapshot -> persisted fileAtMentionSnapshot rows) is now the ONLY expansion path. The request-time live-disk-read fallback (injectFileAtMentions) is deleted. Old histories that predate materialization still build: un-materialized mentions stay plain text. prepareMessagesForProvider no longer takes runtime/workspacePath/ abortSignal/changedFileAttachments — it cannot read live workspace state, so rebuilding a request from the same log rows is deterministic. Tests: durable row lands before the request (and survives with the same id the model saw), no duplicate row on a second stream, double-build from one log is identical, and old-format histories build without errors. Signed-off-by: Thomas Kosiewski <tk@coder.com>
…succeeds
Codex P2: FileChangeTracker.getChangedAttachments() mutated fileState during
detection, so a startup abort or appendToHistory failure between detection and
the durable <system-file-update> append made retries see mtime <= tracked
timestamp — the external edit was silently dropped and never reached
chat.jsonl or the model.
Detection is now side-effect-free: getChangedAttachments() returns
{ attachments, commit } and AgentSession.streamWithHistory invokes commit()
only after the notification row is durably appended. commit() also refuses to
clobber state recorded after detection with the older detection snapshot.
Tests: retry after a one-shot appendToHistory failure re-detects and persists
the notification exactly once (agentSession.fileChangeNotification.test.ts);
re-detection before commit() returns the same change while post-commit checks
return none (agentSession.changeDetection.test.ts).
Signed-off-by: Thomas Kosiewski <tk@coder.com>Durable workflow driving p1-p5 (log purity, turn envelopes, determinism harness, sandboxed hooks, manifest graduation) with per-phase quality gates, adversarial review, and dogfooding. Empty fix patches are tolerated; environmental dogfood failures retry without a fix round; skipImplement args support re-runs after partial completion.
After the final system prompt and toolset are settled (post request.assemble middleware, post tool-policy rebuild) and before streaming starts, aiService.streamMessage appends a 'turn-envelope' row to the session's DurableEventJournal: content-addressed system prompt blob (dedupes unchanged prompts), name-sorted toolset manifest with stable-stringified input-schema hashes, modelString, effective thinkingLevel, and a providerOptions hash (raw options never persisted; they may embed auth-adjacent config). Emission is purely additive and never fails the turn: failures are logged and streaming continues. Retries/continuations re-enter streamMessage and emit their own row. Signed-off-by: Thomas Kosiewski <tk@coder.com>
…ifest Blocking review findings: buildToolsetManifest only read .inputSchema, so AI SDK v3-style tools declaring .parameters (or custom .schema adapters) all hashed identically as the empty schema, and a sparse tools map entry threw a TypeError that silently aborted turn-envelope emission. Fall back through inputSchema ?? parameters ?? schema with safe optional access. Signed-off-by: Thomas Kosiewski <tk@coder.com>
…pes without throwing buildToolsetManifest passed raw schema objects straight to asSchema, which treats non-Schema, non-standard-schema objects as lazy schema functions and throws TypeError. Any turn containing MCP tools (inputSchema jsonSchema wrappers), sanitizeToolSchemaForOpenAI output, or plain JSON Schema .parameters/.schema silently dropped its turn-envelope row, breaking the 'model-visible implies logged' invariant. Extract the JSON schema by shape (wrapper unwrap, plain-schema passthrough, asSchema fallback with catch) and cover those runtime shapes in tests. Signed-off-by: Thomas Kosiewski <tk@coder.com>
Regression net for the log-purity invariant (model-visible implies logged): - replayRequestBuilder: rebuilds one turn's provider request purely from durable logs (chat.jsonl slice + turn-envelope row + blob-stored system prompt), reusing the production pipeline (prepareProviderRequestMessages, addInterruptedSentinel, prepareMessagesForProvider, cacheStrategy system wrapping) and the real streamText ModelMessage->LanguageModelV4 conversion captured through a no-network stub model with StreamManager's per-step transforms. - replayVerify: byte-compares the reconstruction against the recorded request in devtools.jsonl (llmDebugLogs) per turn — system prompt blob vs wire system message, envelope toolset manifest vs re-hashed wire tool schemas (shared hashToolSchema), full LM prompt JSON byte equality with first-divergence reporting. Guarantee scope: same log + same config + same binary. - cacheAudit: diffs consecutive turn-envelope rows and attributes prompt- prefix invalidations (system prompt / toolset delta / model / thinking / provider options) with approximate busted-token attribution from recorded usage (fresh input + cache-write tokens). - debug CLI: 'bun run debug replay-verify <ws>' (exit 1 on FAIL, prints first divergence) and 'bun run debug cache-audit <ws>'. - committed sanitized golden fixture session (3 turns, anthropic cached- system shape, one prefix bust) + CI tests: fixture byte-equality replay, double-build determinism, and cache-bust attribution. Regenerate via MUX_REGENERATE_REPLAY_FIXTURE=1. Signed-off-by: Thomas Kosiewski <tk@coder.com>
Signed-off-by: Thomas Kosiewski <tk@coder.com>
…e + compaction handling Fix round 1 for the determinism harness blocking findings: - CLI: 'bun run debug replay-verify replay-fixture' and 'cache-audit replay-fixture' now map the well-known fixture workspace ID to the committed fixture session dir (resolveReplaySessionDir). - Compaction: both CLI commands load the FULL history via iterateFullHistory (collectFullHistory); replayVerifySession re-slices each turn to its own compaction epoch (sliceEpochForTurn, same boundary semantics as getHistoryFromLatestBoundary), so compacted sessions no longer produce false divergences. - Pairing: turn-envelope rows and devtools runs now record requestHistorySequence (join key); the verifier pairs envelope/recorded/assistant by that key, so a failed stream, a devtools toggle, or a retry skips only its own turn instead of cascading false FAILs. Fully-legacy logs fall back to ordinal pairing with a note. - Crash isolation: buildReplayRequest runs under a per-turn try/catch; corrupted turns FAIL with the error message and verification continues. - Scope limitations removed: the turn envelope now logs the resolved wireProviderName (Coder instance-typed gateways), the per-send Anthropic cache TTL, and blob-stores injected plan-transition content and post-compaction attachments; replay reads them back, making those turns byte-reconstructible instead of documented FAILs. - Fixture regenerated with the new fields; new replayVerify.test.ts covers compaction, missing assistant rows, devtools toggling, retries, corrupted turns, and the plan/TTL round-trip. Signed-off-by: Thomas Kosiewski <tk@coder.com>
…nd replay
Live dogfooding caught replay-verify failing on every turn with
schema-changed:web_search: the envelope hashed the runtime tool's
client-side inputSchema while replay could only hash the wire record
{type, id, args} — the schema never crosses the wire, so they could
never match. Both sides now fingerprint provider-defined tools by
canonical wire identity (id + args), which also makes args changes
(e.g. maxUses) correctly attributable by the cache-bust auditor.- AgentPluginInfo gains hooksPath, resolved with the same §6.2 component rules as mcp.json (wrong-kind/escape invalidates only that component). - validatePluginManifest carries object 'extensions' through opaquely so Mux namespace consumers (plugin hook capability requests) can read them. - computeAgentPluginContainers centralizes the container list + Project Trust gating previously inlined in the plugin MCP provider. Signed-off-by: Thomas Kosiewski <tk@coder.com>
…eware Each plugin's hooks.js (a script evaluating to an object mapping hook names to functions, mirroring OpenCode's vocabulary) loads into its own persistent SandboxHostService mount per workspace session; a host-side adapter registers one spine middleware per loaded hook. Supported points: tool.execute.before (args rewrite / deny), tool.execute.after (observe / annotate via hook_output), request.assemble (contribute context). - Asyncify constraint respected: hook mounts register no asyncified bridge functions; 'mux' is a pure guest Proxy throwing catchable "Capability denied" errors, and hooks run inside runtime.eval whose resolve loop settles async hook promises. - Capabilities: least-privilege grants by default; manifests may request tool visibility via extensions.mux.hooks.tools. tool.execute hooks are invoked only for granted tools, so visibility AND mutation stay bounded. Project-scope plugins remain Project-Trust-gated at discovery (shared computeAgentPluginContainers). - Log purity: request.assemble contributions are materialized as durable hook-context rows (inline text or blob) BEFORE the request mutation; the turn envelope then hashes the final post-hook prompt, keeping "model-visible implies logged" byte-replayable. - Failure posture: crashes, timeouts (per-eval deadline), unreadable or malformed hooks.js are logged and skipped without breaking the turn; only explicit denials surface to the model as tool errors. - Lifecycle: aiService reconciles hooks before request assembly (gated on EXPERIMENT_IDS.AGENT_PLUGINS, fingerprint no-op on the hot path); workspace archive/removal disposes hook mounts and middleware. - Tests: QuickJS integration suite (isolated in CI like other QuickJS suites) covers .env-read denial, grant bounding, annotation, journaled context, catchable capability denial, crash/timeout isolation, broken sibling isolation, edit-reload/disable teardown, and a replay fixture proving the p3 determinism harness stays green with hooks active. Signed-off-by: Thomas Kosiewski <tk@coder.com>
…onents Graduates plugin.json into the unified distribution unit: a Mux 'contributes' extension block declares slash commands (name/description/expansion) and can relocate component paths (skills, mcp, agents, workflows, hooks). Discovery now also resolves agents/ and workflows/ component directories with the same containment rules as skills/. Malformed contributes members warn + fall back so a spec-valid plugin never breaks on the extension. Signed-off-by: Thomas Kosiewski <tk@coder.com>
…tribution Plugin agents/ directories join agentDefinitionsService through the same precedence machinery as its scope roots (project > project-plugin > global > global-plugin > built-in), with plugin-root realpath containment per file. Skill and agent descriptors now carry an optional pluginName so consumers (inspector, UI) can attribute plugin-sourced artifacts, and discoverAgentDefinitions gains dedupeById:false for shadowing reports. Router agents.list/get honor the agent-plugins experiment gate. Signed-off-by: Thomas Kosiewski <tk@coder.com>
…// scheme Extends the existing workflow script resolution with a plugin://<name>/<file>.js scheme: host-local discovery through the shared agent-plugins containers, realpath containment inside the plugin's workflows dir, project containers gated on project trust, and the whole scheme gated on the agent-plugins experiment (includeAgentPlugins threaded from aiService, workflow_run tool, router workflows.list, and the workflow CLI's explicit --experiment opt-in). Plugin skills also join skill:// resolution and workflow enumeration. Adds readPersistedExperimentEnabled for standalone CLI experiment reads. Signed-off-by: Thomas Kosiewski <tk@coder.com>
…ions Backend: collectPluginSlashCommands merges manifest contributes.slashCommands across discovered plugins (first plugin in container precedence wins) and a new workspace.plugins.slashCommands.list oRPC endpoint serves them, gated on the agent-plugins experiment and anchored at the host checkout root. Frontend: suggestions merge them as data-driven entries whose replacement is the declared expansion text; built-in command keys and skill names win on collision. No new UI surfaces beyond the suggestion list. Signed-off-by: Thomas Kosiewski <tk@coder.com>
…s CLI) buildWorkspaceComposition computes the effective composition by layer through the production loaders (skills/agents dedupe:false for shadowing, workflow discovery, MCPConfigService.listServerLayers, shell + plugin hooks) and labels every entry with its source (built-in | global | project | plugin:<name>) plus what shadowed it. Exposed as one bulk workspace.plugins.composition.get oRPC endpoint and as 'bun run debug plugins <workspace-id>'. Plugin discovery and manifest validation run unconditionally; the agent-plugins experiment only gates whether plugin artifacts join the effective layers. Signed-off-by: Thomas Kosiewski <tk@coder.com>
Signed-off-by: Thomas Kosiewski <tk@coder.com>
…rkflow start Plugin-contributed agents were discoverable but unresolvable at stream time: resolveAgentForStream, agent body/frontmatter resolution, and subagent tool discovery all read agent definitions without plugin roots, and the oRPC workflows.start path rejected plugin:// scripts because resolveWorkflowScript never saw the agent-plugins experiment flag. - ResolveAgentOptions gains includeAgentPlugins; agentResolution passes it to every readAgentDefinition/resolveAgentFrontmatter call plus the base inheritance chain (plugin agents may derive from plugin bases). - aiService passes the already-computed experiment flag to resolveAgentForStream. - streamContextBuilder passes opts.agentPluginsEnabled to resolveAgentBody, resolveAgentFrontmatter, and discoverAvailableSubagentsForToolContext (which now forwards it to discoverAgentDefinitions and per-descriptor frontmatter). - router.ts getWorkflowService + workflows.start pass the experiment flag to resolveWorkflowScript so plugin:// workflows resolve when enabled. Signed-off-by: Thomas Kosiewski <tk@coder.com>
ThomasK33
commented
Aug 18, 2026
@codex review |
There was a problem hiding this comment.
💡 Codex Review
https://github.com/coder/mux/blob/2d3d8d977255e574ad5615206da9e810b6cfd129/src/node/services/agentSession.ts#L6383 Link file snapshots to their invoking user row
If the process crashes after this synthetic snapshot is appended but before the corresponding user row is appended, the snapshot remains in chat.jsonl with no way to identify it as orphaned; streamWithHistory only calls filterOrphanedMcpPromptSnapshots, so the next provider request can silently include stale file contents from a message the user never successfully sent. Persist an invoking message ID and filter orphaned file snapshots on load, as is already done for MCP prompt snapshots.
AGENTS.md reference: AGENTS.md:L109-L111
ℹ️ 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".
Uh oh!
There was an error while loading. Please reload this page.
Uh oh!
There was an error while loading. Please reload this page.
Uh oh!
There was an error while loading. Please reload this page.
… plugin-command loading Addresses Codex round 1: - P1: modelFallback.prepare now emits its own turn envelope with the rebuilt request identity so replay pairing compares the request that actually streamed. - P1: hookService uses a static QuickJSRuntimeFactory import (WASM still loads lazily at first runtime creation) instead of a dynamic import. - P2: composer plugin-command loading subscribes to the agent-plugins experiment value so toggling it takes effect without a remount.
ThomasK33
commented
Aug 18, 2026
@codex review Addressed all three round-1 findings in ca2b11f:
|
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit:ca2b11f55d
ℹ️ 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".
Uh oh!
There was an error while loading. Please reload this page.
Uh oh!
There was an error while loading. Please reload this page.
Uh oh!
There was an error while loading. Please reload this page.
Uh oh!
There was an error while loading. Please reload this page.
…der config, hook load retry Addresses Codex round 2: - P1: cache-audit collapses envelopes to the final row per requestHistorySequence (matching pairSessionTurns) so fallback turns audit once with the identity that streamed. - P1: turn envelopes fingerprint the step-0 active tool subset (forced xAI search set, else tool-search active tools) in both the primary and fallback paths, matching what StreamManager actually sends. - P1: replay-verify CLI passes the live ProviderService config view so aliases/custom metadata/cross-typed instances replay correctly. - P2: hookService stores only successfully-loaded candidates in the reconcile fingerprint, so transiently-failed hooks retry on the next send instead of staying disabled for the process lifetime.
ThomasK33
commented
Aug 18, 2026
@codex review Addressed all four round-2 findings in 3ee27c0:
|
There was a problem hiding this comment.
💡 Codex Review
https://github.com/coder/mux/blob/3ee27c0e976710bb755a6c46b2022684143702d4/src/node/services/aiService.ts#L3691 Requeue DevTools metadata for the fallback run
Fresh evidence after the fallback-envelope fix: each fallback model gets a new createDevToolsMiddleware instance and therefore creates a new DevTools run, while DevToolsService.createRun() consumes this metadata ID's pending entry when the primary run starts. Reusing the header here does not attach requestHistorySequence to the fallback run, so pairSessionTurns selects the keyed primary request but the final fallback envelope, making every logged refusal fallback verify against the wrong wire request. Queue the metadata again before starting the fallback run.
ℹ️ 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".
Uh oh!
There was an error while loading. Please reload this page.
Uh oh!
There was an error while loading. Please reload this page.
Uh oh!
There was an error while loading. Please reload this page.
…ce-stable agent sort Addresses Codex round 3: - P1: all live durable-event writers (turn envelopes, sandbox vars snapshots, hook context) obtain their journal from a process-shared registry keyed by resolved session dir, so one seq counter owns each durable-events.jsonl and interleaved producers cannot reuse or regress sequence numbers. - P2: the fallback turn envelope is deferred to a new PreparedModelFallback.onStreamConstructed callback invoked by StreamManager only after createStreamResult succeeds, so a fallback whose stream never starts cannot supersede the primary envelope. - P2: agent listing sorts same-ID duplicates as one group keyed by the winning definition's display name, preserving precedence order within groups for composition shadowing attribution.
ThomasK33
commented
Aug 18, 2026
@codex review Addressed all three round-3 findings in 81d507b:
|
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit:81d507b143
ℹ️ 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".
Uh oh!
There was an error while loading. Please reload this page.
Uh oh!
There was an error while loading. Please reload this page.
ThomasK33
commented
Aug 18, 2026
@codex review Addressed the round-8 finding in the latest push:
|
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit:e69e75495a
ℹ️ 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".
Uh oh!
There was an error while loading. Please reload this page.
ThomasK33
commented
Aug 19, 2026
@codex review Addressed the P2: the step-0 thinking rebuild in |
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit:6fa68015c3
ℹ️ 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".
Uh oh!
There was an error while loading. Please reload this page.
ThomasK33
commented
Aug 19, 2026
@codex review Addressed the P2: after the step-0 message rebuild, |
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit:413c5e0282
ℹ️ 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".
Uh oh!
There was an error while loading. Please reload this page.
Uh oh!
There was an error while loading. Please reload this page.
Uh oh!
There was an error while loading. Please reload this page.
…gin skills on experiment toggle; resolve real runtime in debug plugins CLI
ThomasK33
commented
Aug 19, 2026
@codex review Addressed all three round-11 findings:
|
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit:881c5c51d1
ℹ️ 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".
Uh oh!
There was an error while loading. Please reload this page.
ThomasK33
commented
Aug 19, 2026
@codex review Addressed the P2: Note: the |
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit:32c64c7e2b
ℹ️ 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".
Uh oh!
There was an error while loading. Please reload this page.
Uh oh!
There was an error while loading. Please reload this page.
Uh oh!
There was an error while loading. Please reload this page.
…ailed hook plugins without rebuilding healthy mounts
ThomasK33
commented
Aug 19, 2026
@codex review Addressed all three round-13 findings:
|
There was a problem hiding this comment.
💡 Codex Review
https://github.com/coder/mux/blob/d877e9a91be2f652b8d02d423db00413c4ef866b/src/node/services/aiService.ts#L3696-L3700 Requeue correlation metadata for fallback runs
Fresh evidence after the fallback-envelope fix: when the primary request has reached DevTools, createRun() has already consumed and deleted this pending metadata entry, so copying the same metadata ID into the fallback headers does not attach requestHistorySequence to the fallback model's new DevTools run. pairSessionTurns() consequently selects the keyed primary run while selecting the final fallback envelope, making every successful refusal fallback compare the fallback identity and transcript against the primary wire request. Queue a fresh metadata entry for each fallback run before constructing it.
ℹ️ 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".
Uh oh!
There was an error while loading. Please reload this page.
Uh oh!
There was an error while loading. Please reload this page.
…gin skill roots in inspector
ThomasK33
commented
Aug 19, 2026
@codex review Addressed both round-14 findings:
|
Codex Review: Didn't find any major issues. Swish! Reviewed commit: ℹ️ About Codex in GitHubYour team has set up Codex to review pull requests in this repo. Reviews are triggered when you
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". |
The round-5 construction-gating change moved primary turn-envelope emission into startStream's onStreamConstructed callback, but the aiService test stub replaced startStream without invoking it, so the turn-envelope tests asserted an empty journal. Mirror the real contract: a successful stub construction invokes the callback.
ThomasK33
commented
Aug 19, 2026
@codex review Fixed the |
Codex Review: Didn't find any major issues. Chef's kiss. Reviewed commit: ℹ️ About Codex in GitHubYour team has set up Codex to review pull requests in this repo. Reviews are triggered when you
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". |
Uh oh!
There was an error while loading. Please reload this page.
… kernel, context isolation, and continual-harness features (coder#3900) ## 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, `/refine` trajectory 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-verify` on live sessions). With it on, `code_execution` becomes 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 — coder#3865/coder#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-mode` experiment, nested under PTC; **exclusive-only** — enabling it forces the kernel-first narrowed toolset (supplement-mode RLM measured ~2x flat cost and was removed) - Persistent per-workspace mount: guest `vars` survives calls/turns/restarts via journaled snapshots - **Kernel context isolation**: nested `shux.*` 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, and `vars` - `shux.load({path, key})`: host-side bulk file ingestion straight into `vars` (record shows `{key, bytes, lines, preview}` only) - `shux.task_spawn` + `shux.events()`: fire-and-forget sub-agents with admission handles, asyncify-safe event drain - Batching guidance baked into the kernel-first preamble ("write complete programs") **Continual harness (r2, r6, r11):** - Every memory/skill mutation journals an invertible `refinement` durable event (blob-backed inverses) - Rollback engine with `rollbackOf` lineage: `shux run debug refinements` CLI + RLM-gated `refinement_rollback` tool - `/refine`: bounded trajectory-distillation pass (dream-agent machinery) applying smallest evidence-backed edits, journaled and reversible **Agent ops (r3, r7, r8, r9):** - Nuclear-family messaging: `task_message_parent` / `task_message_sibling` (RLM stamped on task records at spawn; strict same-parent scoping; server-side labels) - RLM-gated compaction keep-recent floor + cumulative read-file tracking - Branch summarization on fork/edit-resend (background generation, tail-guarded append) - `scripts/gate_fingerprint.sh` verification-loop memoizer **Measurement (`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 - Key measured results (sonnet-5 / opus-5 / gpt-5.6-sol; fable-5 at medium): - Context isolation: 504KB file load -> **867 bytes model-visible (0.17%)**; pre-fix the same task leaked 610KB into context and cost 10x flat tools - RLM-exclusive vs flat tools: **-30 to -63% cost in 7/8 model x scenario pairs**, faster in 6/8, all cells correct; organic `vars` adoption 15/16 - Batching preamble (cross-build A/B): sonnet organic batch factor 2.7 -> 3.5 (3/4 seeds fold all 6 loads into one eval, -42% tokens) - Every phase passed an independent gate run + adversarial review + live dev-server-sandbox dogfood with `replay-verify` PASS (evidence in the workflow run reports) - Post-rebase onto the Shux rename: full static-check green; kernel suites (code_execution 50, toolBridge/typeGenerator 43, toolAssembly 14, sandboxHost 25) green; kernel surfaces adopt shux-primary naming with the `mux.*` alias intact ## Risks - **RLM-off regression risk is the headline concern and is heavily defended**: composition tests pin byte-identity per flag combination, and replay-verify was run on live RLM-off control sessions at each phase. Highest-traffic shared code touched: `toolAssembly`, `code_execution`, compaction paths (RLM-gated), task spawn paths (flag stamping). - RLM-on surfaces are experimental by declaration; known rough edges: peak per-request context is higher when `shux.load` materializes large files (latent pressure on multi-MB corpora), and one sonnet seed still fragments batching. - `/refine` auto-applies edits (no approval UI in v1) — mitigated by journal + rollback + immutable-base guard rails. ## Pains - The mid-series `mux` -> `shux` rename on main required conflict resolution across the kernel commits (namespace, type-generator identifiers, description text). - Sub-agent dogfooding infrastructure failures (background-monitor wakes, uncommitted-work timeouts, transient gateway model errors) shaped several workflow-hardening commits. --- _Generated with `mux` • Model: `anthropic:claude-fable-5` • Thinking: `xhigh` • Cost: `$763.80`_ <!-- mux-attribution: model=anthropic:claude-fable-5 thinking=xhigh costs=763.80 --> --------- Signed-off-by: Thomas Kosiewski <tk@coder.com>
Summary
Implements Track 1 of the plugin-architecture work on top of the shared agent foundation (#3865): the provider request becomes a verifiable pure function of the append-only session log ("model-visible ⟺ logged"), with turn-envelope records, a byte-equality replay harness and cache-bust auditor, sandboxed Tier-1 plugin hooks (QuickJS), and graduation of the agent-plugins manifest into a unified
contributesdistribution unit with a per-workspace composition inspector.Background
Research into DeepSeek Harness ("everything is a plugin"; requests reconstructed purely from an append-only log) and OpenCode's plugin hook vocabulary identified two properties worth porting to Mux: log-purity of provider requests (optimal prompt-cache reuse, deterministic replay/fork/resume) and a safe plugin surface. Mux was already ~80% log-pure (history re-read from disk each turn, no in-memory transcript); this PR closes the remaining gaps and builds the plugin surface inside that guarantee. Builds directly on the event spine, journal kit, sandbox host, and capability grants from #3865.
Implementation
Five phases, each implemented → quality-gated → adversarially reviewed → dogfooded via a durable Mux workflow (committed as
workflows/track1-implementation.js):@filemention snapshots materialize intochat.jsonlat append time (before the request is built); the two request-time injectors inmessagePipeline.tsare deleted. Old persisted histories still build.turn-envelopedurable event per assistant turn: content-addressed system prompt (BlobStore, dedupes across turns), name-sorted toolset manifest with schema hashes, model/thinking/providerOptions hashes (hash-only; raw options never persisted). Emission is observability — it never fails the turn.bun run debug replay-verify <ws>reconstructs each turn's request from chat.jsonl + envelopes + blobs through the production pipeline and byte-compares againstdevtools.jsonl;bun run debug cache-audit <ws>diffs consecutive envelopes and attributes prompt-prefix busts with approximate token cost. Fixture-based CI tests keep the invariant enforced. Provider-defined tools (e.g. Anthropic web_search) fingerprint by wire identity (id+args) on both sides — their client-side inputSchema never crosses the wire.hooks.jsin agent-plugin containers loads into persistent QuickJS mounts and registers as event-spine middleware (tool.execute.beforemutate/deny,tool.execute.afterobserve,request.assemblecontribute context). Least-privilege capability grants (manifest-requested tool visibility, Project-Trust-gated project containers); hook-contributed context lands as durablehook-contextrows before the prompt mutation, so replay stays byte-identical. Crash/timeout/malformed hooks are logged and skipped — only explicit denials become model-visible tool errors. Experiment-gated (agent-plugins).plugin.jsongainscontributes(skills, agents, workflows, MCP, slash commands, hooks) consumed through the existing per-scope loaders (no parallel loading paths);plugin://workflow scheme; plugin slash commands surface in the composer suggestions;bun run debug plugins <ws>plus one bulk oRPC endpoint print the effective composition by layer (built-in | global | project | plugin:<name>) including shadowing attribution.Validation
Beyond CI: each phase was dogfooded against a live dev-server sandbox with real provider turns —
<system-file-update>row durably appended before request start; request content byte-identical to log rows.devtools.jsonl.replay-verify3/3 PASS andcache-auditcorrectly attributed a deliberate AGENTS.md edit (~13.6k tokens re-processed)..envreads (secret never reached the model), injected context landed as ahook-contextrow and in the wire request, deliberately crashing hooks were skipped with the turn completing, replay-verify stayed green.Risks
hookService.tsis the file to review hardest.Pains
build-main-watch) crashes on intermediate edit states and can leave a stale/corrupt dist bundle servingCannot find moduleerrors; two workflow dogfooders were lost to this before the process pinned fresh rebuilds.bun run debug send-messagelooks like a turn driver but is display-only; live turns must be driven via the oRPC WS API. Cost several dogfood attempts before being diagnosed.Generated with
mux• Model:anthropic:claude-fable-5• Thinking:xhigh• Cost:$336.75