NEXO 7.4.0 — guardian-claude-desktop lifecycle pipeline: nexo_lifecycle_event, idempotency by event_id, Desktop durable queue
Published 2026-04-22. Minor release over v7.3.0.
v7.4.0 ships the Brain-side half of the guardian-claude-desktop-plan.md pipeline. NEXO Desktop v0.24.0 now funnels every conversation-level transition through a single service, persists the intent to a durable append-only queue BEFORE any UI mutation becomes visible, calls Brain for canonical processing, and reconciles whatever was in flight on the next boot. This release is the Brain half that makes the “canonical processing” step exist. Three moving parts, plus the usual CLI + tool-map plumbing.
nexo_lifecycle_event — the MCP tool
- Contract.
nexo_lifecycle_event(event_id, action, conversation_id, session_id, reason, payload_snapshot, source, schema_version)returns a JSON ack with one ofprocessed/already_processed/accepted/rejected/retryable_error. Theevent_idis the idempotency key — re-delivery of the same id returnsalready_processedwithout re-running any side effect. This is the backbone of section 5 “Idempotencia real” of the plan. - Action set.
close/delete/archive/switch/app-exit/window-close. The first four plusapp-exitare diary-triggering;window-closeis intentionally not (Desktop runs the window-close overlay locally and defers the diary to the subsequentapp-exit). The set is frozen in code and pinned by a contract test so a rename needs a deliberate breaking migration. - Malformed input. Missing
event_id, unknownaction, or missingconversation_idreturnrejectedwith a specific reason string. The handler never silently swallows a client bug.
lifecycle_events table (m51 migration)
Single table keyed by event_id (PRIMARY KEY) with columns schema_version, source, action, conversation_id, session_id, reason, payload_snapshot (JSON), delivery_status, retry_count, created_at, processed_at, last_error. Indexed on delivery_status, conversation_id, and action so Desktop's reconciliation queries stay fast even as the ledger grows. The migration is idempotent; runs once on the first startup after the upgrade.
nexo lifecycle CLI
Two subcommands so Desktop main.js can bridge through runNexoCommand without inventing a new IPC surface:
nexo lifecycle record --event-id <uuid> --action <a> --conversation-id <id> [--session-id ...] [--payload ...]. Prints the JSON ack to stdout. Exit code 0 for terminal ok (processed/already_processed/accepted), 2 forretryable_error, 3 forrejected.nexo lifecycle status --event-id <uuid>. Prints the stored row (or{"status": "not_found"}). Used by the Desktop boot reconciler to avoid double-running canonical work.
Desktop v0.24.0 — what this release unblocks
Desktop v0.24.0 ships in lockstep and consumes nexo_lifecycle_event from three places:
- Click time. Archive/delete flows now call
ConversationLifecycleService.recordLifecycleEvent()BEFORE touching the renderer. The service writes to<NEXO_HOME>/runtime/data/lifecycle_events.ndjson, then relays to Brain. A crash between “user clicks X” and “Brain writes diary+stop” can no longer eat the intent. - Boot reconciliation. On the next app-ready, Desktop replays every non-terminal event in the local queue. For each, it asks Brain's status endpoint first: if Brain already processed it (crash between Brain-side processing and the ack reaching Desktop), the local row is marked
already_processed. If Brain has no record, the event is resubmitted through the service. - Two UX regressions corrected. The full-screen “Cerrando conversación” overlay is gone — archive/delete redirect immediately to Home with a sidebar spinner while the graceful close runs in the background. Desktop now also always opens on Home at startup instead of auto-activating the last conversation.
Test coverage added
tests/test_lifecycle_events.py (12 cases):
- first delivery marks
processedand setsdiary_triggeredper action - duplicate
event_idreturnsalready_processedeven when the second call passes a different action or conversation switchdoes NOT trigger diary- malformed action / missing event_id / missing conversation_id →
rejectedwith specific reasons payload_snapshotroundtrips through the status tool- status endpoint returns
not_foundfor unknown ids - malformed JSON payload is stored as
{_raw: ...}fallback - handler exception surfaces as
retryable_error,handler_threw: true - all four diary-triggering actions flag correctly;
window-closedoes not (by design) VALID_ACTIONSconstant is frozen to the exact plan set
Desktop side adds 29 tests across lifecycle-queue, conversation-lifecycle-service, lifecycle-ipc-contract, and lifecycle-reconciler. Full Desktop suite: 634 pass, 1 skipped, 0 fail.
What to do after updating
nexo update picks up Brain v7.4.0 automatically. The m51 migration runs on first startup; nothing else needs doing on the Brain side. Installing Desktop v0.24.0 (separate DMG) enables the full pipeline end-to-end. If you want to verify the ledger is live, archive a conversation and then inspect <NEXO_HOME>/runtime/data/lifecycle_events.ndjson and nexo_lifecycle_status <event_id>.
Related: full v7.4.0 changelog · v7.3.0 release notes · source on GitHub