Merge branch 'codex/goal-commands' into codex/ralph-tool

# Conflicts:
#	examples/acp-agent/tests/snapshots/advanced-toolchain/system-prompt.expected.md
#	examples/acp-agent/tests/snapshots/advanced-toolchain/tool-schemas.expected.json
#	examples/acp-agent/tests/snapshots/both-mode-turn/system-prompt.expected.md
#	examples/acp-agent/tests/snapshots/both-mode-turn/tool-schemas.expected.json
#	examples/acp-agent/tests/snapshots/code-mode-turn/system-prompt.expected.md
#	examples/acp-agent/tests/snapshots/model-switching/tool-schemas.expected.json
#	examples/acp-agent/tests/snapshots/permission-switching/tool-schemas.expected.json
#	examples/acp-agent/tests/snapshots/skill-load/tool-schemas.expected.json
#	examples/acp-agent/tests/snapshots/text-turn/tool-schemas.expected.json
#	examples/acp-agent/tests/snapshots/workspace-edit/system-prompt.expected.md
#	examples/acp-agent/tests/snapshots/workspace-edit/tool-schemas.expected.json
#	scripts/gen-tool-catalog.ts
This commit is contained in:
Tianyi Cui
2026-07-20 21:05:05 +08:00
466 changed files with 13130 additions and 8908 deletions

View File

@@ -2,11 +2,11 @@
"prompt": "respond",
"logs": [
{ "file": "b/parent.jsonl", "lines": [
{ "type": "session", "id": "{{SID}}", "createdAt": 700, "cwd": "{{CWD}}" },
{ "type": "session", "id": "{{SID}}", "createdAt": 700, "cwd": "{{CWD}}", "delegationDepth": 0 },
{ "type": "request/header", "seq": 0, "time": 3, "data": { "header": { "config": { "model": "fake" }, "system": "SYS PROMPT", "tools": [{ "name": "t1", "description": "D1", "parameters": { "type": "object" } }] }, "reason": "initial" } }
]},
{ "file": "b/child.jsonl", "lines": [
{ "type": "session", "id": "abababab-cdcd-4efe-8ada-badabadabada", "createdAt": 800, "cwd": "{{CWD}}", "parentSession": "{{SID}}" },
{ "type": "session", "id": "abababab-cdcd-4efe-8ada-badabadabada", "createdAt": 800, "cwd": "{{CWD}}", "parentSession": "{{SID}}", "delegationDepth": 1 },
{ "type": "request/header", "seq": 0, "time": 2, "data": { "header": { "config": { "model": "fake" }, "system": "SYS PROMPT", "tools": [{ "name": "t1", "description": "D1", "parameters": { "type": "object" } }] }, "reason": "initial" } }
]}
]

View File

@@ -1,2 +1,2 @@
{"type":"session","id":"abababab-cdcd-4efe-8ada-badabadabada","createdAt":800,"cwd":"/var/folders/2g/b32ct0qn1d728l_v6tdkjytr0000gn/T/acp-snap-cwd-KBQJbW","parentSession":"f6fa7fcf-dd9c-4b39-8815-b25ddcebfd88"}
{"type":"session","id":"abababab-cdcd-4efe-8ada-badabadabada","createdAt":800,"cwd":"/var/folders/2g/b32ct0qn1d728l_v6tdkjytr0000gn/T/acp-snap-cwd-KBQJbW","parentSession":"f6fa7fcf-dd9c-4b39-8815-b25ddcebfd88","delegationDepth":1}
{"type":"request/header","seq":0,"time":2,"data":{"header":{"config":{"model":"fake"},"system":"{{system}}","tools":"{{tools}}"},"reason":"initial"}}

View File

@@ -1,2 +1,2 @@
{"type":"session","id":"f6fa7fcf-dd9c-4b39-8815-b25ddcebfd88","createdAt":700,"cwd":"/var/folders/2g/b32ct0qn1d728l_v6tdkjytr0000gn/T/acp-snap-cwd-KBQJbW"}
{"type":"session","id":"f6fa7fcf-dd9c-4b39-8815-b25ddcebfd88","createdAt":700,"cwd":"/var/folders/2g/b32ct0qn1d728l_v6tdkjytr0000gn/T/acp-snap-cwd-KBQJbW","delegationDepth":0}
{"type":"request/header","seq":0,"time":3,"data":{"header":{"config":{"model":"fake"},"system":"{{system}}","tools":"{{tools}}"},"reason":"initial"}}

View File

