context: norris_tasks anchor + task-hint composition + reset clear

Phase 10 C2. Three additive changes; no regression.

- compose_norris_task_hint(self) — module-scope helper. Returns ""
  when norris_tasks is nil OR list empty OR current pointer past
  end. Otherwise returns "\n\nCurrent step k/N:\n    <task text>".

- Context:to_messages appends the hint AFTER the NORRIS suffix,
  inside the existing `if self.norris_active and self.norris_goal`
  branch. NORRIS_SUFFIX_TEMPLATE is UNCHANGED (R2 fix); the hint
  is a separate concatenation. Goal anchor stays the primary
  per-step instruction; task hint sharpens current focus.

- Context:reset() now clears self.norris_tasks (R6 fix). :reset is
  unreachable mid-Norris (planner runs without readline prompt),
  but if a Norris session crashed leaving stale state, :reset
  recovers cleanly. One line; defensive.

15 unit cases verified:
  - nil/empty/exhausted norris_tasks -> no hint block
  - current=1/3 -> "Current step 1/3" + task text in output
  - NORRIS suffix precedes hint (ordering preserved)
  - hint suppressed when norris_active=false even if tasks set
  - self.turns + self.norris_tasks table identity unmutated
  - Context:reset clears norris_tasks AND turns

Regression: 87/87 safety, 31/31 router_model, repl loads.

C2 isn't called from anywhere yet (ctx.norris_tasks is always nil
until C4 wires the preplan call). No behavior change in the live
tree until then.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-05-17 08:18:24 +00:00
parent e4780483ad
commit 477d8a76cc
+20
View File
@@ -228,6 +228,20 @@ The user will be prompted to confirm destructive actions; expect their
verdict in the next turn as a synthesized "[aish] ... skipped by user"
message if they declined.]]
-- Phase 10 / #89: optional task-hint block appended AFTER the NORRIS
-- suffix when the cloud preplanner emitted a TASK list at :norris
-- launch. self.norris_tasks shape: { current = 1, list = {...} }.
-- Returns "" when no tasks (preplan disabled OR preplan failed OR
-- list exhausted) — keeps the NORRIS suffix backward-compatible.
local function compose_norris_task_hint(self)
if not (self.norris_tasks and self.norris_tasks.list) then return "" end
local k = self.norris_tasks.current
local n = #self.norris_tasks.list
local task = self.norris_tasks.list[k]
if not task then return "" end -- exhausted → no hint
return string.format("\n\nCurrent step %d/%d:\n %s", k, n, task)
end
-- #87: route-aware context compression. Keeps the LAST keep_turns
-- turns; tail-truncates any turn whose content exceeds max_turn_chars.
-- Drops tool turns at the slice head (they'd be orphaned without
@@ -285,6 +299,7 @@ function Context:to_messages(opts)
if self.norris_active and self.norris_goal then
sys_content = sys_content
.. string.format(NORRIS_SUFFIX_TEMPLATE, self.norris_goal)
.. compose_norris_task_hint(self)
end
local msgs = { { role = "system", content = sys_content } }
@@ -517,6 +532,11 @@ function Context:reset()
self.turns = {}
self.pending_exec_output = nil
self.summary = nil
-- Phase 10 R6: clear norris_tasks defensively. :reset is
-- unreachable mid-Norris (no readline prompt while the planner
-- runs), but if a Norris session crashed leaving the field stale,
-- :reset gives the user a clean recovery path.
self.norris_tasks = nil
-- R8 parity: usage_totals + cost_warn_state preserved (matches
-- memory_items + project — "ambient context survives a user-
-- driven conversation reset"). Use :reset_usage to zero the