pub fn project_messages(
msgs: &[ChatMessage],
policy: &ReductionPolicy,
prior: &ReductionLog,
) -> (Vec<ChatMessage>, ReductionLog)Expand description
Pure function of (msgs, policy, prior): the reduction engine’s actual
body (SPEC.md A5, A7, A8, A9, A10, TR-2). A session adapter can delegate
its canonical message slice here; a runtime agent loop calls this
directly against history[1..] so a live agent can build the projected
request view without needing a Session wrapper around its own history.
Deterministic and side-effect free: identical inputs produce byte-identical
output; msgs is never mutated; nothing here touches the filesystem —
including for A8: policy.read_freshness is precomputed data, populated by
probe_read_freshness (the one place disk I/O happens) before this is
ever called. Every reduction already recorded in prior reproduces
verbatim (same id, same placeholder, byte-identical stub) — new reductions
only ever target messages older than the protected tail, so the reduced
prefix stays cache-stable across turns.
Pass order: TR-2 (ReductionKind::DuplicateOutput, content-hash dedup)
runs FIRST, then TR-6 (ReductionKind::Superseded, same-tool/
canonicalized-args keep-latest), then T30/TR-4
(ReductionKind::OutputNormalized, ANSI/redraw collapse), then
ReductionKind::ToolOutputTruncated (A7), then the read-family passes —
ReductionKind::FileReadElided (A8) for an unchanged re-read,
ReductionKind::FileReadDiffed (TR-3) for a changed one — then
ReductionKind::ImageRedacted (A9), then TR-10
(ReductionKind::ToolInputElided, both the successful-call case and
TR-6’s failed-call errored-input-pruning complement), then
ReductionKind::TurnsCleared (A10) last (the one cardinality-changing
pass). Each of TR-2, TR-6, T30/TR-4, and A7 claims a message’s index in
reduced_this_run the moment it mints a reduction for it, and every pass
after the first checks that set — so a single message is claimed by
exactly one pass per project_messages call, never two.
TR-2 running before TR-6, T30/TR-4, and A7 means a byte-identical
duplicate is deduped — the cheapest of the four reductions — rather than
independently superseded, normalized, or truncated. TR-6 running before
T30/TR-4 and A7 follows the identical reasoning one step further: a
result about to be superseded down to one small stub never needs
normalizing or truncating first either. See the TR-2 and TR-6 passes
below for why this ordering must hold within a single call, not just
“eventually” (in short: both verdicts are pure functions of msgs, so
they are unaffected by running before or after T30/TR-4, but T30/TR-4 and
A7 both read from view, which TR-2/TR-6 may have already stubbed — so
TR-2 then TR-6 must go first, or their own claims could lose a race to a
pass that mutates view ahead of them).
T30/TR-4 running before A7 means a noisy bash/exec output is collapsed to
its final rendered content BEFORE A7 ever measures it against
tool_output_trigger_bytes — but T30/TR-4 only CLAIMS the message (taking
it out of A7’s candidate pool) when its own normalized rendering already
fits under tool_output_trigger_bytes; that rendering is what rides the
wire, already bounded, so no truncation is needed on top of it. When the
normalized rendering is STILL over the trigger — genuinely large, mostly
distinct content, not just redraw noise — T30/TR-4 deliberately does not
claim the message at all (raw, untouched) and lets it fall through to A7
below, which truncates the RAW bytes to tool_output_keep_bytes. Either
way the wire payload for a terminal output is bounded by A7’s trigger —
the P7 runaway-output safety net (SPEC.md/TR-12) is preserved for BOTH
small-after-normalization and large-after-normalization outputs. TR-2 and
TR-6 both never claim a read-type tool result (detect_reads), even a
byte-identical or same-args re-read: the read-family passes own that
address space exclusively, with strictly more information (path-aware
freshness, a unified diff) than either TR-2’s flat “identical to msg #N”
or TR-6’s flat “superseded by msg #N” stub could express; T30/TR-4 is
likewise scoped to normalize::NORMALIZE_TOOLS tool identities,
disjoint from READ_TOOLS, so it never contends with the read-family
passes over the same index either.