Circe architecture review · second pass

Keep the direction. The execution guarantees still need work.

Transactional acceptance is a real improvement. Plans, target ownership, stop and completion still have concrete correctness defects.

Verdict

This pass is materially stronger, but I would not call the foundation reliable yet. The remaining work extends beyond RPC lifetime, subscriptions and real-device parity. Several claimed invariants are only partially enforced. A bigger planner or more passing tests will not resolve these specific failures.

I would keep the interaction/operation model, browser connector and native accessibility adapters. Finish a node-owned executor that owns the accepted target, cancellation, observations and completion evidence. Planning should submit proposals to this executor, with the same validation for shortcuts and recovery.

The review covers the uncommitted working tree on e0f1301ccb1ae2860442d60c68c4f4e058fd9280. Previous review: foundation follow-up. Original requirements: voice and desktop design. Line references describe this working tree, not the old committed versions.

What improved

  • Acceptance now groups the request claim, operation and interaction update in one transaction against the stored revision. The earlier approval revision mismatch has an implementation-level repair.
  • A mismatched preferred node is refused, and the accepted target is passed into the mission. Explicit profile disconnection is handled. This improves routing but does not establish a fixed execution session.
  • Desktop and connector missions register around their main execution and clear in a finalizer. The normal action loop checks stop before effects. The remaining race is before registration, with additional stop-result inconsistencies.
  • Request-to-operation linkage and restart reconciliation now exist. Recovery validates a prefix instead of filtering out broken prerequisites.
  • Completion categories, shortcut history and a bounded no-progress rule are present. Their predicates and evidence need the repairs below.

Findings in priority order

P1

1. Plans ignore their own conditions

A plan carries precondition and postcondition strings, but neither is evaluated. The runner only compares a whole-screen digest. A clock change can allow the next step even when typing failed to produce the requested field value. resolvePlanStep also discards the requested role when no role matches, so a textbox request can resolve to a button.

Evidence: A direct call to the production runner with an empty Search value and a changing Clock value applied type, click Go, type, click Go. The requested “Search value is cats” condition never held. This was an in-memory example, not a live desktop run.

Change: Use a small typed condition set for observable facts: target exists with required capabilities, field value equals, checked state, URL, dialog visibility, and artifact evidence. Evaluate prerequisites before each effect and its specific result afterward. A missing role or capability must fail resolution. Allow model judgment for conditions that cannot be checked directly, with explicit evidence and an unknown result.

packages/circe-core/src/computerUse.ts:619, 967, 1022; apps/server/src/circe/Layers/CirceRecoveryPlanner.ts:94, 127

P1

2. Compound commands can bypass verification

inferComputerExpectation classifies every command starting with click, press, tap, hit or select as an action goal, before checking for save/export. When the step selector says done, any clicked/pressed history line is sufficient. It never checks that the activation matches expectation.target and bypasses the verifier.

Evidence: Production runComputerUse returned status=done and verified=true for “Click File, then export the document as a PDF” with initialHistory=[“clicked unrelated-control”], no artifact, and a supplied verifier that returns false.

Change: Limit action completion to an unambiguous single-action goal. Store a structured action result tied to the intended target and observation instead of matching prose history. Compound goals retain their final state or artifact requirement. Keep unknown completion explicit.

packages/circe-core/src/computerUse.ts:707, 1116

P1

3. Stop still has a before-start race and inconsistent outcomes

Acceptance commits before the mission registers for cancellation. requestStop refuses unregistered IDs; interrupt then treats !isActive as stop confirmation. The executor can register afterward and act without a stop flag. Separately, stopWork records status=stopped even if awaitSettled times out. Its fallback to findRunningTask is not restricted to coding/conversation despite its comment, so cancelling a pending lookup/device question can stop unrelated provider work.

Evidence: Direct invocation of the real cancellation registry: requestStop=false, derived stopConfirmed=true, then register, then isCancelled=false. The timeout status and unrelated-task fallback are source findings.

Change: Cancellation must latch from acceptance onward under the operation owner. Distinguish not-started, running, stop-requested and terminal states. Confirm stop only from executor settlement or a cancelled-before-start transition. Route all entry points through that owner; cancelling a pending question must not search for unrelated work.

apps/server/src/circe/Layers/CirceInteraction.ts:1097, 1110, 1119, 1451; apps/server/src/circe/Layers/CirceMissionCancellation.ts:62; apps/server/src/circe/Layers/CirceComputerUse.ts:563

P1

4. Retries can conceal restart reconciliation

findRequest refreshes an operation only while it is not settled. Startup reconciliation marks interrupted operations outcome-unknown but leaves the cached request result as accepted. Retrying that request returns the old accepted result. There is also a settlement-to-request-cache crash window.

Evidence: Source trace: acceptOperation stores an accepted result; reconcile saves outcome-unknown; findRequest sees a settled linked operation and returns stored. No database or live restart was performed.

Change: For linked requests, always hydrate the authoritative operation, including terminal and outcome-unknown states. Do not let cached mutable state override the linked row. Preserve the same operation ID without replaying its effects.

apps/server/src/circe/Layers/CirceInteraction.ts:401, 418, 1381

P1

5. Browser targeting remains mutable during a mission

The interaction accepts node, surface and optional profile, but does not resolve a concrete browser instance/tab. The adapter checks an optional pinned tab at startup; its runtime then sends only profileLabel. ConnectorServer chooses a connection again for every call and operates that connection’s mutable attached tab. With no preferred profile, disconnecting the selected connection can redirect later calls to another. Another attachment can change the tab even with a profile specified.

Evidence: Source inspection confirms snapshot/apply receive neither the accepted tab nor a connection generation. The initial tab check is useful but does not enforce ownership throughout execution.

Change: Resolve and persist the actual target before acceptance. Acquire a session bound to connection instance/generation and tab; every observation and effect must validate it. For desktop use, bind the application/process/window and observation identity. Serialize conflicting missions that share keyboard, focus or a connector attachment. Fail clearly on disconnect or target loss.

apps/server/src/circe/Layers/CirceInteraction.ts:827; apps/server/src/circe/Layers/CirceBrowserConnectorUse.ts:98, 167; apps/server/src/circe/browserConnector/connectorUseRuntime.ts:108; apps/server/src/circe/browserConnector/ConnectorServer.ts:128, 343, 356

P1

6. Recovery still reuses IDs across observations

The semantic planner resolves role/name afresh, but ComputerRecoveryStep still contains elementId. Queued recovery steps are checked against each new tree only for ID membership. If the same path now denotes another control, the new control passes validation. A fresh observation reference does not restore the old ID’s meaning.

Evidence: Calling validateRecoveryStep for p0:1 against a new example tree where p0:1 is Delete returns a valid click. Downstream fresh-tree expectations protect the new Delete control, not the old intended control.

Change: Use one semantic step representation for planning and recovery. Bind IDs only for the immediate observation/action pair. If a recovery step cannot be resolved semantically on the next observation, discard the dependent suffix and replan.

packages/circe-core/src/computerUse.ts:521, 537, 938, 1078; apps/server/src/circe/desktopUse/desktopUseRuntime.ts:67

P1

7. Accepted work still belongs to the submitting RPC

dispatchDevice awaits the entire mission inline. Durable acceptance exists, but its execution lifetime still follows the request. The client helper exposes submit/read/interrupt with no live interaction subscription. These are admitted gaps, and they directly affect controlling a desktop from a phone that disconnects or sleeps.

Evidence: Source confirms inline yield of the mission. I did not disconnect a real client. Server subscription exists, but the reviewed client operations module does not expose it.

Change: Have the authenticated node own a supervised operation worker. Return the accepted operation promptly, latch cancellation before dispatch, and settle state even when the submitting connection disappears. Clients subscribe with an initial snapshot and revision handling, recover by reading durable state, and never replay speech on reconnect. Do not automatically replay uncertain physical effects after node restart.

apps/server/src/circe/Layers/CirceInteraction.ts:891; packages/circe-client-runtime/src/operations/circe.ts:25; apps/server/src/circe/Layers/CirceWsRpc.ts:501

P2

8. Platform readiness still rejects native Mac and Windows control

