Skip to content

fix(harness): serve the system prompt reliably — SYSTEM_MD env-forward + materialization + observability#76

Open
sunholo-voight-kampff wants to merge 6 commits into
arniwesth:mainfrom
sunholo-voight-kampff:fix/system-prompt-env-forward
Open

fix(harness): serve the system prompt reliably — SYSTEM_MD env-forward + materialization + observability#76
sunholo-voight-kampff wants to merge 6 commits into
arniwesth:mainfrom
sunholo-voight-kampff:fix/system-prompt-env-forward

Conversation

@sunholo-voight-kampff

Copy link
Copy Markdown
Contributor

Problem

The AILANG runtime subprocess is spawned with an explicit env allowlist (runtime-process.ts childEnv). Any var not listed is silently scrubbed — and SYSTEM_MD (the path to the system-prompt file, e.g. the AILANG language reference served to agent evals) was not listed. So getEnvOr("SYSTEM_MD") was always "" inside the .ail, the system prompt resolved to 0 chars, and the model ran with no system prompt at all — no harness instructions, no language reference. It stayed invisible because the served system prompt was never logged.

This is the same scrub-bug class the file already documents for MOTOKO_REPO, MOTOKO_PERSIST_RETRIES, AILANG_OLLAMA_MAX_TOKENS, AILANG_STDLIB_PATHSYSTEM_MD was the 5th instance (AILANG_OLLAMA_MAX_TOKENS was even hand-added twice).

Fix (the 4 commits in this PR)

  • f9f722e rpc.ail: env-authoritative system prompt + a system_prompt_built observability event (source / chars / has_ailang_reference / preview) + a loud SYSTEM PROMPT EMPTY error. The observability turned a multi-day mystery into a one-line log.
  • ec90f22 runtime-process.ts: forward SYSTEM_MD through the allowlist.
  • c5b0924 runtime-process.ts: systemic — derive the subprocess env allowlist from config.ts CORE_MAP + EXTENSION_MAPS + the MOTOKO_*/AILANG_* namespaces, so a new env-gated feature reaches the .ail by default instead of failing silently. Adds the drift-guard test runtime-process-env.test.ts; removes a duplicate-key error.
  • b58b0d3 index.ts: materialize an out-of-workspace SYSTEM_MD into the workspace (reusing materializeSystemPromptArg), since the FS sandbox blocks reads outside workdir.

Proof: with the fixes, system_prompt_built reports chars=89825, has_ailang_reference=true (was chars=0); a docx agent eval's cat-the-full-file fallbacks dropped 11→8.

⚠️ Stacked on #75

