Prompt + Context Architecture — Review

An honest read on the current prompt stack + build-context.mjs, tuned for gpt-5.4 on the Responses API. You asked me to push on your calls — so I did.

7 min
The plumbing is right; the contents aren't. Real assistant/user history, markdown developer messages, one clean builder, action-framed tools — all correct and modern. The problems are two: (1) you've handed a reasoning model a stack of rules and prohibitions where it needs examples, and (2) the message array fragments the instructions into five developer turns while burying the one fact that decides every turn — the open-WO list — five messages away from the message it has to interpret.

Where I'd push back

high1 · Rules where you need examples

work_orders.md is 186 words of state-machine spec (states, transitions) with zero worked examples. messages.md is mostly prohibitions — "avoid / don't / never." A reasoning model reads abstract rules and interprets them, differently each time — which is the exact source of the "sometimes asks, sometimes approves all" inconsistency. You can't rule your way to judgment or voice.

FIX  Replace the prose rules with ~6–12 real worked exchanges spanning the range (approve / override / "i got it" / batch yes / genuine ambiguity → ask / casual non-WO → just chat). Examples pin behavior; rules suggest it.

high2 · You emit a cryptic signal you never teach

Factual context ships headers like Open Work Orders (oldest first; recent bundle sent close together) and a ← most recent tag. That burst/recency marker is a real signal — but nothing in the prompt tells the model what to do with it. So it guesses. That's the inconsistency, surfaced: a hint with no key.

FIX  Either teach it ("if WOs were sent close together, a bare 'yes' is ambiguous → ask which") or drop the markers and let the assistant/user history carry recency. Don't ship an unkeyed signal.

med3 · The decisive fact is buried far from the decision

The open-WO list sits at array position 4, ahead of the entire conversation. Then history, then the latest "yes" last. To interpret "yes," the model needs that list — but it's 5+ messages back. Reasoning models weight recent context heavily; you've put the most decision-relevant data the farthest from the decision.

FIX  Restate the open WOs (compact) adjacent to the latest user message — e.g. a short "context for this reply" right before the final user turn. Test it; this is the kind of move that visibly changes the ambiguity behavior.

med4 · Five developer messages = a fragmented system prompt

soul.md, messages.md, work_orders.md are three separate developer turns. They're all "who you are + how to act" — the model reads them as one block anyway, so the split buys nothing and risks them reading as detachable. The clean boundary isn't soul-vs-messages-vs-workorders; it's static instructions (cacheable, identical every turn) vs per-turn facts (change every turn).

FIX  Collapse the three static files into one developer "system" message (keep the files on disk, concatenate at load). Keep factual context as the second developer message. Two developer turns, not five. Cleaner for the model and for prompt caching once the static block grows with examples.

low5 · The "current conversation" delimiter is vestigial

{role:'developer', content:'# This is your current conversation with the user'} is a standalone developer turn wedged between the facts and the history. The assistant/user roles already say "this is the conversation." Inserting a developer turn mid-flow is unusual and can muddy turn-taking. "The 8/8 smoke test included it" proves it didn't break — not that it helps.

FIX  Drop it, or fold the one line into the end of the factual-context message. Don't spend a role turn on a header.

low6 · Behavior leaks into tool descriptions

We already caught draft_tenant's description steering the workflow ("use this together with…"). Tool descriptions are part of the live prompt; when they imply behavior, they compete with work_orders.md and the model gets mixed signals.

FIX  One source of truth: behavior in the prompt, tool descriptions limited to mechanics (what it does, params). Audit the rest of the tools for stray policy.

low7 · soul.md contradicts messages.md

soul.md:43 "Do not say an action happened unless a tool confirmed it" reads as permission to confirm; messages.md says "avoid narrating your actions." A reasoning model surfaces contradictions as inconsistent behavior — this is part of the "Sent." problem.

FIX  Make 43 a pure truth-guard ("never claim an action you didn't take") and let messages.md own the narration rule. No overlap.

The array — now vs how I'd shape it

Now (5 developer turns)

developer: soul.md
developer: messages.md
developer: work_orders.md
developer: factual context
developer: "# This is your current
           conversation…"
assistant: …
user: …            (history)
user: latest       ← facts are 5 back

Proposed (2 developer turns)

developer: SYSTEM  soul + voice + WO
          behavior + FEW-SHOT
          examples — static, cached
developer: FACTS   runtime + open WOs
assistant: …
user: …            (history)
user: latest + compact open-WO
      restate, inline  ← facts adjacent

The two structural moves: fold static instructions into one cached block, and pull the open-WO facts next to the message they decide. The behavioral move (the big one): the SYSTEM block carries examples, not just rules.

gpt-5.4 specifics

What I'd do, in order

Reviewed against current: core/context/build-context.mjs, core/prompts/{soul,messages,work_orders}.md, the Responses-API orchestrator loop.