Skip to content

fix(environment): persist probe snapshots so the prompt prefix stays byte-stable#6093

Merged
SivanCola merged 1 commit into
esengine:main-v2from
SivanCola:fix/env-probe-snapshot-byte-stability
Jul 6, 2026
Merged

fix(environment): persist probe snapshots so the prompt prefix stays byte-stable#6093
SivanCola merged 1 commit into
esengine:main-v2from
SivanCola:fix/env-probe-snapshot-byte-stability

Conversation

@SivanCola

Copy link
Copy Markdown
Collaborator

Summary

治本工程第一步:消除系统提示词前缀里最大的不确定性来源。

The environment section sits inside the provider-cached system-prompt prefix, but probe results were point-in-time observations with only a 5-minute in-memory cache. Between process restarts and rebuilds they drift for reasons invisible to the user: a 2s timeout flips a slow tool between found and timeout; a GUI-launched desktop resolves a different PATH than a login shell (so the CLI and desktop compose different prompts on the same machine); a cold docker daemon reports an exit error. Every flip rewrites the prefix, and the desktop's fresh-system-prompt swap (sessionWithFreshSystemPrompt) then propagates the drifted prompt onto every resumed session — a whole-conversation cache miss at 10x miss pricing plus a persisted rewrite. This is the main mechanism identified behind "desktop costs more than CLI" (#2945, and the Desktop-vs-CLI comparison in #5850's comments).

Changes:

  • Persisted probe snapshots (ProbeOptions.SnapshotDir, opt-in; boot passes config.CacheDir()): one snapshot per probe fingerprint under <cache>/environment/. A snapshot younger than 24h serves without re-probing, so rebuilds, relaunches, and both surfaces on one machine render identical environment bytes. 24h aligns with the controller's cacheColdAfter: past a day idle the provider cache is dead anyway, so a refresh at that point costs nothing extra.
  • Flap merge on refresh: transient failures (timeout, exit …) keep the previous successful observation for the same probe; definitive results (found, not found, not trusted) always win, so genuinely uninstalled tools still drop out of the section.
  • Desktop swap observability: resume/rebind now logs when it swaps a system prompt whose bytes differ from the persisted one. With deterministic composition this should fire only on genuine config changes — a field log without one points at a new nondeterminism source.

Not in this PR (follow-ups): shell resolution (sandbox.ResolveShell) is also PATH-dependent but config-driven and rarely flaps; a swap-policy change for resumed sessions (keep the recorded prompt vs adopt the fresh one) is a product decision left untouched.

Verification

  • New tests: TestProbeSnapshotServesAcrossRestartsWithoutReprobing (snapshot serves byte-identical FormatSection output across a simulated restart even after the tool binary disappears), TestProbeSnapshotExpiresAndAdoptsDefinitiveChanges (post-TTL refresh adopts a real version change and re-persists), TestProbeSnapshotKeepsFoundOverTransientFailure (exit-failure keeps last-good; definitive not found adopted), TestMergeProbeSnapshotOnlyOverlaysTransientFailures.
  • Full suites green: internal/environment (+-race), internal/boot (25s), internal/cli, desktop (53s). go vet clean.
  • REASONIX_RELEASE_CACHE_GUARD=1 go test ./internal/agent -run TestReleaseCacheHitGuard green.

Cache impact

Cache-impact: high - this PR is itself a cache-stability fix: it removes the probe-drift class of prompt-prefix invalidation. No prompt content changes when tools are stable; the section can now be up to 24h stale relative to live tool state.
Cache-guard: new snapshot byte-stability tests above (restart-identical FormatSection assertion) plus TestReleaseCacheHitGuard run locally.
System-prompt-review: requested from @SivanCola — model-visible policy change to sign off: (1) the environment section may lag live tool state by up to 24h (new installs appear on the next refresh); (2) a transiently failing tool keeps its last-good version line until a definitive change. Both trade freshness for prefix stability.

Refs #2945, #5850, #5614.

…byte-stable

The environment section sits inside the provider-cached system-prompt
prefix, but probe results were point-in-time observations with only a
5-minute in-memory cache: a 2s timeout flips a slow tool between
"found" and "timeout", a GUI-launched desktop resolves a different PATH
than a login shell, a cold docker daemon reports an exit error. Every
flip rewrote the prefix; the desktop's fresh-system-prompt swap then
propagated the drifted prompt onto every resumed session — a
whole-conversation cache miss at 10x pricing plus a persisted rewrite.
This was the main mechanism behind "desktop costs more than CLI"
(esengine#2945, esengine#5850 comments).

- Persist one probe snapshot per fingerprint under the shared cache
  root (ProbeOptions.SnapshotDir, opt-in; boot passes config.CacheDir).
  A snapshot younger than 24h serves without re-probing, so rebuilds,
  app relaunches, and the CLI and desktop on the same machine render
  identical environment bytes. 24h aligns with cacheColdAfter: past a
  day idle the provider cache is dead anyway, so a refresh then is free.
- Flap merge on refresh: transient failures (timeout, nonzero exit)
  keep the previous successful observation; definitive results (found,
  not found, not trusted) always win, so uninstalled tools still drop
  out.
- Desktop resume logs when it swaps a system prompt whose bytes differ
  from the persisted one — with deterministic composition this should
  only fire on genuine config changes, so a field log without one
  points at a new nondeterminism source.

The snapshot dir is host state and stays out of the probe fingerprint.
@SivanCola SivanCola requested a review from esengine as a code owner July 6, 2026 10:38
@github-actions github-actions Bot added v2 Go rewrite (1.x) — main-v2 branch, active development desktop Wails desktop app (desktop/**) config Configuration & setup (internal/config) labels Jul 6, 2026
@SivanCola SivanCola merged commit a84f2ae into esengine:main-v2 Jul 6, 2026
15 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

config Configuration & setup (internal/config) desktop Wails desktop app (desktop/**) v2 Go rewrite (1.x) — main-v2 branch, active development

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant