Circe architecture review · follow-up

The redesign has useful parts. Its guarantees are not connected yet.

Keep the interaction model, richer observations and browser connector. Fix target ownership, approval, cancellation and recovery before making the agent execute longer plans.

Verdict

I would not sign off on the foundation yet. This is substantial progress from the earlier design, but adding the right records and interfaces has not made their promised behavior true across the call paths. Several defects can prevent a mission from starting, stop the wrong work, select the wrong browser profile, or repeat an effect.

The next pass should finish the existing design end to end. It should not add another planner above these gaps. “Codex-level” is not a useful acceptance criterion here; completed user tasks, correct targeting, truthful results and reliable interruption are.

Reviewed the uncommitted working tree atop e0f1301ccb1ae2860442d60c68c4f4e058fd9280. The supplied agent report is evidence of what that agent says it observed; I did not independently reproduce its calculator clicks or 10-second timings. I read production source, not tests, and directly invoked pure production functions. No Circe code, live process or database was changed. No desktop/browser automation was launched.

Compare the original design and desktop requirements. This follow-up preserves that direction and identifies implementation gaps.

What improved and should stay

  • Interaction state and pending lookup questions now have server-side storage and a revision check.
  • The regular computer loop requires a grounded input target and checks cancellation again after model selection and before applying an action.
  • Linux observations carry field values and widget state; provider choices include human-readable descriptions.
  • A Chrome extension and native-messaging connector provide a route to the existing profile.
  • Provider judgment uses the existing provider registry. The loop has an explicit verification callback and bounded recovery.

Those are useful changes. Their presence does not establish shared ownership, end-to-end cancellation, cross-platform parity, or reliable completion.

Prioritized findings

P1 · approval

1. Accepting a desktop confirmation conflicts with its stored revision

The reducer advances the revision when consuming “yes.” The start-device branch passes that new state straight into dispatchDevice without saving it. Dispatch saves an accepted operation, then compares the interaction against the new revision, which is not in the database. It returns stale before executing.

Stored interaction: revision 5, pending approval
Production reducer on “yes”: revision 6, start-device
dispatchDevice CAS expects: revision 6
Database still contains: revision 5
Result: conflict; accepted operation can be orphaned

This revision sequence was reproduced by directly calling the production reducer and target-binding function. The database conflict follows from the inspected save path; no full live submission was attempted. Device corrections take the same unsaved-transition path.

Fix: atomically consume the answer and accept the operation against the original stored revision. Keep the original expected revision distinct from the new state revision. Return the persisted state after any conflict.

Source: packages/circe-core/src/interaction.ts:602; apps/server/src/circe/Layers/CirceInteraction.ts:1162, :743.

P1 · target

2. Target fields are stored but dropped before execution

preferredNodeId is declared in the submission contract but not consumed by the interaction implementation. Clients still choose the interaction node with the quick-lookup selector; direct device effects execute locally on that node. They return before the legacy device-routing branch can choose another machine.

Dispatch records preferredProfile in the operation, then sends only the goal, confirmation and request metadata to the mission. The connector mission never passes a profile to its runtime. Connector calls therefore choose an attached connection, or the first connection, afresh. A disconnect can make a later call select a different remaining profile. Session, application, window and tab references are also not bound through this mission contract.

Fix: resolve and pin the execution node, connector instance, profile, tab and session generation before acceptance. Pass that target through every adapter call. Reject a missing target; do not repeat default selection during a mission. Store the interaction owner node in the client reference too.

Source: CirceInteraction.ts:718, :754; CirceBrowserConnectorUse.ts:89; browserConnector/ConnectorServer.ts:128; web CirceVoiceRuntime.tsx:1888 and mobile CirceMobileProvider.tsx:1541.

P1 · stop

3. Stop still has competing owners and misleading confirmation

The interaction reducer sends cancel to stopWork. That function selects the most recently requested coding run on the node, rather than the interaction's desktop operation. With no coding run it can say nothing is running while a desktop mission exists. Its error handler can also manufacture a successful-looking interruption acknowledgment.

A separate interrupt method does consult the operation, but reports the cancellation registry's “flag set” result as stopConfirmed. This does not mean an in-flight action has finished or input has been released. The web/mobile voice components expose submission but do not consume the new interaction read/interrupt/subscription interfaces for their lifecycle.

The single-click shortcut and browser launch/navigation occur outside the regular loop's stop gates. A single click can perform up to 30 keyboard traversal attempts without consulting the mission cancellation flag. Successful shortcut returns also bypass the finalizer that clears that flag.

Fix: every stop targets the exact operation and bypasses the normal submission queue. Gate launches, connector navigation, shortcuts and recovery through the same executor. Confirm cessation only after the executor has stopped and cleaned up input. Retain an explicit unknown state for unreachable targets.

Source: CirceInteraction.ts:639, :961, :1221; Layers/CirceMissionCancellation.ts; CirceComputerUse.ts:243, :551, :677; CirceBrowserConnectorUse.ts:112.

P1 · retries

4. Durable storage does not yet provide durable execution

Request deduplication is recorded after the mission finishes. Acceptance generates a fresh random operation ID. A concurrent or interrupted retry can pass the empty request-cache check and create another operation. Saving an accepted row does not prevent this.

The operation is persisted as accepted and later settled, but the execution remains inline in the submit RPC. I found no startup reconciliation of unfinished operations. The interaction read/subscription interfaces exist, but the inspected voice clients do not use them to restore or follow the active operation. Some final state writes ignore revision conflicts and return the locally computed result anyway.

Fix: atomically claim the request ID and link its operation at acceptance. Retries read that operation. Run it under a node-owned lifetime, publish progress, and reconcile unfinished effects after restart without blindly replaying them. Wire clients to read and subscribe. Use existing persistence and worker patterns, without a second orchestration framework.

Source: CirceInteraction.ts · findRequest, recordRequest, dispatchDevice; persistence/Migrations/070_CirceInteractions.ts; packages/circe-client-runtime/src/operations/circe.ts.

P1 · effects

5. A shortcut can repeat an already-applied click

The single-click path activates a control and polls for any change in the scoped tree. If nothing changes, it falls through into a new model loop with an empty history. The loop can click the same control again. Conversely, any unrelated tree change can make the shortcut report success.

The general loop also rejects an already-satisfied state if it has not first applied an action. Its verification prompt always demands visible goal state, even when the user requested an action such as “click 7.” That explains why a real activation and a correct final state need different evidence.

Fix: represent action completion, desired-state completion and artifact completion separately. “Click 7 once” needs the bound activation result; “make the display show 7” needs a value predicate; “save this document” needs saved-content evidence. An applied-but-unobserved action remains uncertain, with its history retained. It is never reset to an untouched mission.

Source: CirceComputerUse.ts:677 through the shortcut return/fallthrough; packages/circe-core/src/computerUse.ts:808 and buildComputerVerificationRequest.

P1 · recovery

6. Recovery executes a batch against one old observation

The recovery validator drops invalid steps instead of rejecting their dependent continuation. A direct production-function probe supplied “type into a missing field, then click Save”; validation returned just “click Save.” The recovery executor then applies the surviving batch without capturing between steps or passing observation references.

The normal desktop observation reference is a local capture counter. It proves which cached capture the caller used, but does not detect an external layout change. Native handles are positional paths guarded mainly by role/name. Coordinate and focus-dependent fallback still need stronger checks. AT-SPI actions also bypass the low-level desktop input queue.

Fix: reject broken dependencies; execute only a validated prefix. Reobserve and bind each dependent action to the current app/window/document generation. Put semantic actions and input sequences under the same session owner. A plan's element IDs cannot outlive the observation that produced them.

Source: packages/circe-core/src/computerUse.ts · validateRecoverySteps, recover; desktopUse/desktopUseRuntime.ts; CirceComputerUse.ts:192.

P2 · voice

7. One admission function still does not mean one finalized utterance

The new commitUtterance function is useful, but every input transcript delta resets committedForCurrentTurn. A held delegation still commits the first arriving nonempty fragment. A later fragment can therefore reopen admission for the same spoken turn. Silence is inferred from transcript arrival timing rather than a confirmed end of speech.

Fix: retain one audio-turn identity across partial transcript revisions. Tie finalization to the actual provider endpoint protocol or an explicitly specified audio endpoint adapter. Deduplicate all signals against that identity, and handle late corrections as revisions. Do not turn every delta into a new turn.

Source: packages/circe-client-runtime/src/circe/liveVoiceController.ts:754, :824. This is a source-level event-order risk, not a newly observed duplicate physical action.

P2 · platform parity

8. Rich observations and native identity are not shared consistently

Linux now carries value/state. The inspected Chrome snapshot, macOS dump and Windows dump do not provide equivalent field values and widget states to the loop. The connector also maps through a preview snapshot shape that drops useful fields. The calculator repair therefore has not solved the same observation problem on all paths.

The macOS dump numbers a filtered frontmost-process list as p0, but the action script resolves p0 against the unfiltered process list. That can resolve a different application. Role/name checks may refuse it, but cannot establish application identity. Native interaction readiness still admits Linux backends only.

Fix: publish one observation contract implemented end to end by each adapter. Resolve native apps by stable process identity and windows by explicit handles. Keep unsupported platform capabilities unavailable until real-device evidence exists. “Windows needs nothing” is too broad: interactive session and privilege boundaries still apply. Microsoft's UI Automation security documentation.

Source: apps/desktop/chrome-extension/debugger-session.js; browserConnector/connectorUseRuntime.ts; desktopUse/macosAccessibility.ts:72, :97; desktopUse/windowsAccessibility.ts; CirceInteraction.ts:434.

P2 · decision semantics

9. “TypeSafe executes” describes the implementation incorrectly

In provider-first mode, a valid provider answer returns immediately. TypeSafe is asked only if that provider path yields no valid answer. The actual executor is ordinary Circe code, which validates offered keys and invokes adapters. There is no independent TypeSafe judgment of every provider action.

The fallback converts a missing or out-of-range provider confidence to 0.9. A self-reported provider confidence is not interchangeable with TypeSafe's probability output. Key membership establishes syntactic validity, not correct intent or authorization.

Fix: distinguish deterministic host validation, optional TypeSafe selection and provider planning. Do not synthesize high confidence. Missing confidence means unknown; any confidence policy needs evidence for that decision source. TypeSafe's documented role is answering typed questions from state. TypeSafe documentation.

Source: apps/server/src/circe/Layers/CirceDecisionFallback.ts:123 and decide; apps/server/src/server.ts:684.

Should the agent plan 3–6 steps ahead?

Yes to planning ahead; no to replaying a list of old element IDs. The reported per-step delay makes fewer planner round trips worth investigating. It does not explain wrong targeting, lost ownership or ambiguous completion. Those defects survive batching, and the present recovery path shows how batching can make them worse.

PlanGoal, target, subgoals, predicates
BindFresh observation and exact handles
ExecuteOne guarded action or safe sequence
InspectContinue, wait, replan or stop

Produce a short semantic plan once: find the search field, replace its text with the intended query, submit, check the results. Each step includes its precondition, desired result and retry policy. Resolve controls from the current observation when the step becomes eligible. Do not ask a large provider to choose every keypress if the target and action are already unambiguous.

Step: replace search text
Target: bound Chrome instance / profile / tab
Precondition: current document exposes one editable search field
Arguments: exact user query
Postcondition: field value equals query
Retry: inspect field first; replacing can be safe, appending is not

Step: submit search
Precondition: query value verified
Postcondition: results view belongs to that query
Retry: inspect navigation before repeating

Replan when the expected state fails to appear, a new dialog changes the task, the user corrects the goal or the planned action is unsupported. Detect repeated no-progress actions in code. Wait for a relevant change with a deadline; do not spend the whole 24-step budget asking the model to repeat itself.

A small deterministic action sequence can stay local when focus and context are stable. Crossing an app, modal dialog or navigation requires a fresh observation. Use a persistent planner session only if measured startup/context costs justify it; do not assume all of the reported 10 seconds is model reasoning.

Measure provider startup, prompt assembly, observation, inference, action, settling and verification separately. Track how many decisions are required per completed task. Optimize the dominant measured stage, not just the number of model calls.

The next implementation pass

  1. Repair acceptance and ownership. Fix the approval revision transaction, atomically claim request IDs, bind exact node/profile/session targets, and preserve owner identity through reconnect.
  2. Unify stop and all mutation paths. Route shortcuts, navigation, semantic actions, keyboard traversal and recovery through one operation executor. Distinguish requested stop from stopped execution.
  3. Give completion explicit meaning. Add typed action/state/artifact expectations and real predicates. Carry applied/unknown outcomes into recovery; never restart history after an uncertain click.
  4. Finish observation parity and handle validity. Values, selection, toggle state, focus, app/window identity and document generation must reach selectors and verification on every adapter. Correct the macOS indexing defect.
  5. Add semantic planning ahead. Plan bounded subgoals; bind and inspect locally between dependent steps. Reject invalid prerequisite chains rather than filtering them.
  6. Prove the full path on real devices. Use a direct mission runner to isolate executor failures, then use phone/web/live voice against that same interface. The direct runner is a diagnostic tool, not a replacement for product acceptance.

Do not add a second state store, second desktop owner or separate coding-provider path. Most of this work is connecting and simplifying the owners already introduced.

What would change this verdict

Real trajectoryRequired evidence
Weather → city → switch clientSame pending question and arguments, no location-only coding task.
Desktop request → yesExactly one accepted operation starts, stored revision agrees, no orphan acceptance.
Two nodes and two Chrome profilesOnly the named target changes; disconnect stops that target, without replacement selection.
“Click 7 once” versus “show 7”Action-once and state predicates behave differently and report their evidence accurately.
Stop during planning, launch, keyboard traversal and recoveryNo new mutation after cancellation acceptance at the executing gate; confirmation follows cleanup.
Retry during a running request; disconnect after SaveSame operation returned; saved state inspected before any repeat.
Edit → save → reveal in file managerCorrect document and window, saved-content check, preserved goal across app/dialog transitions.
Late transcript fragment and delegationOne committed audio turn, no fragment execution or duplicate mission.
macOS/Windows native taskActual OS run, including permission/session failure and two similar windows. No unit-test-only parity claim.

Record completion, manual intervention, latency, duplicate effects, wrong-target actions and false success independently. The supplied calculator trace establishes that one actuation path can work. It does not establish task completion, correct routing or recovery. No app crash cause was established by this review; preserve the crash trace before attributing it to the harness.

Evidence limits

Direct probes established the approval reducer's revision advance and recovery's removal of an invalid prerequisite. Other findings are source-level control-flow defects or risks with locators above. Existing tests were not treated as proof, and no live mission was run. This review does not claim that every listed failure has occurred in the user's session.

External documentation was checked on 21 September 2026. The browser connector's eventual frame handling should follow Chrome's documented target/frame model; the current snapshot implementation does not demonstrate full iframe support. Published through the Abbyplan workflow. Circe source remains unchanged.