Skip to content

fix: request raw-run semantics for the reflection distiller sub-run#928

Open
gorkem2020 wants to merge 2 commits into
CortexReach:masterfrom
gorkem2020:fix/raw-run-distiller-hooks
Open

fix: request raw-run semantics for the reflection distiller sub-run#928
gorkem2020 wants to merge 2 commits into
CortexReach:masterfrom
gorkem2020:fix/raw-run-distiller-hooks

Conversation

@gorkem2020

Copy link
Copy Markdown
Contributor

Summary

  • The reflection distiller's embedded sub-run (runEmbeddedPiAgent, sessionKey shaped temp:memory-reflection:<agentId>) now passes modelRun: true, so the host's shouldSkipPromptBuildHooks({ isRawModelRun }) guard skips before_prompt_build dispatch for every registered plugin during that call, not just this plugin's own hooks.
  • Live trace evidence (self-hosted Langfuse, openai/gpt-oss-120b generation): a foreign plugin's ~16.6KB injected system-context block (recall-tool policy text describing tools the sub-run has no access to) was being prepended to the distiller's model call before this fix.
  • Smart extraction was investigated as part of the same defect report and found not affected: see "Scope note on smart extraction" below.

Why

#916 and #922 taught this plugin's own before_prompt_build hooks (auto-recall, the self-improvement reset reminder, both reflection injectors) to skip the distiller's sub-session via isInternalReflectionSessionKey(). That guard only covers hooks registered by this plugin. The host's dispatch guard for before_prompt_build lives one layer up, in shouldSkipPromptBuildHooks, and only short-circuits when the run itself is marked raw:

function shouldSkipPromptBuildHooks(params) {
  return params.isRawModelRun || isPromptBuildHookDispatchInProgress();
}

isRawModelRun is derived as params.modelRun === true || params.promptMode === "none". The distiller's runEmbeddedPiAgent call never set either, so the host kept firing every other plugin's before_prompt_build hooks into the sub-run. On the live fleet this meant another installed plugin's recall-policy injector (a before_prompt_build hook unrelated to memory reflection) prepended its own instructions and tool guidance to the distillation prompt, a call that runs with disableTools: true and has no way to act on that guidance. Pure token waste plus a risk of steering the distillate with unrelated directives.

Passing modelRun: true requests the host's raw-run semantics directly, which is the mechanism shouldSkipPromptBuildHooks actually checks. The call's explicit promptMode: "minimal" is preserved and still takes precedence over the raw-run default of promptMode: "none", so the reflection prompt shape (no skills catalog, minimal surface) is unchanged; only the foreign-hook dispatch is skipped.

Interplay with #922: that PR skips this plugin's own auto-recall hook for the distiller sub-session, from inside the sub-session. This PR requests raw-run semantics at the call site instead, so every plugin's before_prompt_build hook is skipped before the sub-run even starts. The two are complementary and touch different lines (this PR only adds three lines directly after the existing disableTools/disableMessageTool fields), so a later rebase of either branch should be straightforward.

Scope note on smart extraction

The original defect report described both the reflection distiller and smart extraction's embedded sub-run as exposed to this issue. Investigation (source read plus live Langfuse trace comparison) shows smart extraction's LLM calls do not go through the host's embedded-agent-runner at all: SmartExtractor is constructed with an LlmClient built by createLlmClient() (src/llm-client.ts), which talks directly to the configured llm.baseURL (an external OpenAI-compatible endpoint or the provider's own OAuth backend) via the openai SDK. That path never calls runEmbeddedPiAgent, so the host's before_prompt_build dispatch (and shouldSkipPromptBuildHooks/isRawModelRun) never runs for it, there is no sub-run to mark raw.

A same-model (openai/gpt-oss-120b) trace comparison from the live fleet confirms this: smart extraction's actual system prompt is exactly "You are a memory extraction assistant. Always respond with valid JSON only.", with no injected block, while the reflection distiller's trace at the same time carries the ~16.6KB foreign injection this PR fixes. The two code paths were easy to conflate at a glance since they share the same downstream model; the trace data disambiguates them. No code change was made to SmartExtractor or src/llm-client.ts.