@@ -3,7 +3,7 @@
"logs": [{
"file": "b/main.jsonl",
"lines": [
{ "type": "session", "id": "{{SID}}", "createdAt": 600, "cwd": "{{CWD}}" },
{ "type": "session", "id": "{{SID}}", "createdAt": 600, "cwd": "{{CWD}}", "delegationDepth": 0 },
{ "type": "request/header", "seq": 0, "time": 4, "data": { "header": { "config": { "model": "fake" }, "system": "SYS PROMPT", "tools": [{ "name": "t1", "description": "D1", "parameters": { "type": "object" } }] }, "reason": "initial" } }
]
}]

View File

@@ -1,2 +1,2 @@
{"type":"session","id":"ccdc749f-56f3-4267-9750-598b5c60b7b2","createdAt":600,"cwd":"/var/folders/2g/b32ct0qn1d728l_v6tdkjytr0000gn/T/acp-snap-cwd-nOQ4Gy"}
{"type":"session","id":"ccdc749f-56f3-4267-9750-598b5c60b7b2","createdAt":600,"cwd":"/var/folders/2g/b32ct0qn1d728l_v6tdkjytr0000gn/T/acp-snap-cwd-nOQ4Gy","delegationDepth":0}
{"type":"request/header","seq":0,"time":4,"data":{"header":{"config":{"model":"fake"},"system":"{{system}}","tools":"{{tools}}"},"reason":"initial"}}

View File

@@ -1 +1 @@
{"type":"session","id":"{{sessionId}}","createdAt":0,"cwd":"{{cwd}}"}
{"type":"session","id":"{{sessionId}}","createdAt":0,"cwd":"{{cwd}}","delegationDepth":0}

View File

@@ -3,7 +3,7 @@
"logs": [{
"file": "b/main.jsonl",
"lines": [
{ "type": "session", "id": "{{SID}}", "createdAt": 500, "cwd": "{{CWD}}" },
{ "type": "session", "id": "{{SID}}", "createdAt": 500, "cwd": "{{CWD}}", "delegationDepth": 0 },
{ "type": "turn/end", "seq": 1, "time": 9, "data": { "error": "model exploded" } }
]
}]

View File

@@ -1,2 +1,2 @@
{"type":"session","id":"44444444-3333-4222-8111-000000000000","createdAt":17,"cwd":"/rec/authored-cwd"}
{"type":"session","id":"44444444-3333-4222-8111-000000000000","createdAt":17,"cwd":"/rec/authored-cwd","delegationDepth":0}
{"type":"turn/end","seq":1,"time":17,"data":{"error":"model exploded"}}

View File

@@ -3,7 +3,7 @@
"logs": [{
"file": "b/main.jsonl",
"lines": [
{ "type": "session", "id": "{{SID}}", "createdAt": 400, "cwd": "{{CWD}}" },
{ "type": "session", "id": "{{SID}}", "createdAt": 400, "cwd": "{{CWD}}", "delegationDepth": 0 },
{ "type": "hook/result", "seq": 1, "time": 8, "data": { "decision": "block", "durationMs": 37 } }
]
}]

View File

@@ -1,2 +1,2 @@
{"type":"session","id":"99999999-8888-4777-8666-555555555555","createdAt":13,"cwd":"/rec/blocked-cwd"}
{"type":"session","id":"99999999-8888-4777-8666-555555555555","createdAt":13,"cwd":"/rec/blocked-cwd","delegationDepth":0}
{"type":"hook/result","seq":1,"time":13,"data":{"decision":"block","durationMs":99}}

View File

@@ -1 +1 @@
{"type":"session","id":"{{sessionId}}","createdAt":0,"cwd":"{{cwd}}"}
{"type":"session","id":"{{sessionId}}","createdAt":0,"cwd":"{{cwd}}","delegationDepth":0}

View File

@@ -3,7 +3,7 @@
"logs": [{
"file": "b/main.jsonl",
"lines": [
{ "type": "session", "id": "{{SID}}", "createdAt": 100, "cwd": "{{CWD}}" },
{ "type": "session", "id": "{{SID}}", "createdAt": 100, "cwd": "{{CWD}}", "delegationDepth": 0 },
{ "type": "request/header", "seq": 0, "time": 100, "data": { "header": { "config": { "model": "fake" }, "system": "SYS PROMPT", "tools": [{ "name": "t1", "description": "D1", "parameters": { "type": "object" } }] }, "reason": "initial" } },
{ "type": "request/header", "seq": 1, "time": 100, "data": { "header": { "config": { "model": "fake" }, "system": "SYS PROMPT\n\nNEW PROMPT LINE", "tools": [{ "name": "t1", "description": "D1", "parameters": { "type": "object" } }] }, "reason": "change" } },
{ "type": "turn/start", "seq": 2, "time": 100, "data": { "turn": 1 } }

View File

@@ -1,4 +1,4 @@
{"type":"session","id":"12121212-3434-4545-8686-787878787878","createdAt":7,"cwd":"/rec/pin-cwd"}
{"type":"session","id":"12121212-3434-4545-8686-787878787878","createdAt":7,"cwd":"/rec/pin-cwd","delegationDepth":0}
{"type":"request/header","seq":0,"time":7,"data":{"header":{"config":{"model":"fake"},"system":"{{system}}","tools":"{{tools}}"},"reason":"initial"}}
{"type":"request/header","seq":1,"time":7,"data":{"header":{"config":{"model":"fake"},"system":"{{system}}","tools":"{{tools}}"},"reason":"change"}}
{"type":"turn/start","seq":2,"time":7,"data":{"turn":1}}

View File

@@ -3,12 +3,12 @@
"echoWorkspace": true,
"logs": [
{ "file": "b/parent.jsonl", "lines": [
{ "type": "session", "id": "{{SID}}", "createdAt": 200, "cwd": "{{CWD}}" },
{ "type": "session", "id": "{{SID}}", "createdAt": 200, "cwd": "{{CWD}}", "delegationDepth": 0 },
{ "type": "request/header", "seq": 0, "time": 5, "data": { "header": { "config": { "model": "fake" }, "system": "SYS PROMPT", "tools": [{ "name": "t1", "description": "D1", "parameters": { "type": "object" } }] }, "reason": "initial" } },
{ "type": "assistant/chunk", "seq": 1, "time": 5, "data": { "turn": 1, "step": 1, "chunk": { "type": "text-delta", "index": 0, "text": "hi" } } }
]},
{ "file": "b/child.jsonl", "lines": [
{ "type": "session", "id": "eeeeeeee-1111-4222-8333-444444444444", "createdAt": 300, "cwd": "{{CWD}}", "parentSession": "{{SID}}" },
{ "type": "session", "id": "eeeeeeee-1111-4222-8333-444444444444", "createdAt": 300, "cwd": "{{CWD}}", "parentSession": "{{SID}}", "delegationDepth": 1 },
{ "type": "request/header", "seq": 0, "time": 6, "data": { "header": { "config": { "model": "fake" }, "system": "SYS PROMPT", "tools": [{ "name": "t1", "description": "D1", "parameters": { "type": "object" } }] }, "reason": "initial" } }
]}
]

View File

@@ -1,2 +1,2 @@
{"type":"session","id":"eeeeeeee-1111-4222-8333-444444444444","createdAt":12,"cwd":"/rec/plain-cwd","parentSession":"56565656-7878-4989-8a9a-9b9b9b9b9b9b"}
{"type":"session","id":"eeeeeeee-1111-4222-8333-444444444444","createdAt":12,"cwd":"/rec/plain-cwd","parentSession":"56565656-7878-4989-8a9a-9b9b9b9b9b9b","delegationDepth":1}
{"type":"request/header","seq":0,"time":12,"data":{"header":{"config":{"model":"fake"},"system":"{{system}}","tools":"{{tools}}"},"reason":"initial"}}

View File

@@ -1,3 +1,3 @@
{"type":"session","id":"56565656-7878-4989-8a9a-9b9b9b9b9b9b","createdAt":11,"cwd":"/rec/plain-cwd"}
{"type":"session","id":"56565656-7878-4989-8a9a-9b9b9b9b9b9b","createdAt":11,"cwd":"/rec/plain-cwd","delegationDepth":0}
{"type":"request/header","seq":0,"time":11,"data":{"header":{"config":{"model":"fake"},"system":"{{system}}","tools":"{{tools}}"},"reason":"initial"}}
{"type":"assistant/chunk","seq":1,"time":11,"data":{"turn":1,"step":1,"chunk":{"type":"text-delta","index":0,"text":"hi"}}}

View File

@@ -90,12 +90,12 @@ function staleRefreshFixtures(dir: string): void {
writeFileSync(plainBehaviorFile, `${JSON.stringify(plainBehavior, null, 2)}\n`)
writeFileSync(join(dir, 'blocked-log', 'session.jsonl'), [
'{"type":"session","id":"99999999-8888-4777-8666-555555555555","createdAt":13,"cwd":"/rec/blocked-cwd"}',
'{"type":"session","id":"99999999-8888-4777-8666-555555555555","createdAt":13,"cwd":"/rec/blocked-cwd","delegationDepth":0}',
'{"type":"hook/result","seq":1,"time":13,"data":{"decision":"stale","durationMs":99}}',
'',
].join('\n'))
writeFileSync(join(dir, 'authored-error', 'session.jsonl'), [
'{"type":"session","id":"77777777-8888-4777-8666-555555555555","createdAt":13,"cwd":"/rec/error-cwd"}',
'{"type":"session","id":"77777777-8888-4777-8666-555555555555","createdAt":13,"cwd":"/rec/error-cwd","delegationDepth":0}',
'{"type":"turn/end","seq":1,"time":9,"data":{"error":"stale"}}',
'',
].join('\n'))

View File

@@ -4,7 +4,7 @@ Runtime event-contract assertions intended for development diagnostics. This pur
The plugin has no environment guard: it is active wherever it is registered. The default [`dsh-agent-spine-demo`](../../examples/agent-spine-demo/README.md) bundle mounts it unconditionally; a custom composition can omit it when the runtime cost is undesirable. It doubles as executable documentation of the event taxonomy — the assertions *are* the contract.
Session itself owns immutable, surface-valid log storage in every composition: it takes one lossless JSON snapshot of each candidate, validates the complete surface transition, deep-freezes the accepted record, and exposes the log through immutable array snapshots. The invariants plugin checks the remaining cross-record and cross-seam rules that Session does not own.
Session itself owns immutable, surface-valid log storage in every composition: it takes one lossless JSON snapshot of each candidate, validates complete provenance and positional replacement, restricts `tool/result` replacement to one current result's `content`, deep-freezes the accepted record, and exposes the log through immutable array snapshots. The invariants plugin checks the remaining cross-record and cross-seam rules that Session does not own.
Session-log assertions run during Cordis `internal/dispatch`, while `Session.append()` is resolving the `session/event` callback snapshot but before it pushes the candidate into the log. A valid transition is staged by exact event identity and applied to the live trace only when that same committed event reaches the plugin's contained post-commit listener. A later internal dispatch check can therefore veto without advancing either the log or the invariant trace, while ordinary `session/event` observer failures remain observe-only.
@@ -31,8 +31,7 @@ Session log (per session):
- **turns pair and nest** — `turn/start` opens a turn, `turn/end` closes the matching one; no overlapping turns.
- **steps nest in turns** — `step/start` opens a step in the open turn; `step/end` closes the matching step.
- **chunks belong to an open step** — `step/start` precedes its `assistant/chunk`s.
- **a `tool/result` needs a prior `tool/call`** — but NOT the converse: a `tool/call` may have no result (a thrown tool-execution pipeline step ends the turn with no `tool/result`, which is legal).
- **provenance sources are valid and unambiguous** — `sourceEventSeqs` contains unique earlier known seqs; only `assistant/message` may carry an explicit empty list, which denotes a known empty provider stream rather than absent legacy provenance.
- **an appended `tool/result` needs a prior `tool/call`** — fresh `surfaceOp: 'append'` results name the open step and consume its pending call. A Session-validated replacement is a turn-enclosed rewrite, not another execution. A `tool/call` may still have no result when the execution pipeline throws.
Agent status (per agent):

View File

@@ -151,6 +151,16 @@ function validateEvent(trace: SessionTrace, event: SessionEvent): SessionTraceTr
break
}
case 'tool/result': {
// Session has already validated a provenance-backed content rewrite.
// It is durable turn work, not a second execution of the original call.
if (event.surfaceOp !== 'append') {
if (trace.openTurn === null) {
throw new InvariantError(
'tool/result surface replacement appended outside any open turn',
)
}
break
}
requireOpenStep(trace, 'tool/result', event.data.turn, event.data.step)
// A result needs a prior matching call in the same step. (The converse
// does NOT hold: a call may have no result — a throwing tool-execution

View File

@@ -189,6 +189,19 @@ describe('session-log invariants', () => {
.toThrow(/no prior tool\/call/)
})
it('keeps fresh tool-result appends open-step and pending-call checked', async () => {
const { ctx } = await setup()
const session = ctx.sessions.create()
session.append('turn/start', { turn: 1, trigger: { kind: 'message', source: { kind: 'user' } } })
expect(() => session.append('tool/result', {
turn: 1,
step: 1,
callId: CallId('closed'),
content: [],
isError: false,
}, { surfaceOp: 'append' })).toThrow(/open is turn 1\/step null/)
})
it('allows a synthetic interrupted tool/result from crash repair without a prior tool/call event', async () => {
const { ctx } = await setup()
const session = ctx.sessions.create()
@@ -465,6 +478,41 @@ describe('HMR safety', () => {
})
describe('surface contract under the invariants composition', () => {
async function toolResultRewriteFixture(openRewriteTurn = true) {
const { ctx } = await setup()
const session = ctx.sessions.create()
session.append('turn/start', { turn: 1, trigger: { kind: 'message', source: { kind: 'user' } } })
const unrelated = session.append('user/message', {
content: [{ type: 'text', text: 'request' }],
source: { kind: 'user' },
}, { surfaceOp: 'append' })
session.append('step/start', { turn: 1, step: 1 })
session.append('tool/call', {
turn: 1,
step: 1,
callId: CallId('rewrite'),
name: 'echo',
arguments: '{}',
})
const originalData = {
turn: 1,
step: 1,
callId: CallId('rewrite'),
content: [{ type: 'text' as const, text: 'original' }],
isError: true,
error: { name: 'ExitError', code: 'EXIT_1' },
meta: { presentation: { kind: 'terminal', output: 'full output' } },
futureField: { nested: ['preserve', 1] },
}
const original = session.append('tool/result', originalData, { surfaceOp: 'append' })
session.append('step/end', { turn: 1, step: 1 })
session.append('turn/end', { turn: 1, reason: { kind: 'completed' } })
if (openRewriteTurn) {
session.append('turn/start', { turn: 2, trigger: { kind: 'message', source: { kind: 'user' } } })
}
return { session, unrelated, original }
}
it('accepts well-formed surface metadata', async () => {
const { ctx } = await setup()
const session = ctx.sessions.create()
@@ -487,6 +535,71 @@ describe('surface contract under the invariants composition', () => {
// no throw — well-formed replace op
})
it('treats a provenance-backed tool-result replacement as a turn-enclosed rewrite', async () => {
const { session, original } = await toolResultRewriteFixture()
expect(() => session.append('tool/result', {
...original.data,
content: [{ type: 'text', text: 'pruned' }],
}, {
surfaceOp: { op: 'replace', start: original.seq, end: original.seq },
sourceEventSeqs: [original.seq],
})).not.toThrow()
})
it('rejects a tool-result replacement outside a turn', async () => {
const { session, original } = await toolResultRewriteFixture(false)
expect(() => session.append('tool/result', {
...original.data,
content: [{ type: 'text', text: 'pruned' }],
}, {
surfaceOp: { op: 'replace', start: original.seq, end: original.seq },
sourceEventSeqs: [original.seq],
})).toThrow(/outside any open turn/)
})
it('rejects a tool-result replacement targeting an unrelated current node', async () => {
const { session, unrelated, original } = await toolResultRewriteFixture()
expect(() => session.append('tool/result', {
...original.data,
content: [{ type: 'text', text: 'forged' }],
}, {
surfaceOp: { op: 'replace', start: unrelated.seq, end: unrelated.seq },
sourceEventSeqs: [unrelated.seq],
})).toThrow(/must target a current tool\/result/)
})
it('rejects a multi-node tool-result replacement even with complete provenance', async () => {
const { session, unrelated, original } = await toolResultRewriteFixture()
expect(() => session.append('tool/result', {
...original.data,
content: [{ type: 'text', text: 'forged' }],
}, {
surfaceOp: { op: 'replace', start: unrelated.seq, end: original.seq },
sourceEventSeqs: [unrelated.seq, original.seq],
})).toThrow(/must rewrite exactly one current node/)
})
it.each([
['callId', { callId: CallId('forged') }],
['turn', { turn: 2 }],
['step', { step: 2 }],
['error', { error: { name: 'ExitError', code: 'DIFFERENT' } }],
['meta', { meta: { presentation: { kind: 'generic' } } }],
['future data', { futureField: { nested: ['changed'] } }],
])('rejects a content rewrite with altered %s', async (_label, altered) => {
const { session, original } = await toolResultRewriteFixture()
expect(() => session.append('tool/result', {
...original.data,
...altered,
content: [{ type: 'text', text: 'pruned' }],
}, {
surfaceOp: { op: 'replace', start: original.seq, end: original.seq },
sourceEventSeqs: [original.seq],
})).toThrow(/may change only content/)
})
it('accepts known-empty assistant provenance and rejects empty provenance elsewhere', async () => {
const { ctx } = await setup()
const session = ctx.sessions.create()