Merge origin/master: scope-aware fusion of the tools/execute seam, session-prefix, and tool-cordis
Master brought 50 commits (the tool-cordis group, dsh-code-runtime + worker, the tools/execute around-dispatch seam + timeout-policy, repeat-tool-guard, agent/session-prefix, the ui reorganization). Beyond the ten textual conflicts, the merge reconciles master's new seams with this branch's scoped-registration world: - tools/execute (new waterfall around core dispatch): dispatched with the SAME exec.agent carrier as the pre/post waterfalls — an agent.ctx wrapper times/retries only its own agent's calls — and its base thunk resolves the tool through the caller's visible view (get(exec.name, exec.agent)), so a scoped/shadowed tool dispatches and a restricted-away global stays UNKNOWN_TOOL. Declared this: Scoped<ToolRegistry> with the scope-filtered doc sentence; invariants table + verify-scoped-dispatch pin it (21 events). - agent/session-prefix (new waterfall, once per loop instance): composed via the fused agentEvents dispatcher (scope-filtered like every agent-subject event), declared this: Scoped<Agent>, table-pinned. agent/pre-step keeps master's new sessionPrefix parameter with this branch's Scoped this. - timeout-policy reads the budget through the caller's visible view (get(exec.name, exec.agent)): a scoped tool's own timeoutMs governs its calls; a global name-twin's budget is never misapplied to a shadowing per-agent variant. - tool-cordis: cordis_inspect's tools section lists the CALLING agent's view (its description promises "what you can call"); the sandbox tool façade's reads resolve through the mount's own scope, mirroring where its register lands writes; sandboxRegisterTool's return type carries the exact-disposer union honestly. dsh-scope declared as peer+dev with the project reference. - doc-sync chain unions master's verify-cordis-api with this branch's verify-scoped-dispatch; the generated catalogs, event matrix (the zero-dispatcher guard passes over master's new events), module graph, and the cordis api-catalog are regenerated on the merged surface. Full gate sequence green on the merged tree: typecheck, lint, per-file 100% coverage (2668 tests), snapshots (38), doc-sync, module graph, build, hygiene, demo smoke.
This commit is contained in:
@@ -387,10 +387,12 @@ export function apply(ctx: Context, config: Config = {}): void {
|
||||
'agent/pre-step': args => args[0],
|
||||
'agent/prompt-submit': args => args[0],
|
||||
'agent/request': args => args[0],
|
||||
'agent/session-prefix': args => args[0],
|
||||
'agent/step-result': args => args[0],
|
||||
'agent/turn-continuation': args => args[0],
|
||||
'agent/error': args => args[0],
|
||||
'tools/pre-execute': args => (args[0] as ToolExecution).agent,
|
||||
'tools/execute': args => (args[0] as ToolExecution).agent,
|
||||
'tools/post-execute': args => (args[0] as ToolExecution).agent,
|
||||
'system-prompt/assemble': args => (args[1] as AssembleContext).scope,
|
||||
'session/created': null,
|
||||
@@ -457,8 +459,11 @@ export function apply(ctx: Context, config: Config = {}): void {
|
||||
// hand-built one-shot (compaction summarize) is unfrozen and skipped — must
|
||||
// be EXACTLY what the session log reconstructs:
|
||||
//
|
||||
// - messages: the derivation over the log prefix strictly before the
|
||||
// in-flight step's `step/start` (the reconstruction boundary). Compared
|
||||
// - messages: the folded header's session prefix (messagePrefix — the
|
||||
// `agent/session-prefix` product, logged on the header because no
|
||||
// session event carries it) followed by the
|
||||
// derivation over the log prefix strictly before the in-flight step's
|
||||
// `step/start` (the reconstruction boundary). The derivation is compared
|
||||
// against a FRESH Session built over that prefix — the same projection
|
||||
// code with zero shared state, so the live cache under test cannot vouch
|
||||
// for itself. Boundary-correct by construction: content appended after
|
||||
@@ -498,18 +503,22 @@ export function apply(ctx: Context, config: Config = {}): void {
|
||||
if (boundary === -1) {
|
||||
throw new InvariantError('a loop-built request with no step/start in its session log')
|
||||
}
|
||||
const rebuilt = new Session(SessionId(`${String(session.id)}-invariant-rebuild`), structuredClone(events.slice(0, boundary)))
|
||||
// JSON equality is sound here: both sides are structuredClones produced by
|
||||
// the same projection code path, so key insertion order matches when the
|
||||
// values do.
|
||||
if (JSON.stringify(options.messages) !== JSON.stringify(rebuilt.deriveMessages())) {
|
||||
throw new InvariantError(`llm request for session "${String(session.id)}" diverges from the boundary derivation (log-reconstruction desync)`)
|
||||
}
|
||||
|
||||
const header = foldRequestHeader(events)
|
||||
if (header === undefined) {
|
||||
throw new InvariantError('a loop-built request with no request/header event in its session log')
|
||||
}
|
||||
const rebuilt = new Session(SessionId(`${String(session.id)}-invariant-rebuild`), structuredClone(events.slice(0, boundary)))
|
||||
// The reconstruction equation: the folded header's session prefix, then
|
||||
// the boundary derivation — the loop
|
||||
// logs the header event BEFORE dispatch, so the fold already covers this
|
||||
// request's prefix. JSON equality is sound here: both sides are
|
||||
// structuredClones produced by the same projection/build code path, so key
|
||||
// insertion order matches when the values do.
|
||||
const expected = [...header.messagePrefix ?? [], ...rebuilt.deriveMessages()]
|
||||
if (JSON.stringify(options.messages) !== JSON.stringify(expected)) {
|
||||
throw new InvariantError(`llm request for session "${String(session.id)}" diverges from the boundary derivation (log-reconstruction desync)`)
|
||||
}
|
||||
|
||||
const headerMatches = options.model === header.config.model
|
||||
&& options.system === header.system
|
||||
&& options.temperature === header.config.temperature
|
||||
|
||||
Reference in New Issue
Block a user