Tests

test/raw-run-distiller-hooks.test.mjs, mocking the Layer 1 embedded runner the same way as test/reflection-per-agent-lane.test.mjs:

  • Asserts generateReflectionText passes modelRun: true to runEmbeddedPiAgent.
  • Asserts the explicit promptMode: "minimal" (and disableTools/disableMessageTool) survive alongside modelRun.
  • Regression guard: asserts this plugin has exactly one runEmbeddedPiAgent call site, so no other call was silently marked raw.
  • Asserts modelRun: true is an unconditional literal, not gated behind a host-version capability check; older hosts that ignore the field simply fail open to the pre-fix behavior.

Stash-verified red before the fix (both modelRun assertions failed against the pre-fix index.ts), green after.

Test plan

  • npm test (full local suite, excluding the pre-existing test/cjk-recursion-regression.test.mjs EADDRINUSE :11434 conflict against a real local Ollama daemon, unrelated to this change)
  • node scripts/verify-ci-test-manifest.mjs
  • npm run verify-package-runtime (rebuilds dist/)
  • dist/index.js committed alongside the index.ts source change

Copy link
Copy Markdown

This PR currently conflicts with the latest master (mergeable=CONFLICTING, merge_state_status=DIRTY). R0 verification and R1 value assessment completed, but the conflict gate deferred deep review because the branch cannot be merged as-is.

Please rebase onto or merge the latest master, resolve the conflicts, and push an updated head. I will then verify the modelRun: true behavior against the host's real hook dispatcher and supported host versions, rather than only the stubbed runner parameters.

@gorkem2020 gorkem2020 force-pushed the fix/raw-run-distiller-hooks branch from a52df9e to bbac3ca Compare July 12, 2026 19:17

@app3apps app3apps left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Re-reviewed the conflict-resolved head bbac3ca. The change is narrow, source and dist stay aligned, and the dedicated regression plus the full test suite pass. The call site now consistently requests raw-run semantics for the reflection distiller.

Non-blocking follow-up: the current tests prove that modelRun: true is forwarded, but they do not execute the real host hook dispatcher. A host-level test should verify that foreign before_prompt_build hooks are skipped across the supported host range and document how raw-run semantics interact with promptMode: "minimal".

Copy link
Copy Markdown

After #919 was merged, this PR now has merge conflicts with the latest main. Please rebase the branch onto the current main, resolve all conflicts, and push the updated head. We will review the updated revision once it is clean and mergeable.

…b-run

The reflection distiller's embedded sub-session (runEmbeddedPiAgent,
sessionKey shaped temp:memory-reflection:<agentId>) only sat inside our
own hook-skip guards (CortexReach#916, CortexReach#922). The host's before_prompt_build
dispatch still fired for every OTHER registered plugin's hooks, so a
foreign plugin's injected system-context block (observed live: 16.6KB
from another plugin's recall-policy injector) got prepended to the
distiller's model call, wasting tokens and risking distillate steering
from instructions the sub-run has no tools to act on.

Pass modelRun: true in the runEmbeddedPiAgent call so the host's
shouldSkipPromptBuildHooks({ isRawModelRun }) resolves true for this
run (isRawModelRun = params.modelRun === true || params.promptMode ===
"none"), skipping before_prompt_build dispatch for all plugins, not
just ours. The explicit promptMode: "minimal" already set on this call
takes precedence over the raw-run default of promptMode "none", so the
reflection prompt shape is unaffected.
Verified red (modelRun/promptMode-literal assertions failed) against
the pre-fix index.ts, green against the fix in the prior commit.

Wire the new test into the local npm test chain and the
core-regression CI group.
@gorkem2020 gorkem2020 force-pushed the fix/raw-run-distiller-hooks branch from bbac3ca to 4a8cd2b Compare July 13, 2026 17:08
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants