Prioritized findings
P1 · approval1. 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 · target2. 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 · stop3. 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 · retries4. 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 · effects5. 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 · recovery6. 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 · voice7. 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 parity8. 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 semantics9. “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.