Circe PR #11: fixes verified

The correction blockers are fixed. This integration milestone is ready within its stated scope.

Verified · round 6

The two round-5 blockers are fixed on PR #11 at 6a89cc9. Two GPT-6 Luna agents implemented bounded fixes; a third audited recovery. The integrated implementation was reviewed and verified independently. All 525 repository tests, 22 strengthened probes and the retained 19-probe suite pass. PR #11 remains open and unmerged.

+5416 −160 6a89cc9

0 findings open. No merge performed. Reviewed 24 September 2026.

Current architectural judgment

The corrections now use the proposal binding checks, and intent identity stays explicit from the persisted frame through the kernel. The implementation reuses the existing evaluator and normalizer. It does not introduce another compiler or orchestration layer.

The real service and restart tests support this milestone. They do not establish live model quality or complete the remaining product capabilities.

Round 6: correction fixes implemented and verified

The two round-5 blockers are fixed on PR #11 at 6a89cc9. Two GPT-6 Luna agents implemented bounded fixes; a third audited recovery. The integrated implementation was reviewed and verified independently. All 525 repository tests, 22 strengthened probes and the retained 19-probe suite pass. PR #11 remains open and unmerged.

Round 5: shared evaluation improved, two correction gaps remain

Independent review of 4db47a0: 519 repository tests and the retained 19-probe suite pass. Two positive continuation checks also pass. Three new compiler-contract probes expose two remaining correction-boundary findings, so the implementation agent’s round-4 approval is superseded.

Round 4: one ordered evaluation, explicit answers, complete durability

This section preserves the implementation agent’s reported verification. The later independent review and round-6 verification supersede that earlier approval.

All six round-3 findings are fixed at 4db47a0 and independently verified. The round-3 probes (8 new failures + 11 repeated controls) all pass; no new findings were introduced.

Round 3: original probes green, contracts still incomplete

Independent re-review of bb7a46b: the original 13 probes and 507 repository tests pass, but eight additional probes expose six remaining findings. The implementation agent’s round-2 approval is superseded by this review.

Round 2: one compiler, durable obligation transitions, honest HTTP outcomes

This section preserves the implementation agent’s reported verification. The later independent review and round-6 verification supersede that earlier approval.

All ten findings are addressed at bb7a46b and independently verified. No new findings were introduced by the fixes.

Final verification evidence

Final run instructionsImplemented fixes525 repository testsTypecheckStrengthened probes22 strengthened probes pass19 retained probes passCurrent-version restart testsBoundary checksReal demoContinuing demoHeld-out fixtures

Historical round-5 evidence

Full sidecarReview textRun instructionsHost probesConfig3 failing probes + positive controls19 retained probes pass519 testsTypecheckStatic boundariesReal demoIncremental diff

All 18 recorded findings fixed

F85–F100 retain passing reproductions. F101 and F102 are now fixed at 6a89cc9. Original finding bodies and evidence remain below.

Round 5 findings, now fixed

P1 F101. Corrections still skip exclusion and binding-consistency checks fixedpackages/perception/src/proposals.ts:699-717 · logic · correctness

Verified at 6a89cc9. Proposals and corrections now call the same binding normalizer. It rejects unresolved exclusions, contradictory summary refs and invalid slot cardinality, using the pending capability for corrections. Corrections retain certified versions; unsupported correction constraints and entity-ref-valued arguments fail closed. The strengthened Q1/Q2 probes include valid identity, so an identity failure cannot mask these checks.

The ordered evaluator now shares dependency checks and verifies correction evidence, but its correction branch still applies fewer binding checks than proposals. It removes accepted exclusions only. It never rejects a correction target covered by unvalidatedExclusionRefs, and never checks that summary refs agree with executable slot refs.

Q1 supplies a frame-certified correction target and a sibling exclusion whose citation is unaccepted. The planner still emits revise for that target. Q2 supplies summary refs naming one task and a slot naming another; the planner silently revises the slot target. Both are public compiler-contract tests with supplied semantic turns. The current built-in graph’s normal retarget path passes the positive control.

These failures do not mean the ordered evaluator refactor should be undone. They show that common binding validation must also be shared. A valid version token proves which entity snapshot was seen; it does not erase an unresolved exclusion or reconcile contradictory target declarations.

Fix direction. Apply the same binding normalization and safety checks to proposal and revision inputs: unresolved exclusions, summary/slot agreement, declared slot constraints and frame coverage. Reject or clarify the unsafe correction before issuing revise. Preserve certified expectations and the passing retarget control; avoid copying another partial check list into the correction branch.

Evidence. Q1 and Q2 fail in evidence/round5/host-review.test.ts. Q4 passes: an ordinary certified retarget revokes authorization, then confirmation completes only the new target. Follow-up to the binding contract in F97.

Source at finding commit
Relevant source
699:         const slots = declaredSlots
700:           .map((slot) => ({
701:             slot: slot.slot,
702:             refs: slot.refs.filter((ref) => !analysis.excludedRefs.has(entityRefKey(ref))),
703:           }))
704:           .filter((slot) => slot.refs.length > 0);
705:         if (slots.length === 0) {
706:           return clarify("an accepted adjustment leaves no targets to correct", pending.targets);
707:         }
708:         const refs = [...slots.flatMap((slot) => slot.refs), ...argumentRefsOf(proposition)];
709:         const evidence = expectedVersionsFromFrame(frame, refs);
710:         for (const ref of refs) {
711:           if (
712:             !evidence.some(
713:               (precondition) => entityRefKey(precondition.ref) === entityRefKey(ref),
714:             )
715:           ) {
716:             return fail(`no frame observation for ${entityRefKey(ref)}`);
717:           }
Prompt for AI Agents
P1 F102. A correction ignores the intent it explicitly names fixedpackages/perception/src/proposals.ts:679-687 · logic · correctness

Verified at 6a89cc9. Frames now associate the live transaction with its intent ID, and context-file decoding preserves that field. Corrections check the pending intent identity and revision against that association and emit expectedIntentId. The kernel checks expectedIntentId before changing bindings. Restart coverage verifies persisted identity, exact-command replay, changed-payload rejection, and one invocation of only the replacement target.

The correction evaluator requires targetIntentRevision but never checks targetIntentId. Command construction at lines 723-726 always targets the frame’s current obligation, so a proposition explicitly naming a different intent can be silently redirected to this transaction. Matching revision numbers do not identify an intent.

Q3 gives a correction a targetIntentId that differs from the pending intent while keeping a valid revision and certified replacement target. The compiler emits revise for the current transaction. The reproduction proves the compiler accepts the wrong destination; it does not claim the built-in graph currently emits that inconsistent proposition.

The original proposal path already treats expected intent identity as meaningful. A correction should either match the intended pending object or fail closed, especially when applications can supply their own proposition builders.

Fix direction. Validate the named intent and revision together against the exact pending intent/obligation association before constructing the revision. If the frame cannot establish the association, reject or clarify rather than defaulting to the current transaction. Carry explicit destination identity through the command boundary as needed.

Evidence. Q3 fails in evidence/round5/host-review.test.ts. Keep Q4 as the valid-retarget control and add matching-intent, mismatched-intent and stale-revision coverage.

Source at finding commit
Relevant source
679:       if (proposition.operation === "correction") {
680:         if (proposition.targetIntentRevision === undefined) {
681:           return fail("correction does not name the revision it replaces");
682:         }
683:         if (!pending) return fail("no pending intent to correct");
684:         const capability = pendingIntent?.capability;
685:         const offered = capability
686:           ? frame.capabilities.find((entry) => entry.capabilityId === capability)
687:           : undefined;
Prompt for AI Agents

Original findings, preserved with passing reproductions

P1 F85. Excluding every pending target falls through to confirmation fixedpackages/perception/src/interpret.ts:365-388 · logic · correctness

correctionProposition returns undefined for both an unchanged target set and an empty result. The affirmation branch treats either result as permission to fall through to commit. An accepted exclusion can therefore disappear precisely when it excludes everything.

R1 first gates completion of auth rotation, then supplies accepted affirmation and exclusion decisions for "Yes, except auth rotation. Leave that alone." The host emits confirm and the real service completes that excluded task. This fails even with correct model decisions; a better prompt or API key cannot repair the deterministic translation.

Fix direction. Distinguish no adjustment, a valid revision, and an empty or invalid revision. An accepted adjustment must never fall through to confirmation of the original targets; empty selection should cancel or clarify under an explicit policy.

Evidence. R1 in host-review.test.ts asserts both absence of confirm and unchanged service task status.

Source at finding commit
Relevant source
365:     if (act === "affirm" || act === "reject") {
366:       const hasRoles = includes.length > 0 || excludes.length > 0;
367:       if (pending && hasRoles) {
368:         const correction = correctionProposition({
369:           frame,
370:           act,
371:           base: includes.length > 0 ? includes.map((entry) => entry.ref) : pending.targets,
372:           excluded: excludes,
373:           pending,
374:           decisions: decisionsFor([
375:             ...includes.map((entry) => entry.specId),
376:             ...excludes.map((entry) => entry.specId),
377:           ]),
378:         });
379:         if (correction) return [correction];
380:       }
381:       if (!obligation) return [];
382:       return [
383:         {
384:           act,
385:           operation: act === "affirm" ? "commit" : "cancel",
386:           refs: obligation.targets.map(cloneRef),
387:           polarity: "include",
388:           decisions: [actTrace],
Prompt for AI Agents
P1 F86. An exclusion converts a new capability into the old operation fixedpackages/perception/src/interpret.ts:395-412 · logic · correctness

The request branch treats any excluded reference overlapping the pending intent as a correction. That inference ignores the newly accepted capability. It preserves the old capability while replacing its targets with the new request targets.

R2 gates complete(auth rotation), then accepts request, resume, include(Billing export), and exclude(auth rotation). The graph emits revise of the old complete intent to Billing export. The requested resume never happens, and a later confirmation would complete the wrong task. Exclusion overlap does not establish that the user is correcting the old operation.

Fix direction. Represent correction versus a new operation in accepted semantics. Preserve the selected capability, and clarify if the relationship to the pending intent is unresolved. Do not use a target-set overlap heuristic to replace an accepted operation.

Evidence. R2 checks the accepted resume decision and requires a corresponding proposal, with service state as the outcome.

Source at finding commit
Relevant source
395:     const pendingHit =
396:       pending !== undefined &&
397:       excludes.some((entry) =>
398:         pending.targets.some((target) => entityRefKey(target) === entityRefKey(entry.ref)),
399:       );
400:     if (pending && (includes.length > 0 || excludes.length > 0) && (act === "correction" || pendingHit)) {
401:       const correction = correctionProposition({
402:         frame,
403:         act: "request",
404:         base: includes.length > 0 ? includes.map((entry) => entry.ref) : pending.targets,
405:         excluded: excludes,
406:         pending,
407:         decisions: decisionsFor([
408:           ...includes.map((entry) => entry.specId),
409:           ...excludes.map((entry) => entry.specId),
410:         ]),
411:       });
412:       if (correction) return [correction];
Prompt for AI Agents
P1 F87. Continuation commands bypass whole-turn safety checks fixedapps/inspector/src/language-plan.ts:110-127 · logic · correctness

planLanguageCommands adds confirm, cancel, and revise commands independently of compileSemanticTurn. Its new verifyPropositionEvidence helper checks citations only. It does not apply the whole-turn structural, exclusion, dependency, frame identity, and evidence checks already centralized for proposals.

R3 supplies a valid confirmation with an accepted sibling exclusion for its target; the planner still emits confirm. R4 supplies a mismatched frame ID; compilation rejects that frame, yet the planner again emits confirm. These exercise the exported planner with caller-supplied semantic turns. The built-in demo need not generate the malformed combination for this entry-point contract to matter. This repeats the earlier alternate-writer safety drift.

Fix direction. Extend one evaluated whole-turn representation to cover all consequence-bearing operations, and make every command come from that result. Do not add another partial validator in the inspector or assume accepted citations imply a valid complete turn.

Evidence. R3 and R4 in host-review.test.ts cover exclusion and frame-identity bypasses. Add matching dependency and malformed-sibling preservation controls.

Source at finding commit
Relevant source
110:   turn.propositions.forEach((proposition, index) => {
111:     const commandId = commandIdFor(proposition, index);
112:     const evidence = verifyPropositionEvidence(turn, proposition, {
113:       traces: turn.traces,
114:     });
115:     const reject = (reason: string): void => {
116:       rejected.push({ index, reason });
117:     };
118:     if (proposition.operation === "commit") {
119:       const obligation = frame.interaction.liveObligation;
120:       if (!evidence.ok) return reject(evidence.reason ?? "unverified evidence");
121:       if (!obligation) return reject("no live obligation to confirm");
122:       commands.push({
123:         kind: "confirm",
124:         commandId,
125:         transactionId: obligation.transactionId,
126:         intentRevision: obligation.revision,
127:       });
Prompt for AI Agents
P1 F88. A failed file write reports refusal after changing the service fixedapps/task-service/src/service.ts:315-328 · logic · data-integrity

The action handler mutates the task and inserts a SUCCEEDED operation before persist succeeds. The outer catch at lines 380-383 converts every exception, including a storage error, into HTTP 400 bad-request. The adapter treats that response as a confirmed execution failure.

R8 makes the temporary persistence path unwritable by placing a directory there. The service task changes to paused in memory, while the adapter returns ok:false. The idempotency map also claims success before durable commit. The kernel receives a definitive failure even though the live service applied the operation; a restart can then produce another history.

Fix direction. Stage the task and operation together, persist them before publishing the new in-memory state, and distinguish validated request refusals from internal or uncertain outcomes. Define and test the durability boundary; write-and-rename alone does not establish power-loss durability.

Evidence. R8 in boundary-review.test.ts injects a real filesystem write failure and checks the service snapshot alongside the adapter outcome.

Source at finding commit
Relevant source
315:     task.status = transitionStatus(action, task.status);
316:     task.version = before + 1;
317:     task.updatedAt = clock();
318:     operations.set(operationKey, {
319:       key: operationKey,
320:       fingerprint,
321:       action,
322:       taskId: task.id,
323:       status: "SUCCEEDED",
324:       versionBefore: before,
325:       versionAfter: task.version,
326:       at: task.updatedAt,
327:     });
328:     persist();
Prompt for AI Agents
P1 F89. Malformed success responses become terminal failure fixedpackages/adapter-http-tasks/src/adapter.ts:380-414 · logic · correctness

The adapter turns an undecodable or unexpected response into EXECUTION_ERROR unless the HTTP status is at least 500. A 200 response with invalid JSON is not evidence that the service refused the mutation. The current fallback nevertheless returns ok:false and makes the kernel mark the effect FAILED.

R7 lets the real service apply a pause, then replaces its successful response with malformed JSON while preserving HTTP 200. The task is paused but the effect is FAILED instead of UNKNOWN. The existing lost-response test covers a thrown fetch, so it misses this equivalent loss of authoritative outcome evidence.

Fix direction. Only recognized, contract-valid refusal responses should become definitive failures. Treat missing or malformed outcome evidence as unknown, preserve the operation receipt, and reconcile without reinvoking. Validate success and reconciliation identity fields as part of the protocol.

Evidence. R7 in host-review.test.ts requires UNKNOWN after the applied action loses a decodable response.

Source at finding commit
Relevant source
380:       const body = (await response.json().catch(() => undefined)) as unknown;
381:       if (response.ok && isRecord(body) && body.ok === true) {
382:         return {
383:           ok: true,
384:           status: "SUCCEEDED",
385:           result: isRecord(body.task) ? { task: body.task, replayed: body.replayed === true } : undefined,
386:           receipt: {
387:             effectId: requestBody.effectId,
388:             executor: `${authorityId}-executor`,
389:             externalOperationId: requestBody.idempotencyKey,
390:             idempotencyKey: requestBody.idempotencyKey,
391:             dispatchedAt: clock(),
392:           },
393:         };
394:       }
395:       const code = isRecord(body) && typeof body.error === "string" ? body.error : undefined;
396:       const failureFor = (): InvokeResult["failure"] => {
397:         switch (response.status) {
398:           case 404:
399:             return { kind: "TARGET_INVALIDATED", detail: `task ${target.id} no longer exists` };
400:           case 409:
401:             if (code === "version-mismatch") return { kind: "STALE_REFERENCE" };
402:             if (code === "invalid-transition") return { kind: "PRECONDITION_FAILED" };
403:             return { kind: "EXECUTION_ERROR", detail: code ?? "conflict" };
404:           case 400:
405:             return { kind: "EXECUTION_ERROR", detail: code ?? "bad request" };
406:           default:
407:             return { kind: "EXECUTION_ERROR", detail: `task service responded ${response.status}` };
408:         }
409:       };
410:       // A 5xx is not a confirmed refusal: the action may have been applied.
411:       if (response.status >= 500) {
412:         throw new Error(`task service responded ${response.status} for ${requestBody.capability}`);
413:       }
414:       return { ok: false, failure: failureFor() };
Prompt for AI Agents
P1 F90. Pinned replay skips unfinished clarification writes fixedapps/inspector/src/host.ts:539-566 · logic · data-integrity

The host pins the interpreted turn before dispatch, but writes the clarification event afterward only when !pinned. A crash or append failure between those writes leaves a durable question in the turn record without a corresponding pending obligation in context. Redelivery reuses the turn and permanently skips the missing write.

R5 interrupts recordEvent on obligation-opened, reopens the file host, and redelivers the same observation. The returned turn still contains its question, but the next frame has no pendingQuestion. This loses the interaction exactly at the restart boundary the milestone claims to cover. The same ordering also leaves a gap for interrupted resolution events.

Fix direction. Persist deterministic obligation transitions with the pinned plan, including prior question identity, then idempotently complete those transitions on both first delivery and replay. Do not call the model again to reconstruct the missing transition.

Evidence. R5 in host-review.test.ts injects the append failure after pinning and checks the following frame after restart and redelivery.

Source at finding commit
Relevant source
539:         if (!pinned) {
540:           // Record the obligation this turn leaves open, or close the one it
541:           // answered. The event is durable evidence; the frame carries it back
542:           // to perception on the next turn.
543:           if (question !== undefined) {
544:             recordObligationEvent(
545:               config.context,
546:               "obligation-opened",
547:               config.interactionId,
548:               input.now,
549:               {
550:                 questionId: `${config.interactionId}:question:${input.observationId}`,
551:                 awaiting: "disambiguation",
552:                 question,
553:                 candidates: frame.candidates.map((candidate) => cloneRef(candidate.ref)),
554:               },
555:               knownRefs(runtime.state(), input.refs ?? []),
556:             );
557:           } else if (previousQuestion && (commands.length > 0 || answer !== undefined)) {
558:             recordObligationEvent(
559:               config.context,
560:               "obligation-resolved",
561:               config.interactionId,
562:               input.now,
563:               previousQuestion,
564:               knownRefs(runtime.state(), input.refs ?? []),
565:             );
566:           }
Prompt for AI Agents
P1 F91. Shared context imports another interaction’s clarification fixedapps/inspector/src/host.ts:278-295 · logic · correctness

findPendingQuestion looks up events by entity references but never filters evidence.interactionId. Its signature does not even accept the current interaction. Entity overlap is not interaction identity, and the shared semantic store can contain history for more than one interaction.

R9 records another interaction’s open question for a shared task, then runs the current host with that task in context. The current frame receives "Other user secret request" as its pendingQuestion. This mixes discourse and can disclose unrelated interaction content to the model. Per-host file paths in the demo conceal the shared-store case.

Fix direction. Make obligation lookup explicitly interaction-scoped. Apply that scope before bounded paging so unrelated events cannot crowd out the current interaction’s state. Preserve shared entity knowledge without borrowing another interaction’s discourse.

Evidence. R9 in boundary-review.test.ts uses the supported in-memory shared context store and two distinct interaction identities.

Source at finding commit
Relevant source
278: export function findPendingQuestion(
279:   store: SemanticContextStoreContract,
280:   refs: EntityRef[],
281: ): FramePendingQuestion | undefined {
282:   if (refs.length === 0) return undefined;
283:   const events = store.eventsFor(refs, { limit: 128 });
284:   const resolved = new Set<string>();
285:   for (const event of events) {
286:     const detail = event.detail;
287:     const questionId =
288:       isRecord(detail) && typeof detail.questionId === "string" ? detail.questionId : undefined;
289:     if (!questionId) continue;
290:     if (event.kind === "obligation-resolved") {
291:       resolved.add(questionId);
292:       continue;
293:     }
294:     if (event.kind !== "obligation-opened" || resolved.has(questionId)) continue;
295:     const awaiting =
Prompt for AI Agents
P2 F92. An unrelated query silently consumes an open question fixedapps/inspector/src/host.ts:557-565 · logic · correctness

The host resolves previousQuestion whenever the current turn emits any command or an answer. It has no accepted semantic relationship establishing that this turn answered, rejected, or superseded that particular obligation.

R10 opens an ambiguous completion request and then asks a separate status query about Docs refresh. The query is answered, but the following frame has lost the original unresolved clarification. Ordinary conversation cannot safely interleave with a pending question under this rule.

Fix direction. Resolve only an explicitly identified obligation through an accepted answer, rejection, or supersession transition. Preserve pending clarification across unrelated queries and requests; use the same typed transition plan for persistence and replay.

Evidence. R10 in host-review.test.ts verifies the unrelated query does not consume the earlier question.

Source at finding commit
Relevant source
557:           } else if (previousQuestion && (commands.length > 0 || answer !== undefined)) {
558:             recordObligationEvent(
559:               config.context,
560:               "obligation-resolved",
561:               config.interactionId,
562:               input.now,
563:               previousQuestion,
564:               knownRefs(runtime.state(), input.refs ?? []),
565:             );
Prompt for AI Agents
P2 F93. Relation observations never reach the configured host fixedapps/inspector/src/host.ts:397-404 · logic · correctness

The host reads observeRelations by casting each authority to a shape with an optional method. The HTTP adapter instead returns observeRelations beside authority and executor at adapter.ts:472. The demo and tests pass only adapter.authority, so the method the host probes is absent.

R6 asks about a task with blockedBy data and inspects its actual frame. The task attributes contain the raw blocker IDs, but candidate.relations is empty. The demo’s grounded answer reads the attribute fallback, making the output look correct while the promised relation evidence channel remains disconnected.

Fix direction. Wire relation observation through an explicit typed provider contract or a declared authority extension used consistently at both ends. Test the frame’s relation evidence, source authority and version provenance, not only the rendered answer.

Evidence. R6 in host-review.test.ts requires the blockedBy relation in the actual host-built frame.

Source at finding commit
Relevant source
397:       const relations = (
398:         authority as {
399:           observeRelations?: (refs: EntityRef[]) => Promise<RelationObservation[]>;
400:         }
401:       ).observeRelations;
402:       if (typeof relations === "function") {
403:         const observations = await relations.call(authority, [ref]);
404:         if (observations.length > 0) ingestRelationObservations(config.context, observations);
Prompt for AI Agents
P2 F94. Stored continuation commands escape semantic replay validation fixedapps/inspector/src/turn-store.ts:126-134 · logic · data-integrity

The accepted-turn decoder recompiles the stored semantic turn but compares only the propose subset of record.commands. Other command kinds need only a string kind. A confirm, cancel, or revise command can therefore disagree with the stored interpretation and still be accepted for replay.

R11 records a no-op "Thanks" turn while a transaction awaits confirmation, appends a structurally valid confirm command to that turn’s JSONL record, and reopens the host. The decoder accepts the inconsistent record. This is a codec and replay-consistency finding, not a claim that the application must withstand an attacker with arbitrary filesystem control. The existing recompilation guarantee now covers only part of the durable execution plan.

Fix direction. Recompute and compare the complete deterministic command plan from the saved frame and semantic turn, including continuation commands and ordering. Reuse the unified compiler from F87 and reject inconsistent stored commands; cryptographic signatures are unnecessary for this consistency check.

Evidence. R11 in host-review.test.ts expects reopening to reject the injected continuation. The reproduction checks decoder acceptance, not execution of the altered command.

Source at finding commit
Relevant source
126:   if (record.commands !== undefined) {
127:     if (!Array.isArray(record.commands) || record.commands.some((command) =>
128:       !command || typeof command !== "object" || typeof (command as { kind?: unknown }).kind !== "string")) {
129:       throw new Error("accepted turn commands are malformed");
130:     }
131:     const propose = record.commands.filter((command) => command.kind === "propose");
132:     if (encodeValue(propose) !== encodeValue(expected.commands)) {
133:       throw new Error("accepted turn typed commands do not carry the compiled proposals");
134:     }
Prompt for AI Agents
P1 F95. Confirmation still ignores partial and unverified exclusions fixedpackages/perception/src/proposals.ts:1057-1072 · logic · correctness

The replacement compiler checks only whether subtracting accepted exclusions leaves zero refs. If any ref remains, it emits confirm for the original transaction and revision. A confirm command cannot carry the smaller target set, so this still authorizes the excluded frozen target. The branch also never checks unvalidatedExclusionRefs.

N1 constructs a confirmation for two gated targets with an accepted sibling exclusion for one; the planner returns confirm. N2 uses a sibling exclusion whose decision is not accepted; the planner again returns confirm. These are exported compiler contract tests with supplied semantic turns, not claims that the current built-in graph emits these exact combinations. The original one-target exclusion probe passes, but it covered only the empty-set case.

Fix direction. Compare against the complete frozen obligation target set. If an accepted adjustment changes it, revise and require the new authorization or clarify; never confirm the old revision. Reject confirmation when an unvalidated exclusion affects any frozen target, and verify summary refs agree with what confirmation will actually execute.

Evidence. N1 and N2 in round3/host-review.test.ts. Follow-up to F87; the original exclusion and frame-identity reproductions pass.

Source at finding commit
Relevant source
1057:         // A sibling exclusion constrains a confirmation exactly as it
1058:         // constrains a proposal.
1059:         const remaining = proposition.refs.filter(
1060:           (ref) => !analysis.excludedRefs.has(entityRefKey(ref)),
1061:         );
1062:         if (remaining.length === 0) {
1063:           rejected.push({ index, reason: "an accepted adjustment leaves no targets to confirm" });
1064:           adjustmentWithoutTargets = true;
1065:           return;
1066:         }
1067:         commands.push({
1068:           kind: "confirm",
1069:           commandId: commandIdFor(proposition, index),
1070:           transactionId: obligation.transactionId,
1071:           intentRevision: obligation.revision,
1072:         });
Prompt for AI Agents
P1 F96. Accepted citations still substitute for a valid prerequisite fixedpackages/perception/src/proposals.ts:902-911 · logic · correctness

continuationIssue checks whether a dependsOn predecessor has accepted citations, but never checks whether the predecessor itself passed command evaluation. evaluateTurn already records rejected prerequisites for proposals; the new continuation loop ignores those results.

N3 supplies a structurally valid prerequisite that compileSemanticTurn rejects for lacking an executable capability, then a confirmation depending on it. The whole-turn planner nevertheless emits confirm. A dependency can therefore be rejected by one part of the supposedly shared compiler and accepted by another. This is the same distinction between accepted evidence and a valid executable proposition that earlier reviews required.

Fix direction. Use one ordered validity result for every operation kind. A continuation must depend on a valid predecessor, including its structural, prohibition, evidence and operation checks. Do not infer prerequisite validity from citations or rerun a smaller dependency checker.

Evidence. N3 in round3/host-review.test.ts proves the predecessor is rejected before asserting that its dependent must not be emitted.

Source at finding commit
Relevant source
902:   for (const relation of proposition.relations ?? []) {
903:     if (relation.kind !== "dependsOn") continue;
904:     if (relation.targetIndex >= index) return "forward dependencies are not an executable form";
905:     const related = turn.propositions[relation.targetIndex];
906:     if (!related) return "malformed proposition: relation target is missing";
907:     const relatedAccepted =
908:       decisionsAccepted(related, acceptedDecisions) &&
909:       citationIssue(related, turn, acceptedDecisions, tracesById, tracesAvailable) === undefined;
910:     if (!relatedAccepted) return "a dependency proposition was not accepted";
911:   }
Prompt for AI Agents
P1 F97. Corrections bypass certified frame evidence fixedpackages/perception/src/proposals.ts:1084-1105 · logic · correctness

The correction branch constructs slots and emits revise after citation checks without applying expectedVersionsFromFrame or verifying that every binding ref has frame evidence. The evaluated proposal results above this branch are not used for corrections. Moving the code into proposals.ts has not made the evidence rule shared.

N4 supplies a correction with an accepted citation and a target absent from the certified frame. The planner emits revise for that target. A downstream authority or kernel may reject an unknown entity, but that is not proof that the semantic selection was grounded in this frame. The public compiler should reject this before issuing any command.

Fix direction. Apply shared binding validation and frame coverage to correction slots and argument refs, including exclusion and slot consistency checks. Carry the certified versions through the revision path where required rather than substituting later observations. Keep kernel validation as a second boundary.

Evidence. N4 in round3/host-review.test.ts expects no command for a correction target missing from the frame. This is a compiler-contract reproduction, not a demonstrated execution of a nonexistent service task.

Source at finding commit
Relevant source
1084:       const declaredSlots =
1085:         proposition.slots && proposition.slots.length > 0
1086:           ? proposition.slots
1087:           : [{ slot: "targets", refs: proposition.refs }];
1088:       const slots = declaredSlots
1089:         .map((slot) => ({
1090:           slot: slot.slot,
1091:           refs: slot.refs.filter((ref) => !analysis.excludedRefs.has(entityRefKey(ref))),
1092:         }))
1093:         .filter((slot) => slot.refs.length > 0);
1094:       if (slots.length === 0) {
1095:         rejected.push({ index, reason: "an accepted adjustment leaves no targets to correct" });
1096:         adjustmentWithoutTargets = true;
1097:         return;
1098:       }
1099:       commands.push({
1100:         kind: "revise",
1101:         commandId: commandIdFor(proposition, index),
1102:         ...(obligation !== undefined ? { transactionId: obligation.transactionId } : {}),
1103:         expectedIntentRevision: proposition.targetIntentRevision,
1104:         slots,
1105:       });
Prompt for AI Agents
P2 F98. Unrelated commands still consume a clarification fixedapps/inspector/src/host.ts:559-563 · logic · correctness

The fix preserves pending questions across queries, but still resolves previousQuestion whenever commands.length is nonzero. Emitting a command does not establish that the current turn answers or supersedes the pending question. The new durable transition faithfully persists that incorrect inference.

N5 opens "Complete the failing one", then submits a separate accepted request to pause Docs refresh. The pause succeeds, but the following frame has lost the unresolved completion question. The original query probe passes; the broader obligation lifecycle remains incomplete.

Fix direction. Have the accepted turn explicitly name the obligation it answers, rejects or supersedes. Derive and pin a resolved transition only from that relationship; preserve the question across independent work. Apply the same rule when a command is rejected at runtime.

Evidence. N5 in round3/host-review.test.ts checks a successful unrelated request and then the next frame. Follow-up to F92, whose query-specific reproduction is fixed.

Source at finding commit
Relevant source
559:             if (previousQuestion) {
560:               obligations.push({ kind: "resolved", question: previousQuestion });
561:             }
562:           } else if (previousQuestion && commands.length > 0) {
563:             obligations.push({ kind: "resolved", question: previousQuestion });
Prompt for AI Agents
P1 F99. The replacement durable writer can acknowledge corrupt or unsynced state fixedapps/task-service/src/service.ts:280-299 · logic · data-integrity

The new writeDurable calls writeSync once and ignores its returned byte count. A partial write is then fsynced and renamed over the state file, and the service returns success. Its directory-sync catch also suppresses every error, including real I/O failures, while the module claims successful responses are durable against power loss.

N6 injects a legal short write at the filesystem boundary. The action returns HTTP 200 and the persisted file is truncated invalid JSON. N7 injects EIO specifically for directory fsync; the service still returns 200. These are deterministic fault-injection tests, not an assertion that an actual power failure was exercised. Staging in-memory state fixes the earlier pre-write fault but does not make this replacement writer complete.

Fix direction. Write all encoded bytes with a full-write loop or a suitable complete-write API before rename. Handle zero progress and failures explicitly. Do not swallow genuine directory I/O errors; define supported durability semantics and keep post-rename uncertainty from being mistaken for a clean rollback or a durable success. Reuse the repository’s established file persistence patterns where applicable.

Evidence. N6 and N7 in round3/durability-review.test.ts use a real temporary file while injecting partial write and directory EIO behavior. Preserve the earlier failed-write staging control.

Source at finding commit
Relevant source
280:     const temporary = `${file}.tmp`;
281:     const fd = openSync(temporary, "w");
282:     try {
283:       writeSync(fd, content);
284:       fsyncSync(fd);
285:     } finally {
286:       closeSync(fd);
287:     }
288:     renameSync(temporary, file);
289:     try {
290:       const directoryFd = openSync(directory, "r");
291:       try {
292:         fsyncSync(directoryFd);
293:       } finally {
294:         closeSync(directoryFd);
295:       }
296:     } catch {
297:       // Directory fsync is not available everywhere; the file fsync above
298:       // still establishes the operation boundary for process crashes.
299:     }
Prompt for AI Agents
P1 F100. The 404 path still accepts an unverified refusal fixedpackages/adapter-http-tasks/src/adapter.ts:423-430 · logic · correctness

The stricter response decoder handles malformed 2xx and most unrecognized errors correctly, but its 404 branch returns TARGET_INVALIDATED without validating the response body. An arbitrary proxy or malformed 404 therefore becomes a definitive failure despite the stated requirement for contract-valid refusals.

N8 lets the real service apply a pause and replaces the reply with plain-text HTTP 404. The service task is paused while the kernel marks its effect FAILED. This is the same uncertain-outcome failure mode as the original malformed-success reproduction, now isolated to the remaining unchecked status branch.

Fix direction. Require the service’s expected refusal payload on 404 as on other statuses. An unreadable, mismatched or unrecognized response must preserve UNKNOWN and the reconciliation receipt. Add controls for genuine task-not-found and malformed/proxy responses.

Evidence. N8 in round3/host-review.test.ts checks both applied service state and the expected UNKNOWN effect. Follow-up to F89; the original malformed-200 probe passes.

Source at finding commit
Relevant source
423:       // Only a recognized, contract-valid refusal is a confirmed failure.
424:       // Any other status or body (5xx, unknown codes, unreadable payload) is
425:       // an unconfirmed outcome and must not be reported as a refusal.
426:       const code = isRecord(body) && typeof body.error === "string" ? body.error : undefined;
427:       if (response.status === 404) {
428:         return {
429:           ok: false,
430:           failure: { kind: "TARGET_INVALIDATED", detail: `task ${target.id} no longer exists` },
Prompt for AI Agents
Original baseline walkthrough

Useful integration milestone with reproduced correctness and recovery failures.

Review of implementation and architecture against the original semantic-runtime requirements.

Authority observations → immutable frame → bounded interpretation → semantic turn
  proposal → whole-turn compiler → kernel
  continuation → inspector language planner → kernel   [F87: divergent checks]

Pinned turn → commands → obligation event   [F90: interrupted replay gap]
Kernel → HTTP adapter → service state + operation record → file
  malformed response / failed persistence   [F88, F89: false certainty]
Original architecture assessment and scope limits

The direction is right. A service-owned task version, conditional action, idempotent operation key, and reconciliation endpoint are a useful real boundary. The service remains independent of Circe; the adapter translates its protocol. The interpretation graph asks bounded questions over the frame and adds no English clause parser. Keep those choices.

The implementation is not ready to merge. The largest architectural regression is language-plan.ts becoming a second compiler with a smaller safety contract. The correction and continuation paths bypass or discard constraints that the proposal path already enforces. Consolidating these paths will remove complexity and defects together.

Clarification state also needs a firmer owner. Scanning entity-indexed events to infer a pending question makes persistence order, interaction identity, and resolution meaning implicit. A small typed obligation transition in the durable host plan is enough. This does not call for a new orchestration framework or a rewrite of the kernel.

The service and adapter need a precise distinction between confirmed refusal and missing outcome evidence. An HTTP reply is not automatically a confirmed outcome. Stage durable state changes together and preserve UNKNOWN whenever application cannot be established. These are ordinary integration contracts, not semantic-model problems.

The demo is useful integration coverage, but both sides of its HTTP protocol were authored together and its semantic answers are scripted. The 494-test total proves the existing suite passes. It does not establish general language understanding, fault tolerance at all append boundaries, or production readiness. The independent probes deliberately provide correct accepted decisions and still expose implementation failures.

P1 and P2 are only partially covered. The graph selects one capability for all included candidates; it does not represent multiple distinct operations in a compound request. Unsupported mixtures need safe abstention or clarification until that scope is implemented.

Transaction pause/resume in language and non-reference argument filling remain deferred, as the author states. Task.resume is a domain capability and is not transaction resumption.

Queries currently render selected task facts. This is grounded output, but it is a narrow query facility. Durable clarification exists on the successful scripted path, with the lifecycle gaps documented above.

P3 reusable belief publication, P4 deliberation and P7 scale remain open. P6 live semantic evaluation and outcome calibration remain future evidence. Missing TypeSafe credentials are not a blocker for this code review and are not counted as a finding.

The real task adapter is a useful P5 reference integration. This review does not establish independent third-party interoperability or production durability. Retain its narrow service boundary and add adversarial protocol and restart tests before expanding scope.

Original changed files
ARCHITECTURE.md+6 −4
README.md+32 −5
apps/inspector/package.json+3 −0
apps/inspector/src/host.ts+216 −15
apps/inspector/src/language-plan.ts+176 −0
apps/inspector/src/real-cli.ts+266 −0
apps/inspector/src/real-integration.test.ts+334 −0
apps/inspector/src/turn-store.ts+28 −0
apps/task-service/package.json+23 −0
apps/task-service/src/index.ts+2 −0
apps/task-service/src/main.ts+46 −0
apps/task-service/src/seed.ts+38 −0
apps/task-service/src/service.test.ts+165 −0
apps/task-service/src/service.ts+417 −0
apps/task-service/tsconfig.json+8 −0
docs/SEMANTIC-CONTEXT-MODEL.md+2 −0
docs/SEMANTIC-PERCEPTION.md+39 −9
packages/adapter-http-tasks/package.json+25 −0
packages/adapter-http-tasks/src/adapter.test.ts+251 −0
packages/adapter-http-tasks/src/adapter.ts+473 −0
packages/adapter-http-tasks/src/index.ts+1 −0
packages/adapter-http-tasks/tsconfig.json+8 −0
packages/context/src/types.ts+14 −0
packages/perception/src/index.ts+1 −0
packages/perception/src/interpret.test.ts+463 −0
packages/perception/src/interpret.ts+466 −0
packages/perception/src/proposals.ts+23 −0
pnpm-lock.yaml+40 −0
Copy verified-state handoff · Ctrl+`
Copy re-review · Ctrl+Space