Harness spec 3.1.0: the prefix stays still, in the CLI
ci / check (push) Canceled after 0s

The system prompt is made once per session (start, compaction, model
switch), kept in session_meta and resent byte for byte; it holds no
date. What changes mid-session -- the date, a mode the person chose, a
plan or memory or instruction file they edited, a personality, MCP
instructions that arrived later -- is a <session-note> on the next user
message, stored with it. The tool list is fixed for the session:
plan_submit is always offered and answers unavailable outside plan
mode, the wrap-up keeps the tools and runs no call, a withdrawn tool
stays listed and answers its refusal; the first request waits up to
mcp_wait_ms for MCP servers. Compaction asks for its summary as the
next turn of the same conversation. Cached input tokens are parsed
(incl. llama.cpp timings), stored per request and shown in /context.

Spec: context.md §7/§10, loop.json context.note (+ plan_ended,
plan_own, personal, empty, mcp_wait_ms), modes.json plan.refused,
compact.md without the transcript heading. Conformance area prefix.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
HomerandClaude Opus 5.5 committed 2026-11-05 13:22:02 +00:00
1 parent ecae6247fd
commit 8e06590d57
36 files changed
+1533 -169

No files matched your search

+55
View File
@@ -23,6 +23,9 @@ import { CONTEXT, hunks, lineCount, memoryResults, presentIds, ResultContext, sh
import { readBody } from "../src/tool/read.ts"
import { applyOps, type Op, type Target } from "../src/memory/store.ts"
import { jobNote, type NoteCase } from "../src/jobs/note.ts"
import { compactAsk, noteFor, offeredFor, toldAt, unavailable, type Now, type Requirement, type Told } from "../src/session/prefix.ts"
import { promptText } from "../src/prompt/assemble.ts"
import { TOOL_SPECS } from "../src/harness.ts"
type Case = Record<string, unknown>
@@ -150,6 +153,58 @@ export const AREAS: Record<string, (c: Case) => unknown> = {
return text
})
},
// The prefix (context.md §10): one session's requests, step by step — which prompt each sends (a
// new number each time one is made), which tools it offers, and the note on a user message.
prefix: (c) => {
const start = c.start as Partial<Now> & { could?: Requirement[]; met?: Requirement[] }
const could = new Set<Requirement>(start.could ?? [])
let met = new Set<Requirement>(start.met ?? could)
const now: Now = { date: start.date!, mode: start.mode ?? "manual", plan: start.plan ?? "", memory: start.memory ?? "", personality: start.personality ?? "", files: start.files ?? {}, mcp: start.mcp ?? {} }
const modeText = (m: Mode) => fillPrompt(promptText(`modes/${m}.md`), { plan_dir: (c.plan_dir as string | undefined) ?? ".agent/plans" })
const requires = (t: string) => TOOL_SPECS[t]?.requires as Requirement[] | undefined
let made: { system: number; tools: string[]; told: Told } | undefined
let prompts = 0
let carried: string | undefined
const make = () => {
if (made) return made
made = { system: ++prompts, tools: (c.tools as string[]).filter((t) => offeredFor(requires(t), could)), told: { ...toldAt(now), ...(carried ? { date: carried } : {}) } }
carried = undefined
return made
}
const fields = ["date", "mode", "plan", "memory", "personality", "files", "mcp"] as const
return (c.steps as Record<string, any>[]).map((st) => {
if (st.op === "set") {
for (const f of fields) if (f in st) (now as unknown as Record<string, unknown>)[f] = structuredClone(st[f])
if (st.met) met = new Set(st.met)
// The model's own tool changed it: its result told the model.
if (st.by === "model" && made) for (const f of fields) if (f in st && f !== "date") (made.told as unknown as Record<string, unknown>)[f] = structuredClone(st[f])
return null
}
if (st.op === "call") {
const here = new Set(met)
if (now.mode === "plan") here.add("plan")
else here.delete("plan")
return unavailable(st.tool, requires(st.tool), here) ?? null
}
if (st.op === "model") {
carried = made?.told.date
made = undefined
return null
}
const k = make()
if (st.op === "user") {
const { note, told } = noteFor(k.told, now, modeText)
if (note) k.told = told
return { system: k.system, tools: k.tools, note: note || null }
}
if (st.op === "tool" || st.op === "wrapup") return { system: k.system, tools: k.tools, note: null }
if (st.op === "compact") {
made = undefined
return { system: k.system, tools: k.tools, ask: compactAsk() }
}
throw new Error(`prefix: unknown op ${String(st.op)}`)
})
},
library_scope: (c) => resolveBase(c.bases as { id: string; name: string }[], c.base as string | null),
toolcall_index: (c) => {
// The deltas as the OpenAI stream carries them: `function.name` and `function.arguments`.