Skip to main content

Persistent agents and memory

ExperimentalAvailable on: WindowsmacOSLinuxPresent in the build, unproven. May change or be removed.

What an agent that outlives its session actually requires, what Harmony has landed toward it, and what is still gated off.

A session is durable, but it is still a session: one conversation, owned by the daemon, with a beginning and an end. A persistent agent is a Harmony identity that can outlive that session, with a frozen checkpoint from which a saved agent can be reopened and selective, scoped memory it may use later. It is not a copy of a running model or a provider login.

This page exists because the term is easy to oversell. The app branch has implemented more of the path, but persistent-agent operations are off by default in current builds. The existing memory fabric is a separate feature. The distinctions below match feature status.

Four things that are not one thing

Most confusion about "persistence" comes from treating four separate mechanisms as a single switch. They are independent, and Harmony keeps them separate on purpose:

MechanismWhat it isWhat it is not
IdentityA stable agent record — who this agent is, across sessionsA provider login, or a session id
Provider sessionThe resume binding a provider CLI can prove for its own conversationProof of who the agent is
CheckpointA frozen, integrity-checked snapshot of workspace and session state at save timeThe live process, or its scrollback
MemoryScoped records admitted after trust checks or held for reviewA transcript, or anything the agent decided to keep on its own

Saving an agent means saving identity plus a frozen checkpoint. Memory extraction is a separate transaction with its own status, not a side effect the save receipt can claim as complete. A save and activation do not rewrite the original runtime event history; an explicit, confirmed delete is different and removes Harmony-owned agent data, not provider-native sessions.

What is landed today

Agent memory, with trust, scope and a sensitive-content screen. The local memory fabric is present in current builds independently of the gated persistent-agent save path:

  • Policy before recall. Memory records carry provenance, trust and scope. Pending-review and foreign records are not general recall. When the gated rehydration path is enabled, its capsule also excludes contradicted and ungranted memory. A verified, explicit user statement can be admitted under policy; an external page or document cannot promote itself by claiming to be the user and remains review-required.
  • Host-derived trust. The daemon uses its own accepted events and session state, not a request flag, a wire hint or the model's say-so, to establish trust. Content an agent reads is data, not permission.
  • Sensitive content is refused at admission. Material that screens as sensitive — credential-shaped content and the like — is blocked from memory rather than stored and filtered only at recall time.

Local memory stays on your machine by default, under the same rules as data on disk. Only a separately enabled, explicitly reviewed Cloud handoff may export a filtered subset; see what crosses to Cloud.

What is built but off

Persistent-agent save, activation and privacy operations are implemented on the app branch behind the persistentAgents.enabled feature gate, which is off in current builds. A disabled operation refuses with NALA_CAPABILITY_UNSUPPORTED: it does not save an identity, open a session or claim that memory was written. These are implemented paths, not a claim that the full app has passed a release gate.

Opening a saved agent: exact when proven, rehydrate by choice

nala.persistentAgent.activate still prepares a checkpoint-bound plan. The Desktop Open action and TUI /agents open <id> now call a separate, daemon-owned nala.persistentAgent.open path that consumes that plan through the session service, process factory and supervised Harmony worker. auto only chooses exact resume for the first-party Harmony Agent runtime when a sealed Harmony-owned Pi JSONL session reference (path, file hash, last entry, SDK session id and frozen journal watermark) validates and the actual worker opens that same file and proves it in its ready handshake. It continues that transcript; third-party provider adapters still declare exact resume false.

A tampered, missing, stale or unsupported session produces a typed offer of rehydration with no worker launched. It never silently changes mode. The TUI requires a separate y/n confirmation; Desktop offers Rehydrate in a new session / Keep saved agent. A confirmed choice is bound to the displayed checkpoint and capsule hash. Rehydration starts a fresh supervised runtime session and installs bounded latest-checkpoint and admitted, same-agent scoped memories as labeled loader context — untrusted data, not fake user text or higher-priority instructions. The receipt and appended incarnation say which mode was actually used. Offline rehydration and an eligible third-party saved-agent source can use this fresh Harmony worker; no third-party session file is read or exactly resumed. The current TUI pane remains bound to its original session: Open starts a separate worker, not a silent same-pane session switch. /resume of a paused provider session is a different command. These are branch-level paths, not a packaged app or a live-provider model-turn qualification.

Opt-in native context can include bounded root AGENTS.md or CLAUDE.md in the rehydration loader as hashed untrusted-user-authored data, default OFF. For that path the daemon now derives the workspace root from its own verified launch intent (or one unambiguous same-workspace root), not a renderer-supplied path; an ambiguous/missing root refuses. The opt-in is not applied to exact resume. No provider-native memory/session store is read or overwritten, and the importer/exporter seam remains unwired. A test-only Pi SDK proved loader retention across a turn; the production provider wire and packaged Desktop/TUI-to-worker journey remain unproven.

