context: [background] memory injection block (Phase 4 commit #2)

Phase 4 commit #2 per docs/PHASE4.md §5/§12.

ctx.memory_items (array of {kind, content, ...}) loaded by repl.lua
at startup from history.load_memory(). When non-empty AND ctx not in
Norris mode, to_messages() appends a [background] block to the
system prompt:

  [background] (memory.jsonl; manage via :memory)
  - (fact) User prefers terse responses
  - (context) Project: aish (LuaJIT REPL)

Suppression under Norris (R-C1): when ctx.norris_active is true the
[background] block is omitted. Norris already anchors via its
NORRIS suffix carrying the goal; a 2KB background block per planning
iteration would add ~16K tokens of redundant input over an 8-step
run.

Suffix composition order is now:
  1. DEFAULT_SYSTEM_PROMPT (Phase 0 + Phase 2 MCP, statically embedded)
  2. [background] block — when memory_items non-empty AND NOT norris_active
  3. NORRIS MODE block — when norris_active

repl.lua wiring (memory_items population at startup, :memory meta cmds,
:remember shortcut, :memory inject for live refresh) lands in commit #3.

Verified composition order with 4 cases:
  default-only         → 697 chars, no background, no norris
  memory_items only    → 824 chars, background YES, no norris
  memory + norris      → 1451 chars, background NO, norris YES (suppressed)
  norris only          → 1451 chars, background NO, norris YES

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-05-13 04:52:42 +00:00
parent 199dd87eaa
commit c1a5c736ec
+22
View File
@@ -136,6 +136,22 @@ end
-- model (user → assistant → user → assistant), no strict-template breakage.
--
-- The system prompt is NOT stored in self.turns per §6.
-- Phase 4: [background] block composer. Memory items from memory.jsonl
-- are stored on self.memory_items (loaded by repl.lua at startup) and
-- rendered as a dim-styled suffix on the system prompt. Suppressed when
-- norris_active to avoid stacking large background contexts in
-- per-iteration broker calls (R-C1 review fold-in). Cap honored via
-- inject_max_chars argument from the caller (already truncated by repl).
local function compose_background(items)
if not items or #items == 0 then return "" end
local lines = { "", "", "[background] (memory.jsonl; manage via :memory)" }
for _, it in ipairs(items) do
lines[#lines + 1] =
("- (%s) %s"):format(it.kind or "?", (it.content or ""):gsub("\n", " "))
end
return table.concat(lines, "\n")
end
-- Phase 3: NORRIS MODE suffix appended to the system prompt when
-- self.norris_active. Carries self.norris_goal so eviction of the
-- user's "[norris] goal: ..." turn doesn't lose the anchor.
@@ -165,6 +181,12 @@ message if they declined.]]
function Context:to_messages()
local sys_content = self.system_prompt
-- Phase 4 [background] memory block. Suppressed during Norris (R-C1
-- — avoid ~16K of redundant tokens per planning iteration).
if not self.norris_active then
sys_content = sys_content .. compose_background(self.memory_items)
end
-- Phase 3 NORRIS MODE suffix. Last block so its instructions dominate.
if self.norris_active and self.norris_goal then
sys_content = sys_content
.. string.format(NORRIS_SUFFIX_TEMPLATE, self.norris_goal)