Persistent agents and memory
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:
| Mechanism | What it is | What it is not |
|---|---|---|
| Identity | A stable agent record — who this agent is, across sessions | A provider login, or a session id |
| Provider session | The resume binding a provider CLI can prove for its own conversation | Proof of who the agent is |
| Checkpoint | A frozen, integrity-checked snapshot of workspace and session state at save time | The live process, or its scrollback |
| Memory | Scoped records admitted after trust checks or held for review | A 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.