Save-time memory work is separate

For an opted-in memoryMode: extract save, the identity and checkpoint commit first. A separate, durable, retryable job then uses a deterministic structural extractor over bounded checkpoint objective, decision and explicit preference fields, checked against accepted events at the frozen journal watermark. It makes no model call and does not scrape a transcript; a job can finish with zero candidates. Browser/document-derived or otherwise unverified candidates, if retained, stay pending review. Only a candidate backed by host-accepted, explicit user evidence can be auto-admitted after policy checks.

The save receipt's memoryExtraction.state is deferred after a successful extract-job enqueue, not_started if the independent enqueue is not confirmed, or not_applicable for a checkpoint-only save. defer mode also reports deferred without requesting a job. None of these means "memory saved": job status (queued, leased, retry_wait, complete or abandoned) and redacted counts are available separately through owner-scoped inspect. Even a complete job can have zero committed memories.

Forget, archive, delete and export

nala.memory.forget tombstones a verified agent's memory and excludes it from future daemon recall, with a durable cache-epoch check. Retention uses explicit agent states: archive hides an agent from active selectors but keeps its data. Delete requires a separate, workspace-bound confirmation token; this second request is not independent proof of a human click. A confirmed delete scrubs Harmony-owned checkpoints, incarnations and memory bank and preserves a minimal audit marker. An entitlement lapse never implies deletion. A bounded export carries a redacted, integrity-hashed manifest of Harmony-owned identity, checkpoints and eligible memory; it excludes forgotten data, credentials and provider-native sessions. Import/restore is not implemented. Neither delete nor forget can recall text already sent to a model or securely erase historical backups.

The read-only entitlement projection can report local_core, entitled, lapsed or unknown consistently across local clients. There is no site grant reader wired at this tip, so it cannot claim an authenticated paid grant; it does not gate local Core save/activate, and a lapse never removes inspect, export or delete. A Cloud handoff check is separate. This is status plumbing, not paid enforcement.

Caller authority and the embedded browser

The daemon now gives each authenticated pipe connection its own identity and can bind a first-party agent socket after a successful daemon-verified launch handshake (nonce, durable intent/session and live-process checks). A shared pipe token or a wire callerAgentId alone is not an agent principal; unbound persistent calls fail closed with NALA_AUTHORITY_UNVERIFIED if the operator enables the gate. Stock Node does not prove the socket's OS process, and ordinary one-shot or multiplexed clients do not automatically get a bound agent identity. The gate remains off by default.

The agent-driven embedded browser backend is composable but unavailable by default. browser.embeddedBackend.enabled is false and is a second latch alongside embedded-backend selection and a qualified host invoker; missing conditions leave the agent backend unavailable. This is distinct from the existing browser surface, and does not imply an agent can drive it in current builds.

Browser actions are packet-bound, not free-form

On the app feature branch, the closed agent-browser set now includes browser_open, browser_observe, browser_click, browser_type, browser_select, browser_scroll, browser_wait and gated browser_submit. All remain off in current builds without explicit browser selection; embedded also needs the second default-off latch above. An observed visible light-DOM element ref and its short-lived generation token are required for each act, with an owned lease, fresh DOM, paired screenshot and ApprovalService check. No selector or JavaScript is taken from the model. Values for type/select come only from a host-verified, scoped and origin-bound user packet; unknown facts refuse and must be asked. A gated production packet source now resolves the exact active MAIN-approved JobPacket: ordinary approved text facts as well as human-answered revisions only after their approval. A select without a typed option still asks. Without an approved scoped packet, values fail closed, and backend selection alone cannot invent one. A refused type/select can enqueue a durable FactRequest and A2A notice. The human answer in TUI/Desktop creates a new draft JobPacket with provenance; only MAIN-reviewed approval makes it active and moves blocked(missing_fact) to prepared. An unanswered fact stays blocked; TUI alone cannot approve. Page content stays untrusted-page data. The Harness compiler separately quotes/hashes browser/download/clipboard fragments in a volatile inspector projection, not yet Pi's provider request constructor. Standalone CDP screenshot, popup denial, physical teardown/durable receipts and download quarantine are composed. An Electron 44, sandbox-on local fixture now proves embedded act/upload/download, authenticated host-pipe guest-death notice (durable lease revocation) and tiled full-page CDP pixels below the fold (38/38 specialist checks). browser.embeddedBackend.enabled stays false; that fixture is not a packaged/onscreen build, atomic capture of dynamic pages or independent screenshot OCR.

