Omi Agent Control Plane and Subagent Architecture Specification
Status: Locked for phased implementation Date: 2026-06-23 Phase 1 implementation baseline:desktop/macos/agent TypeScript runtime
Normative terms: MUST, MUST NOT, SHOULD, and MAY are requirements levels.
This document is the long-term source of truth for Omi’s agent control plane and subagent architecture. Future implementation plans and tickets should reference this document instead of redefining the same session, run, adapter, event, and migration semantics.
1. Executive decision
Omi is the agent control plane. ACP, pi-mono, Hermes, OpenClaw, AgentVM, A2A agents, and future runtimes are execution adapters beneath it. The canonical product object is an OmiAgentSession. Every user turn or delegated objective is an Omi AgentRun. Every concrete execution or retry of that run is a RunAttempt. Runtime-native session IDs are opaque AdapterBinding data and are never Omi session IDs.
Phase 1 MUST evolve the existing desktop/macos/agent TypeScript runtime into the first omi-agentd. It MUST NOT begin with a Rust rewrite or a separate new service.
The implementation MUST introduce a durable runtime kernel above the existing harness abstractions. The current HarnessAdapter interface is a useful transitional driver API, but it is not the control-plane boundary and MUST be demoted below the kernel.
2. Non-negotiable invariants
task_id,session_id,run_id,attempt_id, and adapter-native session IDs are distinct identifiers.- An Omi session ID never changes because the selected adapter changes, an adapter process restarts, a native session becomes stale, or execution moves to another runtime node.
- Every accepted user query creates exactly one
AgentRun. - Every time control is handed to an adapter for that run, the kernel creates a
RunAttempt. - Retries and failover create new attempts under the same run; follow-up user messages create new runs in the same session.
- UI objects do not own process, session, or lifecycle authority. UI state is a projection of kernel state.
- Exactly one runtime attempt has execution authority for a run at a time.
- A runtime-native session ID exists only in
adapter_bindingsand compatibility fields explicitly namedadapterSessionIdorlegacyAdapterSessionId. - Silence, unchanged terminal output, UI dismissal, or process disappearance never means success.
- Run completion is determined only by a terminal kernel state persisted by the control plane.
- Cancellation acknowledgement means the kernel accepted and dispatched the cancellation request; it MUST NOT falsely claim that an adapter stopped unless the adapter confirmed or the worker terminated.
- Omi long-term memory and agent-session history remain separate systems. Agent output may produce memory candidates, but agents do not directly establish canonical memories.
- Phase 1 MUST NOT create another in-memory session authority alongside the kernel.
3. Pre-Phase-1 findings and Phase 1 baseline
Before Phase 1, the desktop agent implementation had several independent lifecycle authorities:desktop/macos/agent/src/index.tsowns ACP process state, a session-key map, one active session, one active abort controller, and recursive retry behavior.PiMonoAdapterowns syntheticpi-session-*identifiers and process-global conversational state.AgentBridge.swiftowns a Node process and one uncorrelated message continuation.- Each
TaskChatStatecreates a separateAgentBridge, persists an ACP-native ID asacpSessionId, and treats it as the continuity identity. - Each floating pill creates a separate
ChatProviderand therefore a separate Node process to gain parallelism. TaskAgentStatusRegistryand pill state maintain separate in-memory status authorities.
- Outbound messages lacked request, run, attempt, and event correlation.
- Starting a new ACP query aborts the prior global active query.
- ACP retries recursively call
handleQuery, hiding whether a fresh execution occurred. - ACP process exit clears all in-memory session mappings.
- pi-mono advertises session resume even though its synthetic sessions do not survive a process restart.
- Task chat stored adapter-native ACP IDs in message rows and had no canonical Omi session identity.
- Cancellation is an uncorrelated notification with no acknowledgement contract.
- Parallel floating pills achieve isolation by multiplying UI-owned processes rather than using a scheduler and worker pool.
desktop/macos/agent: AgentRuntimeKernel
owns canonical session/run/attempt lifecycle, AgentStore persists that lifecycle in SQLite, ACP and pi-mono
execute through RuntimeAdapter, Swift uses AgentRuntimeProcess plus projection stores rather than owning
runtime authority, and local control tools operate through the kernel. Runtime-native IDs remain adapter bindings
or explicitly named compatibility fields; they are not Omi session IDs.
4. Target architecture
4.1 Process boundary
Phase 1 uses one long-lived Node process per signed-in desktop app instance. Swift owns that process through a sharedAgentRuntimeProcess; individual AgentBridge values become lightweight client handles.
The Node daemon may own multiple bounded adapter subprocesses. Consolidating lifecycle authority into one daemon does not require forcing every run through one underlying model process.
4.2 Worker pools
The adapter registry MUST support bounded worker pools because the current ACP and pi-mono implementations are effectively single-flight per process.- A worker handles at most one active attempt at a time in Phase 1.
- ACP bindings MAY share an idle worker sequentially, but concurrent prompts require separate workers until the ACP implementation supports safe multiplexing.
- A live pi-mono binding MUST be pinned to one worker because pi-mono state is process-global and switching session keys currently restarts or replaces process state.
- UI surfaces MUST NOT create or own workers.
- When all workers are busy, the kernel queues runs rather than spawning without limit.
- The initial global worker cap SHOULD match the current practical desktop parallelism ceiling. Use eight active workers by default, configurable through
OMI_AGENT_MAX_WORKERS.
5. Decision: kernel versus HarnessAdapter
5.1 Required design
Phase 1 MUST introduceAgentRuntimeKernel above the current HarnessAdapter interface.
HarnessAdapter MUST NOT become the canonical session or lifecycle API. It lacks:
- Omi session, run, and attempt identity;
- durable persistence;
- native-session adoption and resume fidelity;
- ordered events and replay;
- cancellation acknowledgement;
- retry/failover semantics;
- artifacts, delegations, grants, scheduling, and status projection.
5.2 Transitional implementation
Create a new lower-level interface:PiMonoAdapterMAY remain unchanged initially and be composed by aHarnessAdapterShimthat implementsRuntimeAdapter.- ACP process and JSON-RPC logic MUST be extracted from
index.tsinto anAcpRuntimeAdapter. - Existing
HarnessAdaptertypes MUST be explicitly documented as adapter-native. RenamePromptResult.sessionIdtoadapterSessionIdwhen call sites are migrated. index.tsMUST become bootstrap and transport wiring, not contain runtime-specific lifecycle branches.
6. Canonical domain model
6.1 AgentSession
A durable, user-visible conversational or work identity. A session can contain many runs and can acquire multiple historical adapter bindings.
Examples:
- one backend chat conversation;
- one task chat identified by
task_id; - one floating-pill investigation;
- one delegated research session.
6.2 AgentRun
One accepted unit of user or parent-agent intent in a session. Every compatibility query(...) invocation creates a new run.
6.3 RunAttempt
One concrete execution of a run by one adapter worker. Attempts make retries, stale binding replacement, authentication restarts, and failover explicit.
6.4 AdapterBinding
The opaque relationship between an Omi session and a runtime-native session. Bindings have generations and resume fidelity:
native: the runtime can resume its own durable state;reconstructed: Omi can reconstruct sufficient context from canonical data;none: native state does not survive worker/process loss.
native. Current pi-mono bindings are none.
6.5 AgentEvent
An ordered lifecycle, output, tool, progress, usage, approval, artifact, or delegation event.
6.6 Artifact
A concrete result such as a report, file, patch, screenshot, structured output, or checkpoint.
6.7 Delegation
A durable parent-to-child relationship connecting parent and child sessions/runs.
6.8 Grant
A recorded capability decision scoped to a session or run. Phase 1 records the existing legacy permission behavior without requiring a new approval UI.
7. Identifier format
Use UUIDv4 generated by the TypeScript runtime and prefix each identifier:ses_<uuid>run_<uuid>att_<uuid>evt_<uuid>bind_<uuid>art_<uuid>del_<uuid>grant_<uuid>
8. Phase 1 SQLite storage
8.1 Database location and engine
Swift MUST pass an explicit state directory to the Node daemon. The database file is:node:sqlite DatabaseSync API behind a small AgentStore interface. Do not add a native SQLite npm dependency in Phase 1.
Because Node’s node:sqlite API is still under active development across Node 22 minor versions, startup and packaging MUST include a runtime probe using the exact bundled Node binary. The probe MUST import node:sqlite, create an in-memory DatabaseSync, apply the Phase 1 schema, run a transaction, and fail loudly if the bundled runtime does not support the required API or SQL features. Do not rely only on a generic “Node 22+” version check.
Initialize:
8.2 Complete minimal schema
8.3 Intentionally omitted Phase 1 tables
Do not add separate tables for messages, approvals, model profiles, runtime nodes, workspaces, leases, or checkpoints in Phase 1.- Completed text and structured results are stored on
runs. - Streaming and tool history are stored in
events. - Checkpoints are artifacts with
role = 'checkpoint'. - Runtime-node placement is recorded on each attempt.
- Approval requests are events; legacy decisions are grants.
8.4 Startup reconciliation
On daemon startup:- Open and migrate the database.
- Mark every attempt in
starting,running,waiting_input,waiting_approval, orcancellingasorphanedunless its adapter can prove the execution is still alive. - Mark its run
orphanedif no other non-terminal attempt exists. - Clear persisted
adapter_instance_idvalues because worker instance IDs are process-local. - Mark active
resume_fidelity = 'none'bindings stale. - Preserve native-resumable ACP bindings as active but unpinned.
- Emit durable reconciliation events.
9. Run and attempt semantics
9.1 Creation boundary
When a query is accepted:- Resolve or create the canonical session.
- Insert one run with
status = 'queued'. - Emit
run.queued. - Schedule execution.
- Immediately before adapter-specific binding/session work begins, insert attempt 1 with
status = 'starting'.
9.2 When to create another attempt
Create a new attempt under the same run when:- an adapter-native resume fails and execution is retried with a fresh binding;
session/set_model,session/prompt, or equivalent fails due to a stale native session and the kernel retries;- authentication causes the adapter process/session to be rebuilt and the run is re-entered;
- the selected worker crashes and the run is retried;
- execution fails over to another adapter, model, runtime node, or checkpoint;
- the user explicitly invokes a future
runs.retry(run_id)operation.
- internal network retries hidden inside an adapter while the same adapter execution remains authoritative;
- a follow-up message, which is a new run;
- UI reattachment or event replay;
- tool calls inside the same model turn.
9.3 Retry rules
- Only one non-terminal attempt may exist per run.
- Attempt numbers are monotonically increasing from 1.
- A failed attempt records
retryable,retry_reason, and any error. - The next attempt records
resume_from_attempt_idand optionallycheckpoint_artifact_id. - Run usage and cost are the sum of all attempts, not only the successful attempt.
- The run becomes terminal only after success, cancellation, timeout, or retry exhaustion.
- The current recursive
handleQuery(msg)retry behavior MUST be replaced with an explicit loop in the kernel.
10. Adapter-binding lifecycle
10.1 Resolution order
For a run using adapterA:
- Find the active binding for
(session_id, A). - Confirm it is compatible with required
cwd, system-prompt hash, and adapter constraints. - If compatible, use or resume it.
- If no binding exists and a compatibility request supplies
legacyAdapterSessionId, adopt that ID as binding generation 1. - If native resume fails, mark the binding stale and create the next generation.
- If the runtime cannot resume, create a fresh native binding and record the declared resume fidelity.
10.2 ACP
- ACP native IDs are stored only in
adapter_bindings.adapter_native_session_id. - Successful
session/resumeestablishes or revalidates anativebinding. - A stale native session failure creates a new attempt and a new binding generation.
- Process restart does not delete the Omi session or run history.
10.3 pi-mono
- Current synthetic
pi-session-*IDs are opaque process-local binding IDs. PiMonoAdapter.supportsFeature(SESSION_RESUME)MUST returnfalsein Phase 0/1.- pi-mono bindings use
resume_fidelity = 'none'until true persistence or reconstruction exists. - On worker restart, the prior binding becomes stale and a new generation is created.
- A pi-mono worker may have only one live pinned binding in Phase 1.
11. Event model
11.1 Canonical event envelope
11.2 Required event types
Lifecycle:11.3 Persistence policy
- Persist all lifecycle, tool terminal, artifact, usage summary, input/approval, and delegation events.
- Stream token deltas immediately, but persist coalesced text chunks no more frequently than every 100 ms.
- Compact coalesced chunks into
message.completedafter a message finishes. - Do not normalize or persist private chain-of-thought. Adapter thought streams become safe progress summaries or remain compatibility-only transient UI data.
- Event
cursorisevents.event_seqfor durable events. Ephemeral deltas carry the latest durable cursor plus an in-memory sequence.
12. Cancellation contract
12.1 Kernel API
12.2 Required behavior
- Persist
run.cancellation_requestedand set the run tocancelling. - Invoke
RuntimeAdapter.cancelAttemptfor the active attempt. - Record whether cancellation was dispatched and whether the adapter explicitly acknowledged it.
- Return
cancel_ackto Swift immediately after the kernel transaction and dispatch result. - Mark the run
cancelledonly when the adapter execution exits, confirms cancellation, or the worker is terminated after a bounded grace period. - Drop or quarantine late adapter events after a terminal attempt state.
session/cancel notification. Therefore Phase 1 reports dispatchAttempted = true and adapterAcknowledged = false unless the ACP runtime provides a confirming response. Kernel acknowledgement remains truthful.
The compatibility facade MAY return partial text for a cancelled query, but the canonical terminal status remains cancelled, never succeeded.
13. Exact AgentBridge.query(...) compatibility facade
13.1 Swift source compatibility
Keep the existing public call shape and add optional canonical fields. Existing callers continue compiling.resume remains temporarily supported but MUST be renamed in internal protocol data to legacyAdapterSessionId.
QueryResult becomes:
sessionId preserves old adapter-native behavior for unconverted call sites. New code MUST use omiSessionId.
13.2 Shared Swift process owner
AgentBridge.start() MUST no longer spawn its own Node process. It registers a client handle with a shared AgentRuntimeProcess actor.
AgentRuntimeProcess owns:
- one Node daemon process;
- stdin/stdout/stderr pipes;
- request-to-stream routing;
- tool-call routing;
- auth event routing;
- process restart and health;
- a subscription stream for canonical events.
AgentBridge owns:
- a stable
clientIdfor the lifetime of the handle; - a default adapter ID derived from its existing
harnessModeinitializer; - at most one active request for source compatibility;
- callbacks for its active request.
13.3 Session resolution order
For every compatibility query, the facade resolves a session in this order:- Use
omiSessionIdif supplied and valid for the current owner. - Else resolve by
surfaceRefusing(owner_id, external_ref_kind, external_ref_id). - Else resolve by legacy alias
(owner_id, legacy_client_scope, sessionKey). - Else create a session and persist the supplied surface or legacy alias.
sessionKey is never an adapter-native session ID. It is only a compatibility alias.
13.4 Stable surface references
The three initial surfaces MUST supply:| Surface | surface_kind | External reference |
|---|---|---|
| Main chat | main_chat | chat:<backend-chat-session-id> or chat:default |
| Task chat | task_chat | task:<task_id> |
| Floating pill | floating_pill | pill:<pill-uuid> |
13.5 Wire protocol v2
Inbound query:13.6 v1 compatibility window
For one release, the Node facade SHOULD accept current v1query, interrupt, warmup, and invalidate_session messages.
- v1
idmaps torequestId. - v1
sessionKeymaps to a legacy alias. - v1
resumemaps only tolegacyAdapterSessionId. - v1 messages use the process default adapter.
- The v1 path still creates canonical sessions, runs, attempts, and events.
- Do not maintain a separate v1 session map.
14. Existing task-chat acpSessionId migration
14.1 Interpretation
Existingtask_chat_messages.acpSessionId values are adapter-native ACP session IDs. They MUST NOT be copied into sessions.session_id and MUST NOT be reinterpreted as Omi IDs.
14.2 Migration strategy
Use lazy backfill on first task-chat access or first new task query:- Resolve or create one canonical Omi session using
external_ref_kind = 'task'andexternal_ref_id = task_id. - Read the latest non-null
acpSessionIdfrom existing task messages. - If the selected adapter is ACP and the canonical session has no ACP binding, insert that ID as adapter binding generation 1 with
resume_fidelity = 'native'. - Attempt native resume as attempt 1.
- If resume fails, mark the binding stale and create a fresh binding in attempt 2. The canonical Omi session remains unchanged.
- Older distinct ACP IDs remain legacy history in message rows. Do not import every historical value as an active binding.
14.3 Transitional writes
For one rollback-compatible release:TaskChatStatestoresomiSessionIdandlegacyAcpSessionIdseparately in memory.- New logic uses
QueryResult.omiSessionIdfor canonical continuity. - Existing
acpSessionIdmessage writes MAY continue usingQueryResult.adapterSessionIdonly when the active adapter is ACP. - Never write an Omi session ID into the
acpSessionIdcolumn. - After the compatibility release, stop writing
acpSessionId; retain the column read-only until a later database cleanup.
agentSessionId column is required in the task-message database because the canonical mapping is durable in omi-agentd by task_id. A future cloud/cross-device migration may add a canonical session reference to the action-item model.
The current action-item chatSessionId = task.id marker is a separate legacy scheduler/deduplication field. It MUST NOT be repurposed to store an Omi agent session ID.
15. Canonical status projection
The kernel is the only status authority.15.1 Swift projection store
Add oneAgentRuntimeStatusStore or equivalent observable projection fed by canonical events. It indexes:
15.2 Surface migration
ChatProvider.isSendingbecomes a local presentation cache derived from its active run and MUST be reconciled from kernel events.TaskAgentStatusRegistrybecomes a compatibility view over the projection store, then is removed.AgentPill.statusbecomes a projection of the pill session’s latest run.- Dismissing a pill detaches/archives the view and explicitly cancels its active run only when current product behavior requires cancellation; it does not terminate the daemon.
- Closing task chat does not determine run completion.
- A task-agent run completing does not automatically mark the user’s action item complete.
16. Delegation APIs and tools
16.1 Kernel operations
The kernel should expose operations equivalent to:16.2 Implementation decision
Phase 1 did not build capability-manifest code generation in the first persistence/kernel slice. Implement the tools as thin, hand-written TypeScript wrappers against the kernel in one module with one set of Zod schemas. They MUST NOT be independently reimplemented in Swift, MCP, and each adapter. Required sequence:- Stabilize session/run/attempt/event APIs.
- Implement
list_agent_sessions,get_agent_run,cancel_agent_run, andinspect_agent_artifacts. - Implement
send_agent_message; it creates a new run in an existing session. - Implement
delegate_agentafter child session/run/delegation creation is transactional and the scheduler supports it. - Generate MCP, Swift, documentation, and permission metadata from a capability manifest in the later capability-consolidation phase.
list_agent_sessions, get_agent_run,
cancel_agent_run, inspect_agent_artifacts, send_agent_message, and delegate_agent. send_agent_message
creates a follow-up run in an existing Omi session. delegate_agent creates or continues a distinct child Omi
session linked to a parent run and records the delegation through kernel-owned state.
16.3 Delegation semantics
call: create a child session/run and suspend the parent until a structured result is available.spawn: create an asynchronous canonical child session/run and immediately return handles. This is not the legacy floating-pillspawn_agenttool and does not create or manage floating UI.continue: create another run in an existing child session.
16.4 Delegation versus floating-pill tools
delegate_agent is the canonical control-plane delegation path. It creates or continues an Omi child
AgentSession, AgentRun, and Delegation through kernel-owned state.
The existing spawn_agent and manage_agent_pills tools remain the floating-bar UI workflow for now. They start and
manage circular floating agent pills and MUST NOT be documented as aliases for canonical delegation. Conversely,
delegate_agent spawn mode returns canonical child handles and MUST NOT imply that a floating pill appears.
Until a later UX migration, models should choose explicitly:
- use
delegate_agentfor canonical child runs that need durable session/run/delegation tracking; - use
spawn_agentfor the legacy floating-pill product surface and other-app/background UI workflow; - use
manage_agent_pillsonly to list, dismiss, or clear floating pill UI state.
17. Phase 0 permission policy
Phase 0 MUST NOT be blocked by a redesign of approvals.17.1 Preserve behavior, isolate semantics
Create a namedLegacyPermissionPolicy and route existing permission decisions through it.
For the initial release it MAY preserve:
- ACP auto-selection of the currently used allow option, including
allow_alwayswhen that is the existing behavior; - existing pi-mono tool policy;
- the legacy tmux path’s permission bypass until that path is retired.
- every automatic ACP decision MUST emit an
approval.resolvedor equivalent audit event; - once the store is present, each run MUST receive an explicit
legacy_defaultgrant record describing the broad legacy authority; - new adapters MUST explicitly opt into this policy rather than inheriting it accidentally;
- no code outside the policy module may make ad hoc approval decisions;
- the product MUST label this mode as legacy/high-trust internally.
17.2 Dangerous semantics that remain
The following remain dangerous and are deliberate temporary debt:- selecting
allow_alwayscan create broader runtime state than a run-scoped Omi grant represents; --dangerously-skip-permissionsbypasses runtime checks in the legacy tmux path;- user-installed pi-mono extensions execute inside the adapter subprocess trust boundary.
18. Model and context policy
18.1 Logical model profiles
Product code SHOULD move from provider-specific model IDs to logical profiles:model_profile and requested_model_id when available.
18.2 Context boundary
- A delegated child receives minimal explicit context, not the complete parent transcript by default.
- Omi memories are retrieved through scoped Omi tools/MCP.
- Agent outputs may create
memory_candidateartifacts with provenance and uncertainty. - The memory ingestion pipeline decides whether a candidate becomes a canonical memory.
- Canonical memories created from agent work record the originating run ID.
19. Standards and future adapters
These standards remain edge protocols, not Omi’s internal domain model:- ACP: interactive coding-harness adapter.
- MCP: capability and context plane.
- A2A: remote/federated agent adapter.
- Hermes: use its richer native gateway adapter where needed; ACP is a compatibility option.
- OpenClaw: preserve the distinction between native subagents and ACP harness sessions.
- AgentVM: becomes another runtime node and placement target.
20. Smallest implementation sequence
Step 0 — protocol and compatibility scaffolding
- Add protocol v2 fields:
protocolVersion,requestId,clientId, canonical IDs, andcancel_ack. - Continue accepting current v1 messages for one release.
- Introduce
LegacyPermissionPolicywithout changing current user-facing permission behavior. - Set pi-mono
SESSION_RESUMEcapability to false. - Add bounded timeouts and cleanup for pending request/tool maps where possible without changing product flow.
Step 1 — durable store and domain types
- Add the SQLite schema and migration runner.
- Add typed repositories and transactional lifecycle methods.
- Add startup reconciliation.
- Add ID generation.
Step 2 — adapter extraction
- Define
RuntimeAdapter. - Extract ACP process/protocol code from
index.tsintoAcpRuntimeAdapter. - Wrap the current
PiMonoAdapterwith aRuntimeAdaptershim. - Add adapter registry, bounded workers, and single-flight enforcement per worker.
Step 3 — runtime kernel
- Implement session resolution.
- Implement run/attempt state machine.
- Implement binding generations.
- Replace recursive retries with explicit attempts.
- Persist and publish events.
- Derive canonical run status.
Step 4 — JSONL compatibility facade
- Map current
querybehavior to session resolution plus a new run. - Translate adapter events into current
text_delta, tool, thinking/progress, result, and error messages with correlation fields. - Translate current
interrupttoruns.cancel. - Keep
warmupas a scheduler hint, not session authority. - Keep
invalidate_sessionas binding invalidation, not Omi session deletion.
AgentBridge.query(...) behavior passes compatibility tests while all execution flows through the kernel.
Step 5 — shared Swift runtime process
- Replace per-
AgentBridgeprocess ownership with sharedAgentRuntimeProcessownership. - Route responses by
requestIdinstead of one global continuation. - Preserve one active request per
AgentBridgehandle initially. - Expose canonical result fields and event subscriptions.
Step 6 — canonical surface identities
- Main chat passes a stable chat surface reference.
- Task chat passes
task_id, lazily adopts the latest legacy ACP ID, and stores Omi and ACP IDs separately. - Floating pills pass their pill UUID as the external reference.
- All three surfaces subscribe to canonical status.
TaskAgentStatusRegistrybecomes a projection adapter rather than an authority.
Step 7 — delegation controls
Phase 1 baseline:- Read/control tools run against the kernel.
send_agent_messagecreates a follow-up run in an existing Omi session.- Transactional delegation and
delegate_agentcreate or continue child Omi sessions. - Artifact metadata is exposed through local kernel tools; richer product UI for artifacts remains a later phase.
21. First code files to change
The first ten files are:desktop/macos/agent/src/protocol.tsdesktop/macos/agent/src/runtime/types.ts— newdesktop/macos/agent/src/runtime/sqlite-store.ts— newdesktop/macos/agent/src/runtime/kernel.ts— newdesktop/macos/agent/src/runtime/compatibility-facade.ts— newdesktop/macos/agent/src/adapters/interface.tsdesktop/macos/agent/src/adapters/acp.ts— new extraction fromindex.tsdesktop/macos/agent/src/adapters/pi-mono.tsdesktop/macos/agent/src/index.tsdesktop/macos/Desktop/Sources/Chat/AgentBridge.swift
desktop/macos/Desktop/Sources/Chat/AgentRuntimeProcess.swift— new if not initially nested inAgentBridge.swiftdesktop/macos/Desktop/Sources/Providers/ChatProvider.swiftdesktop/macos/Desktop/Sources/ProactiveAssistants/Assistants/TaskAgent/TaskChatState.swiftdesktop/macos/Desktop/Sources/Rewind/Core/TaskChatMessageStorage.swiftdesktop/macos/Desktop/Sources/FloatingControlBar/AgentPill.swiftdesktop/macos/Desktop/Sources/ProactiveAssistants/Assistants/TaskAgent/TaskAgentStatusRegistry.swiftdesktop/macos/Desktop/Sources/ProactiveAssistants/Assistants/TaskAgent/TaskChatCoordinator.swift
index.ts MUST shrink to bootstrap, adapter registration, JSONL parsing, and shutdown. It MUST NOT retain separate ACP and pi-mono session managers after the migration.
22. First tests to add or update
22.1 TypeScript tests
-
sqlite-store.test.ts- migrations are idempotent;
- WAL/foreign-key setup is applied;
- data survives reopen;
- external-ref and active-binding uniqueness constraints hold;
- transactional state plus event writes are atomic.
-
session-resolution.test.ts- canonical ID lookup wins;
- task/main/pill external references resolve stably;
- legacy aliases resolve without becoming adapter IDs;
- owner isolation is enforced.
-
run-attempt-lifecycle.test.ts- one query creates one run and one attempt;
- stale resume creates attempt 2 under the same run;
- follow-up creates a new run;
- only one active attempt is allowed;
- usage aggregates across attempts.
-
adapter-binding.test.ts- legacy ACP ID adoption creates generation 1;
- resume failure marks it stale and creates generation 2;
- ACP binding is
native; - pi-mono binding is
noneand becomes stale after restart.
-
event-stream.test.ts- durable cursors are monotonic;
- replay after cursor is ordered;
- concurrent runs do not mix events;
- coalesced deltas compact into message completion.
-
cancellation.test.ts- cancellation request is persisted before dispatch;
cancel_ackis emitted exactly once;- dispatch and adapter acknowledgement are distinct;
- late events after terminal cancellation are ignored;
- partial text does not turn cancellation into success.
-
compatibility-facade.test.ts- current v1 query maps to canonical session/run/attempt;
resumebecomes an ACP binding only;- current outbound callbacks still receive expected message types;
invalidate_sessioninvalidates a binding, not the Omi session.
-
Update
pi-mono-adapter.test.tsSESSION_RESUMEis false;- worker binding pinning is enforced;
- abort dispatch result is represented honestly.
-
Replace
session-lifecycle.test.tsreplication tests- test the real kernel and fake adapter instead of copying
index.tscontrol flow.
- test the real kernel and fake adapter instead of copying
22.2 Swift tests
-
Agent runtime multiplexing
- two
AgentBridgehandles share one daemon; - events are routed by request ID;
- interrupting one request does not interrupt another.
- two
-
Task-chat legacy migration
- latest
acpSessionIdbecomes an ACP binding; - canonical session is resolved by task ID;
- Omi session ID is never written into
acpSessionId; - stale ACP resume retains the same Omi session.
- latest
-
Status projection
- main chat, task chat, and pill statuses follow canonical run events;
- UI close/dismiss does not fabricate terminal success;
- process restart produces
orphanedstatus rather than silent completion.
-
Protocol v2 parsing
- result exposes Omi, run, attempt, and adapter IDs separately;
- cancellation acknowledgement parses correctly;
- deprecated
sessionIdretains legacy native behavior.
23. Phase 1 acceptance criteria
These acceptance criteria describe the Phase 1 baseline contract and remain the regression guard for the merged implementation:- Every main-chat, task-chat, and floating-pill query returns durable Omi
session_id,run_id, andattempt_idvalues. - Restarting the Node daemon preserves canonical sessions, runs, bindings, events, and terminal statuses.
- ACP and pi-mono both execute through
RuntimeAdapterand the kernel. - ACP native IDs and pi-mono synthetic IDs appear only as adapter bindings or explicitly named compatibility fields.
- Current task
acpSessionIddata lazily migrates without being reinterpreted as an Omi ID. - Recursive retry logic is removed; retries create visible attempts.
- Every query-scoped outbound message is correlated by request, session, run, and attempt.
- Cancellation produces an explicit acknowledgement and a truthful terminal state.
- Multiple bridges/pills can run through one shared daemon without event mixing.
- Canonical status drives main chat, task chat, and pills; UI registries are projections only.
- Existing permission behavior remains available through a named, auditable legacy policy.
index.tsno longer contains independent ACP and pi-mono lifecycle authorities.
24. Explicitly deferred
The following are not Phase 1 blockers:- a new user-facing approval UX;
- cloud synchronization and cross-device leases;
- AgentVM placement and failover;
- Hermes, OpenClaw, and A2A adapters;
- automatic context reconstruction across adapters;
- durable pi-mono native resume;
- automatic memory ingestion from child results;
- Rust control-plane implementation;
- complete event sourcing or CQRS.
- local capability-manifest consolidation for the existing Omi control tools from one canonical source;
- artifact write/lifecycle APIs over the existing
artifactstable; - a minimal desktop artifact projection that exposes references and metadata already owned by the kernel;
- prompt and discoverability alignment that keeps canonical
delegate_agentdelegation distinct from the existing floating-pill tools.
- broad capability generation across cloud directories, remote runtimes, and future standards;
- full artifact browser UX, arbitrary artifact-content browsing, cloud artifact sync, and cross-device artifact leases.
- migrating floating pills into canonical child-session projections or replacing the legacy floating-pill tool UX.
25. Long-term direction
After Phase 1, Omi should add a cloud agent directory and relay that owns global routing, runtime registration, single-writer leases, compact status synchronization, and optional artifact synchronization. The local TypeScript daemon remains a runtime node. AgentVM becomes another node. Failover creates a new attempt from a checkpoint rather than moving a live process invisibly. The durable abstraction remains:A globally addressable OmiAgentSession, containing durableAgentRuns and explicitRunAttempts, executed by selected runtime adapters under Omi-owned grants, with ordered events and concrete artifacts.