Harness spec: stubs per request, the compaction table from the reads it covers
context.md and loop.json `context`, spec 3.0.0-draft: - A stub names only a result in the request being built. A chat on the server sends no earlier reply's tool results, so a read in the next reply is answered in full there; the CLI, which resends every result, keeps stubbing across steps. Said for reads and for repeated listings alike. - The compaction table is drawn from the reads the summary covers: the full reads since the previous compaction in turns still in the history, whether or not the request still carries them. A turn taken back takes its reads with it. The CLI's dedupe table is exactly that set; a chat finds it in what it stored. - Ids are the result's number in the session, in hex, at least 4 digits (#0001); the examples and the prompt block now use such ids. - A bracket names the path as the call gave it; what identifies a file is its absolute path. Conformance `context`: a `reply` step (a request carrying none of the results so far) and a case for both rules. The CLI's runner keeps the history apart from the request; compactionTable takes the reads. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
6 files changed
+150
-40
No files matched your search
+14
-5
@@ -117,15 +117,23 @@ export const AREAS: Record<string, (c: Case) => unknown> = {
|
||||
const files = new Set(c.files as string[])
|
||||
return instructionChain(c.boundary as string, c.start as string, (p) => files.has(p) && readableUnder(c.grants as Record<string, string> | undefined, p))
|
||||
},
|
||||
// Context hygiene: one session's insertions, step by step. The conversation is the results
|
||||
// inserted since the last compaction, which is what a stub may point at.
|
||||
// Context hygiene: one session's insertions, step by step. `messages` is the request being built
|
||||
// (what a stub may point at); `history` the results since the last compaction, which its table
|
||||
// is drawn from. `reply` starts a request that carries none of them, as a chat's next reply does.
|
||||
context: (c) => {
|
||||
const messages: { role: string; content: string }[] = []
|
||||
const rc = new ResultContext(memoryResults(), () => presentIds(messages), { ...CONTEXT.cap, ...((c.limits ?? {}) as object) })
|
||||
const history = new Set<string>()
|
||||
const store = memoryResults()
|
||||
const rc = new ResultContext(store, () => presentIds(messages), { ...CONTEXT.cap, ...((c.limits ?? {}) as object) })
|
||||
return (c.steps as Record<string, any>[]).map((st) => {
|
||||
if (st.op === "compact") {
|
||||
const table = rc.compactionTable()
|
||||
if (st.op === "reply") {
|
||||
messages.length = 0
|
||||
return ""
|
||||
}
|
||||
if (st.op === "compact") {
|
||||
const table = rc.compactionTable(store.all().filter((r) => history.has(r.id)))
|
||||
messages.length = 0
|
||||
history.clear()
|
||||
return table
|
||||
}
|
||||
let text: string
|
||||
@@ -138,6 +146,7 @@ export const AREAS: Record<string, (c: Case) => unknown> = {
|
||||
} else if (st.op === "recall") text = rc.insert({ kind: "recall", target: st.target, offset: st.offset, limit: st.limit }, st.id)
|
||||
else text = rc.insert({ kind: "output", tool: st.tool, args: st.args, text: st.text }, st.id)
|
||||
messages.push({ role: "tool", content: text })
|
||||
history.add(st.id)
|
||||
return text
|
||||
})
|
||||
},
|
||||
|
||||
Reference in new issue
Block a user