A JobPacket and packet-only PDF executor now have a Desktop review panel, MAIN sender/owned-pane confirmation and daemon active-packet authority. browser_upload execution rechecks approved packet, current lease, generation and PDF hash; a revoked packet is refused. Release hold: the worker chooses its browser_upload tool list at initialization, before a packet becomes active after browser_open; it is not yet a correct per-turn advertise/revoke contract. Staged private PDFs are retained for the exact lease so Chromium can read them at later fixture submission and cleaned on confirmed close. Downloads yield untrusted quarantine hash refs, never executable or automatically admitted memory. Positive login/passkey/MFA/CAPTCHA heuristics hand the leased page to a human; negative detection proves nothing, and the agent must not bypass a challenge. Host-verified local-user resume is a separate decision.

browser_submit remains a disarmed gated tool by default: submitArmed: false. Desktop now renders the pending field-by-field pre-submit diff, redacts sensitive values and routes its decision through the existing ApprovalService, but production still lacks a trusted host intent resolver/current-form reader/armed gate. Switching the flag or seeing a modal cannot submit. A tool return is not evidence an employer received an application. The separately gated schema-v9 ledger deduplicates attempts, records outcome_unknown without automatic retry, and requires a host-matched confirmation observation for submitted_verified; its normal production RPC still has no host confirmation artifact reader. A scripted local-fixture 20-encounter run exercised 18 unique rows, 13 fixture-verified submissions, four blocks, one unknown and two duplicate skips across a daemon kill/restart; fixture approvals/preview text are not a production user or independent employer confirmation. Neither piece makes an autonomous job-application service. Real applications require explicit user authorization. See Harmony browser agent.

Terminal controls keep the same boundaries

Harmony TUI runs real model turns. /save is a gated persistent save request, not provider /resume. Bare /agents opens the scoped saved-agent picker; /agents open <id> now calls the supervised Open path and shows exact resume or an explicit rehydration offer, then the mode actually used. It launches a separate worker and does not silently rebind the existing TUI pane. /fleet, /agents fleet and Alt+A retain the live fleet. A memory review overlay uses provenance/trust; missing RPCs say unavailable.

/browser status|handoff|release|approvals now uses a connection-bound, operator-facing browser-control RPC and the one live lease/ApprovalService; /browser resume still refuses without a host-verified local-user decision. /jobs shows application receipts, never presenting outcome_unknown as submitted. /cloud status and its Cloud status row show observed workspace state; no fresh task+workspace+agent+incarnation claim means the agent Cloud dot stays neutral. /cloud handoff|return and CLI counterparts only preview and route identity transfer/return to Desktop review/Bring Home; terminal commands cannot approve a Cloud handoff, verify an HMAC or append an incarnation. CLI harmony cloud status --json is read-only. Gates OFF yield an inert explanation, not a success receipt.

Cloud handoff continuity: what crosses, and what never does

With the separate Cloud and identity gates explicitly enabled, MAIN's owner review shows the exact agent, checkpoint, memory policy and exclusions before a repository handoff. The versioned manifest carries the same stable agent ID, latest integrity-checked, filtered portable checkpoint and source incarnation lineage, plus a handoff idempotency key. Memory sync defaults to none. Only an explicit owner-reviewed admitted-only-scoped selection may add redacted, same-agent admitted memory. The site imports one Cloud-side incarnation per owner/agent/handoff key; retries return that record, conflicts refuse. Its Command Center identity card is a read-only receipt, not a live agent-state indicator.

Never in this identity export: provider session files or credentials, secret/local-only/sensitive records, unapproved/pending or global memory, JobPackets/résumés/application ledger records, arbitrary device paths, or a full raw local transcript. Returning requires the site's HMAC-verified, owner/workspace/sealed-result-bound envelope and a live local session; the daemon appends a new incarnation or reports conflict, never overwrites the local lineage. The site's signing key does not cross to Desktop/daemon. Neither the portable checkpoint nor the imported card proves exact provider session resume or an active Cloud model turn. Terminal /cloud handoff|return requires Desktop's real owner-review/verified-return path; its own preview makes no transfer. All of this remains default OFF and unqualified as a live Cloud round trip.

What is not in the product

  • A qualified persistent-agent Cloud round trip — operational owner review, Cloud-side import and authenticated return code is present, but no live provider/VM, packaged Desktop and deployed site were exercised as one end-to-end user journey. An imported record is not a running agent.
  • Paid enforcement — there is no plan tier, price, or paywall attached to any of this. Nothing on this page is a purchasable feature, and its arrival changes no plan.

How this differs from what already ships

Durable sessions keep a terminal or conversation alive across a closed window, sleep, or a reboot — that is a session outliving its view. Agents and sessions covers why a session is a conversation owned by the daemon, not an identity. Persistent agents are the missing piece above both: the agent outliving its session. Its future context is a bounded checkpoint plus selected, scoped memory, not a transcript carried forever. The persistent-agent path is still off by default in current builds.