This branch is cut off fix/reliable-compaction (#75), so the diff vs main currently also shows #75's compaction commits. Please review the last 4 commits (f9f722e..b58b0d3) — those are this PR. Once #75 merges, this diff reduces to just the system-prompt fixes.

sunholo-voight-kampff and others added 6 commits June 25, 2026 07:46
…ls counting + output headroom + local summarizer

Squashed reliable-compaction work. Resolves the chars/4 over-count thrash and
the dead cloud-summarizer, on top of arniwesth#70:
- token estimate counts tool_calls args and uses calibrated chars/7 (measured
  ~7.5 chars/tok for AILANG code + DOCX XML; old chars/4 over-counted ~1.9x ->
  premature compaction -> working-set thrash)
- reserve 75k output headroom so input can't crowd the context window
- compact_step_actual drives the elision LEVEL off the provider's ACTUAL last
  input_tokens (ground truth), not a char estimate; LoopTotals.last_input_tokens
- wire compaction_ai's summarizer to a local ollama model (registry default
  points at a cloud model that's dead on a local-only rig)

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
The system message (AILANG reference) was seeded into the conversation list and
flowed through compaction, which could drop/reorder it — so the model lost its
syntax reference after compaction and drifted into invalid syntax (writing
`module a.b.c` / `use std.xml`, i.e. another language). partition_system_msgs()
splits ALL system messages off before BOTH compaction layers and re-prepends
them for every provider call, so the reference is structurally immune to
compaction. Verified: docx reimplement 0/17 -> 16/17, markdown 3/3.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
… failure

The AILANG language reference was silently never reaching the model. The ollama
profile config.json sets system_prompt="", which overrode the SYSTEM_MD env that
run.sh sets, so cfg.agent.system_prompt resolved to "" -> raw_system="" -> the
model got a 0-char system prompt: neither the harness instructions NOR the AILANG
reference. It wrote AILANG from training + reading project files, and docx ground
to 0/17 at max_steps. This stayed invisible for days because the served system
prompt was never logged.

Three changes, all in rpc.ail run_with_config:
- env-authoritative: SYSTEM_MD env now wins over the profile config (one explicit
  source of truth, env over config) so the reference actually reaches the model.
- observability: emit `system_prompt_built` every run (source, chars,
  has_ailang_reference, preview). "Is the reference served?" is now one log line,
  not a multi-day investigation.
- loud failure (CLAUDE.md rule #2: no silent fallbacks): emit an `error` event when
  a system-prompt source was set but produced empty content.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
The rpc.ail env-authoritative read (f9f722e) couldn't work because the .ail
runs as a subprocess spawned with an EXPLICIT env allowlist (runtime-process.ts
childEnv) that scrubs every unlisted var. SYSTEM_MD wasn't listed, so
getEnvOr("SYSTEM_MD") was always "" inside the .ail — the model ran with a
0-char system prompt (no harness instructions, no AILANG reference). docx 0/17.

Same gotcha the file already documents for MOTOKO_PERSIST_RETRIES / MOTOKO_REPO.
Add SYSTEM_MD to the allowlist. Now the system_prompt_built event reports the
reference as served.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
… the scrub-bug class)

The .ail runtime subprocess is spawned with a hand-maintained env allowlist (childEnv).
Any var not listed is silently scrubbed — which has broken at least FIVE env-gated
features whose var someone forgot to add: SYSTEM_MD (4-day system-prompt bug),
MOTOKO_REPO, MOTOKO_PERSIST_RETRIES, AILANG_OLLAMA_MAX_TOKENS, AILANG_STDLIB_PATH.
AILANG_OLLAMA_MAX_TOKENS was even hand-added twice (a TS1117 duplicate-key error),
proving the list is unmaintainable. Root cause: the allowlist is a second source of
truth that drifts from config.ts's CORE_MAP — which already maps SYSTEM_MD.

Systemic fix (audit-before-patch, not a 6th one-off):
- autoForwardedEnvKeys(): derive the forward set from CORE_MAP + EXTENSION_MAPS +
  the MOTOKO_*/AILANG_* namespaces + SYSTEM_MD. New env-gated features now reach the
  .ail BY DEFAULT instead of failing silently. Explicit childEnv entries still win
  (computed values/defaults); arbitrary host vars are NOT forwarded (no secret leak).
- Remove the duplicate AILANG_OLLAMA_MAX_TOKENS block (the TS1117 error).
- runtime-process-env.test.ts: drift guard asserting forwarding covers every CORE_MAP
  /EXTENSION_MAPS var — fails loudly if the allowlist ever stops covering a config key.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…space

The AILANG runtime is FS-sandboxed to workdir (AILANG_FS_SANDBOX), so a SYSTEM_MD
that points outside it (e.g. an eval harness writing the prompt to /tmp) is silently
unreadable — rpc.ail's readFile returns empty and the model runs with no system
prompt. The harness already materializes external --system-prompt files into the
workspace; apply the same treatment to SYSTEM_MD. Now ANY caller's external prompt
path just works, instead of each of the 5 eval scripts (and every future one) having
to remember to place the file inside the workspace. The loud system_prompt_built
event still surfaces any remaining gap.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants