LFG main service architecture
One backend for sessions, tasks, lasting chats, and event-driven agents
Decision and system boundary
Make LFG the shared service for manual requests, scheduled work, and incoming messages. The native app, WhatsApp, and email all use this service.
Keep Sessions, Tasks, and Chats as the app's main concepts. Add connections and triggers as supporting configuration, rather than another top-level conversation concept.
Build logical service modules within the existing TypeScript LFG host. Separate processes only where persistent connections or agent execution require them. Avoid a fleet of new microservices.
Use a thin Baileys WhatsApp connector. Reuse existing Claude/Codex execution adapters and Cua tooling. OpenClaw and Hermes are references and optional future adapters, not required dependencies.
This consolidated draft replaces the earlier Hermes-first recommendation. It incorporates the later decisions about LFG ownership, existing chats, Markdown memory, and event triggers.
“Main service” means a common application contract and authority. It does not require moving every LFG host or every agent into one process.
Domain model: what each object means
| Object | Meaning | Important relationships |
|---|---|---|
| Agent profile | Assistant identity, instructions, runtime defaults, permitted capabilities | One profile can participate in several chats |
| Chat | Lasting discussion and its memory scope | Links participants, channel bindings, and execution sessions |
| Session | Provider conversation, transcript, and execution context | Can serve a chat, a task run, or direct work |
| Task | Saved work instructions and execution settings | Has one or more time, event, or manual triggers |
| Trigger | Condition that starts work | References its task, connection, filters, and context policy |
| Connection | Authorized external account | Owns credentials, subscription state, sync cursor, and health |
| Event | Durable record of an incoming change or due occurrence | Retains source account, conversation, sender, and message identity |
| Run | One accepted unit of work | Links origin, session, optional task, outcome, and result destinations |
| Delivery | One intended reply, notification, or result publication | Tracks destination and delivery status separately from execution |
| Space | Desktop resource | Assigned temporarily through exclusive control leases |
Do not redefine a session as one message. A session can contain many turns and can resume after its process stops.
Start with one active execution session per chat. Keep the chat stable if an explicit reset, provider change, or recovery later creates another session.
A group and a private owner chat can use the same agent profile without sharing transcripts or private memory. Profile identity does not grant access across chats.
Generalize scheduled tasks incrementally. Existing task-run records remain the authority for scheduled occurrences. Add a common run reference that points to existing records or new event executions.
Do not require every interactive message to become a saved Task. Direct requests and chat turns can create execution records without task definitions.
Services exposed to the app and connectors
This plan proposes the following logical interfaces. Existing routes remain compatible; new names describe the intended contract rather than implemented endpoints.
| Service | Responsibility | Operations exposed |
|---|---|---|
| Identity and policy | Authenticate users/connectors and determine access | Pair identity, resolve principal, check capability |
| Sessions | Manage direct and resumable agent conversations | List, create, continue, inspect, stop, fork |
| Tasks and triggers | Save work and determine when it starts | Create/edit task; configure time/event trigger; pause; run now |
| Chats | Preserve discussion identity and route messages | List, open, submit message, bind channel, inspect history |
| Connections | Maintain email/WhatsApp accounts and subscriptions | Connect, disconnect, select monitored scopes, view health |
| Event admission | Save, deduplicate, and classify incoming work | Admit event, inspect decision, replay failed processing |
| Execution | Choose a runner and track bounded work | Start, observe, cancel, record outcome |
| Memory | Read/write scoped Markdown and build context | Remember, recall, correct, forget, inspect source |
| Actions and approvals | Enforce capability grants and action approval | Propose, approve, reject, expire |
| Delivery and notifications | Publish results through the right interfaces | Reply, notify, stream progress, deep-link, inspect delivery |
| Resources and artifacts | Manage hosts, desktops, files, and captures | Acquire/release Space, view artifact, request takeover |
The app's Sessions screen consumes Sessions and Execution. Tasks consumes Tasks, Triggers, and run history. Chats consumes Chats, Memory, and Execution.
Connection and monitoring settings show linked accounts, selected groups/mailboxes, active rules, subscription health, and recent decisions. They need no new primary navigation destination.
WhatsApp and email adapters call the same application operations after authentication and normalization. They do not access native session internals or duplicate memory logic.
External participants receive a narrow connector identity. They do not inherit the native app owner's authority merely because they can message the bot.
Keep one command path for submitted work and one authorized event stream for state updates. Reuse LFG's existing event replay where compatible.
Three ways work begins
Manual work. A user starts a session, sends a chat message, or chooses Run now. Create a request identity, validate authority, then submit through the owning execution path.
Scheduled work. The existing scheduler claims a due occurrence and submits its task snapshot. Preserve finite schedules, occurrence budgets, retry lineage, pause behavior, and existing outcome checks.
Event-driven work. A connector saves an event. Match its account and conversation against enabled triggers. Apply deterministic filters before any optional model classifier.
A trigger can ignore the event, record it as context, update permitted memory, start work, or request owner review. The rule defines the permitted actions.
Use explicit modes: mentions only, topic watch, or every eligible incoming message. A topic classifier receives only permitted local context and has no action credentials.
For interactive messages, continue the chat's session. For a background automation, use a separate execution session and deliver its result back to the originating chat.
Serialize submissions within one session. Different sessions can run concurrently under resource limits. Briefly batch group bursts while keeping direct commands responsive.
Use source event plus trigger/version as the logical execution identity. Give an explicit manual replay its own audited attempt. Do not repeat an action simply because its trigger changed later.
Persist the input snapshot and context references used for a run. Capture its source, sender, trigger, task revision, permission scope, session, and result destinations.
Pub/sub intake and outgoing push
There are two separate flows inside the LFG service: inbound notifications start processing; outbound notifications inform people.
| Direction | Mechanism | LFG responsibility |
|---|---|---|
| Inbound Gmail | Mailbox watch through Google Pub/Sub | Fetch changes since that account's saved cursor |
| Inbound Outlook | Microsoft Graph subscriptions and webhooks | Validate delivery and fetch required message data |
| Inbound other mail | IMAP IDLE where supported | Maintain mailbox connection and reconcile after reconnect |
| Inbound WhatsApp | Baileys persistent connection and message events | Normalize account/chat/sender identity and handle reconnects |
| Outbound app updates | Authorized event stream with replay | Keep screens consistent after reconnect |
| Outbound phone alerts | APNs through LFG's existing push path | Notify about results, failures, or required input |
| Outbound chat/email | Provider send APIs | Deliver replies or approved drafts to explicit destinations |
Gmail publishes an account address and history cursor, not the full email. Fetch all relevant changes and create message-level events. Maintain one watch/cursor per connected mailbox, rather than one watch per thread.
A persistent Pub/Sub subscriber can run on the LFG host. Add public webhook ingress only when the selected provider requires it or a cloud relay improves availability.
Gmail watches need renewal. Delivery can lag or fail, so retain reconciliation timers. Normal agent work starts on events; maintenance timers repair subscriptions and gaps.
Source: Gmail push documentation.
Graph supports mailbox change subscriptions and HTTPS webhooks. Queue admitted notifications before lengthy processing, and renew subscriptions through the connector lifecycle.
Sources: Outlook notifications, webhook delivery.
IMAP IDLE watches a selected mailbox over an open connection. Baileys exposes connection and message events for the paired WhatsApp account. The latter remains unofficial integration with account and protocol risks.
One WhatsApp account connection can route many groups and contacts. For each message, preserve both the conversation and its actual sender. For email, use the connection plus provider thread ID; never group solely by subject.
Persist delivery intentions separately from run completion. A completed agent answer does not prove WhatsApp delivery or an email send. Track app alert delivery separately too.
A phone notification points to a run, chat, or approval. The app fetches authoritative state when opened. Notification receipt must not be a prerequisite for server execution.
Approval buttons submit an authenticated command bound to the exact action and its expiry. Notification text or a group participant's reply cannot grant owner authority.
Memory, execution, and permissions
Memory. Keep curated memories in Markdown as agreed. Use separate folders for owner-private, group/contact, and explicitly shared project context.
Each chat has recent messages, a compact working summary, and retrieval from authorized notes and history. Keep original source references so summaries do not become the only evidence.
Serialize writes per memory scope and use atomic replacement. Record source and date, supersede changed facts, and support Remember, Edit, and Forget. A search index remains rebuildable from permitted source content.
Resolve scopes before retrieval. Opening a group chat in the owner's private app preserves that chat's group scope. Starting a separate private discussion can grant broader context explicitly.
Operational persistence. Keep existing session and task stores during the first phase. Add a durable inbox, processing claims, subscription checkpoints, and delivery outbox behind a separate operational-store interface.
Recommend SQLite for the new operational queue and deduplication ledger, where atomic claims and related updates matter. This does not move curated memory into a database. This storage choice remains a draft decision.
Do not mirror provider transcripts into a competing writable store. LFG owns message admission and execution metadata; provider adapters retain their canonical transcript ownership.
Execution. Keep agent reasoning independent of desktop allocation. API, CLI, and MCP tasks need no Space. Acquire a compatible desktop only when the work needs a screen.
Use existing host runners and Cua adapters. Respect session ownership: an externally owned session remains read-only unless the supported continuation or fork path grants control.
Assign one controller per desktop. Human takeover pauses the agent and retains the lease until the user finishes. Enforce the existing design's host memory reserve during desktop admission.
Permissions. Grant capabilities per principal, chat, connection, and task. Pass delegated agents only the context and tools their work requires.
Enforce approval at the action boundary. A worker with unrestricted shell and account cookies can bypass a nominal tool broker; constrain its credentials and tools accordingly.
Approve a reply policy when binding each channel. It can permit ordinary assistant replies in that chat.
For email drafts and account actions, follow the current design's inline owner approval. Any automatic-send policy must be explicit and bounded.
The Cua prototype documents automatic in-Space tool approval. UI approval cards alone cannot enforce this plan; execution adapters need actual action restrictions.
Deployment and recovery
Start with one authoritative LFG host for assistant orchestration. Use existing additional hosts as execution workers. Do not require centralized hosting for ordinary existing sessions.
Assign each chat, connection, and task an owner host. Route commands to that owner. Keep imported/native sessions discoverable through current fleet behavior.
A selected host can own multiple mailboxes and the assistant WhatsApp account. Do not connect the same stored WhatsApp credentials from two competing connector processes.
Keep identity, routing, state, and policy within the LFG service. Run connection adapters under its existing lifecycle or as supervised children. Reuse existing maintenance and recovery mechanisms.
Optional cloud ingress validates webhooks and queues notifications while the owner host is offline. It does not provide desktop execution or a WhatsApp connection unless those components actually run there.
Persist before acknowledging provider delivery. Deduplicate transport notifications and source messages separately. Advance cursors after discovered work reaches durable state.
Recover queued work after restart. Classify first-time mailbox history as context import unless the owner explicitly enables historical processing.
Use bounded retry, backoff, and an inspectable failed-event queue. Stop and surface uncertain external mutations rather than blindly replaying them.
Automatic host failover requires an explicit lease and credential handoff. Defer it initially. A disconnected host must show queued/unavailable status instead of pretending work has started.
Apply backpressure and limits per account, chat, trigger, and host. A busy group must not exhaust all execution capacity or classifier spend.
Show source-event receipt, filter decision, run state, permission waits, and delivery result as distinct evidence. Reuse the shared session-state resolver rather than inventing another busy heuristic.
Suggested run states: queued, starting, running, waiting for input/resource, succeeded, incomplete, failed, and cancelled. Waiting does not mean success.
Build sequence, acceptance, and evidence
| Phase | Deliverable | Acceptance gate |
|---|---|---|
| 1. Shared contracts | Ownership, identity, work admission, origin metadata, run references | Existing sessions and finite scheduled tasks behave unchanged |
| 2. Lasting chats | Stable chat IDs independent of Space allocation; Markdown memory | Restart and session replacement preserve chat identity and memory scope |
| 3. WhatsApp interface | Existing bot account, selected groups, shared submission path | Two groups remain isolated; app and WhatsApp share each group's chat |
| 4. Event-triggered tasks | Durable inbox, filters, classifiers, trigger definitions | Repeated events launch one logical run; ignored events are inspectable |
| 5. Email connections | First Gmail mailbox, then a second; Outlook/IMAP as needed | Independent cursors and thread routing; reconnects and expiry recover |
| 6. Delivery and push | Replies, drafts, app events, APNs, approval links | Results reach the right destination; app state survives a missed alert |
| 7. Delegated desktop work | Resource leases, constrained actions, Cua execution | Concurrent agents cannot share cursor control or bypass approval |
Reuse existing push in phase 1 where possible. Phase 6 extends it to chat and event-run results; it is not a new APNs stack.
Use a complete vertical slice early: mention the bot in a group, run one permitted MCP action, view progress in LFG, and return a result.
Then add an email-triggered draft, a scheduled group summary, and a desktop task. Verify all three use the common execution and delivery contracts.
Test duplicate delivery, crashes between steps, overlapping rules, out-of-order events, bot reply loops, stale approvals, missing host capacity, and cross-group memory attempts.
Observed existing code. Sessions expose direct/closed conversation state and ownership. Scheduled tasks have time schedules and run records. The host exposes event replay and APNs device registration. Push code watches session transitions independently of app connections.
Design/prototype work. The newer chat design separates lasting threads from a shared Space pool. The older Cua Message prototype still binds messages and agent references to a Space. Treat the separation as work to implement.
Proposed additions. Channel bindings, account connectors, event intake, event triggers, generalized run references, scoped memory operations, delivery outbox, and enforced action permissions.
The current task model requires schedules. Its run trigger supports only scheduled and manual starts. The plan does not claim event triggers already exist.
Local source inspection:
- Session and task models: src/sessions.ts, src/scheduled-tasks.ts, src/scheduled-task-api.ts.
- API and push paths: src/commands/serve.ts, src/push/watcher.ts, src/push/apns.ts.
- Chat design: .codex/design/cua-agent-threads/revision-brief.md and cua-agent-model.md.
- Cua prototype: .worktrees/cua-message/src/cua-message.ts and docs/cua-message.md.
This is an architecture draft, not an implementation or deployment. No product files, databases, account connections, schedules, or running services changed.