---
summary: "The System: prefix / stripLeadingSystemContext contract for hiding injected context from the owner"
read_when:
  - Adding a new place that injects context ahead of a user turn
  - Debugging internal context (exec output, activity, a steering directive) leaking into visible chat or speech
title: "System-context injection"
---

# System-context injection

The runtime frequently prepends **internal context** to the user's turn before
the koi sees it — recent exec results, message-activity notices, and a captured
device-activity snapshot. The koi model must receive all of it, but the **owner
must never see it** in chat or hear it spoken. (The voice steering directive is
NOT part of this: it travels on the system-prompt channel, never inside the user
turn — see the producer section.)

This is a load-bearing convention: the producer marks injected context with a
recognized prefix, and the display layer strips exactly that marker back off.
If a new injection site skips the marker, its context leaks straight into the
owner's terminal or voice output. Follow the rules below whenever you add one.

## The two recognized forms

A block is treated as injected context (and hidden from the owner) when it
appears at the **very start** of the turn in one of these forms:

1. **`System: `-prefixed lines.** Every line of the block begins with
   `System: `, and the block is separated from the user's own text by a blank
   line. Multi-line events (e.g. exec output) get the prefix on **every** line,
   not just the first. Multiple blocks may stack, each ending in a blank-line
   separator.

2. **A `# …` heading block terminated by `\n---\n`.** Used by the captured
   device-activity snapshot (`# Device Activity (exact, real-time)` /
   `# Device Activity (relevant, distilled by fast triage)`). Everything from the
   leading `# ` heading through the first `\n---\n` horizontal rule is the block.

## Producer side

- `prependSystemEvents` (`src/auto-reply/reply/session-updates.ts`) builds the
  system-events block. It prefixes **every line** with `System: ` and prepends
  the block plus a blank-line separator to the turn body. This uniform prefix is
  what lets the display layer strip the whole block reliably.
- The voice steering directive (`VOICE_TURN_DIRECTIVE` in
  `src/gateway/voice-steering.ts`) is deliberately **not** injected into the user
  turn: its only injection site joins it into the koi prompt's
  `extraSystemPrompt` (the system-prompt channel) in `src/gateway/openai-http.ts`.
  Inlining it — even `System:`-prefixed — was removed because the inlined
  directive persisted into session history and re-rendered/echoed (the exact leak
  this convention exists to prevent). `stripLeadingSystemContext` still strips a
  _stacked_ run of `System:` blocks as a backstop for history recorded before
  that change.
- The device-activity snapshot is produced by `src/capture/context-block.ts`
  (real-time) and `src/capture/context-digest.ts` (distilled), using the
  `# …` + `\n---\n` form.

## Display side

- `stripLeadingSystemContext` (`src/tui/tui-formatters.ts`) removes the injected
  context from what the owner sees. It skips the **entire** leading run of
  `System: ` lines and the blank separators between/after them, stopping at the
  first real (non-blank, non-`System:`) line; it also strips a leading `# …`
  block up to its `\n---\n`. It intentionally handles the _stacked_ case — an
  earlier version dropped only the first block and leaked a second one.
- The voice reply path has its own guards (`stripLeakedDirectives` /
  `makeLeakStripper` in `src/gateway/voice-steering.ts`) for the mirror problem:
  a model that **echoes** a directive back at the start of its own reply. Those
  strip a leaked directive out of the koi's output before it is spoken or shown.

## The rule for contributors

When you add a new place that injects context ahead of a user turn:

- Mark it with one of the two recognized forms above — **never** emit bare
  context that the owner would see. Prefer `System: ` on every line for
  event/notice-style context.
- Keep a blank line between the injected block and the user's real text.
- If you introduce a genuinely new marker shape, update
  `stripLeadingSystemContext` (and add a `tui-formatters` test) in the same
  change, so the display layer strips exactly what you inject.