readiness only considers linux-x11 and linux-wayland as ready backends. Mac/Windows observers and actuators therefore cannot make the interaction’s computer route ready, even if available. Browser connector readiness also bypasses the Headless controlAllowed gate.

Evidence: These are explicit conditions in production source, independent of the lack of Mac/Windows device evidence.

Change: Derive readiness from declared and observed capabilities for each supported backend, then apply preset policy to every route including connectors. Keep Linux as the first verified target; Mac and Windows should remain experimental until their real acceptance runs pass.

apps/server/src/circe/Layers/CirceInteraction.ts:537, 549; apps/server/src/circe/desktopUse/DesktopDriver.ts:406

Voice admission needs one more contract check

The report says admission keys on the provider’s audio item. The reviewed client event type carries startMs, not an explicit audio item ID. The controller treats a timestamp as identity, and its null-timestamp fallback includes currentInputTurnStartMs === null, which can reopen admission on every delta. Transcript reduction replaces same-start fragments, which requires the producer to send cumulative revisions, not incremental chunks.

Do not claim provider-independent deduplication until the actual transport contract establishes those semantics. Normalize producer events into explicit session/utterance identity, fragment mode and commitment. Run revised-transcript and null-timestamp cases through the real transport adapter. This is a source-level contract concern; no microphone session was recorded in this review.

packages/circe-client-runtime/src/circe/liveVoice.ts:111, 214; liveVoiceController.ts:838

The next implementation order

1. Node owns workAcceptance, cancellation from acceptance onward, supervised execution, settlement, current retry results, reconnect read/subscription.
2. Bind the targetOne concrete browser session/tab or desktop process/window. Reject target loss and conflicting control.
3. Enforce the goalTyped prerequisites and results, required role/capability matching, one step format for plan and recovery, structured action evidence.
4. Prove the user flowLive voice and remote existing-app control on Linux, then the same acceptance scenarios on Mac and Windows.

A short plan remains useful for reducing model calls. Its speedup is conditional on correct local checks. Evaluate each step after the previous result becomes observable; an unrelated visual change must never satisfy its condition. Use event-driven waits where adapters expose them and bounded settling where they do not. Keep the model free to plan and interpret ambiguity while the host enforces target ownership and truthful outcomes.

Prefer adding these guarantees to the existing owner over adding separate ledgers, planners or verification services. One owner should decide whether the accepted operation may act and when it has ended.

Evidence and release acceptance

I invoked the current production helpers and runner directly under Node with in-memory observations. This independently reproduced the wrong-role match, ignored plan conditions, false completion, reused recovery ID acceptance and cancellation registry race. These checks did not use the supplied regression tests. They do not establish microphone, browser, OS input or remote transport reliability.

Actual runner result:
Goal: Click File, then export the document as a PDF
History: clicked unrelated-control
Verifier supplied: false
Result: {status: "done", verified: true, steps: 1}

Actual cancellation registry sequence:
requestStop before registration: false
Derived stopConfirmed: true
Register accepted operation
isCancelled after registration: false

I did not run the reported 1231 tests or six typechecks, launch desktop/browser automation, modify Circe source, start or stop its server, or open its database. I cannot confirm the cause of the stopped server on port 3773 or the migration state from the pasted report. The report’s Mac/Windows results remain unit-test claims.

The next acceptance run should demonstrate these user outcomes, with actual observations and operation state retained:

  • “Check the weather” → city answer continues the same interaction and returns weather.
  • “Open YouTube and search for…” uses the intended existing profile and shows matching results.
  • A native calculator goal completes from both a fresh state and an already-satisfied state.
  • A disposable document is edited and saved, then reopened to verify its content.
  • Stop before start and during planning/input causes no later effect. Cancelling a weather question leaves unrelated coding work alone.
  • A phone disconnect/reconnect returns the same operation and current outcome without duplicate effects or replayed speech.
  • Changing connector attachment, disconnecting a profile, or moving focus to another app cannot redirect the mission.
  • A failed type prerequisite prevents Save; an unrelated clock/spinner update does not count as success.

Run these first on Linux with live voice and existing apps. Treat Mac and Windows as experimental until the same behaviors are observed on real nodes. Test counts and package typechecks are useful engineering checks, but they do not replace these outcomes.