Memory and sync

Memory is a built-in oHarness architecture plugin (memory), not an MCP server. It registers the Memory tool, per-turn system-prompt index, Memory page and a background synchronizer through the same PluginHost as model/tool/audit plugins. The page extension is intentionally small: registered descriptors plus host-owned renderers (currently memory), not arbitrary frontend JavaScript or an iframe SDK.

Default behavior

  • Notes always work locally, unsigned-in and offline: <project>/.oharness/memory/*.md.
  • Existing memoryDir overrides are preserved. No migration of local note files is needed.
  • Signing in enables automatic sync by default, detected within about 15 seconds while the Harness is running. New local notes/edits are picked up on the next tick.
  • The Memory page offers 关闭云同步 and 立即同步. OFF persists across process restarts and re-login, and blocks automatic and manual cloud requests until explicitly re-enabled. Local tools and editing continue to work.
  • Turning OFF cancels in-flight client requests. Data already accepted by Cloud cannot be retracted; OFF does not delete existing cloud copies.
  • Each local memory directory is pinned to its first syncing account and Cloud origin. Changing account/origin pauses sync; use a separate memory directory for another account. The UI does not offer a one-click unsafe reassignment.

Configuration boundaries

The page maintains ~/.oharness/plugins.json (OHARNESS_HOME overrides this), under runtimePlugins[id=memory].config.syncTargets[sha256(absoluteMemoryDir)]:

{ "userId": "verified-account-label", "origin": "https://your-cloud.example", "enabled": false }

Do not put R2 keys or Supabase configuration in this file. Cloud's URL remains in cloud.json; R2 credentials exist only in the Cloud website deployment environment. The local user label comes from the stored login; Cloud independently verifies the access token with Auth before deriving any object key.

To disable the entire plugin (page, tools, prompts and background work), add { "id": "memory", "enabled": false } to plugins.json's runtimePlugins. This takes effect on the next Harness creation. The page's sync switch works live. For embedders, harness.memory is now optional when the plugin is disabled; the lower-level AgentOptions.memory remains a legacy embedding API.

Storage and conflict rules

Cloud exposes authenticated /api/memory/identity, /snapshot and /commit. It stores no memory database rows. R2 holds immutable Markdown revisions plus an atomically replaced JSON manifest beneath:

users/<server-verified-user-id>/projects/<project-id>/memory/
  manifest.json
  objects/<note-id>/<revision-id>.md

The Git root commit identifies a project across normal clones. Non-Git projects use the existing absolute-path-based project ID and do not automatically map across different device paths. A shared custom memoryDir also shares its local sync setting; prefer one directory per project/account.

The local baseline (~/.oharness/memory-sync/) enables three-way reconciliation: unchanged notes are skipped, one-sided changes are copied, two-sided changes are shown as conflicts. No last-write-wins overwrite. Deletions are tombstones. Cloud manifest writes use ETag compare-and-swap to reject concurrent stale updates. When the manifest has not changed, only the manifest is fetched from R2, avoiding repeated object reads. Sync retries transient failures after a 60-second cooldown; manual sync can retry immediately. Conflicts wait for user resolution.

The UI backs up deleted notes and losing local conflict versions under ~/.oharness/memory-backups/. Those JSON notes can be restored using the editor; there is no backup-browser or restore button yet. Direct filesystem edits remain supported, but concurrent external editor writes during a merge are not protected by the process-local write lock. Keep one running process per local directory.

Initial bounds: 500 manifest entries including tombstones per project, 128 KiB per note body, 2 MiB total serialized snapshot. Old cloud revisions are retained; automatic revision/tombstone garbage collection and cloud account-data deletion are not implemented. Do not apply a blanket expiry rule to live revision objects. R2 is private and uses TLS, but this is not end-to-end encryption: Cloud can read note content. Only Memory notes sync, not session logs, files or credentials.

Verification and deployment

Run npm run test:memory, npm run test:plugins, npm run typecheck and npm run build. script/memory-ui-demo.ts starts an isolated unsigned-in UI on 4319 with a temporary home; it never reads the user's credentials. Cloud has a separate npm run test:memory suite and R2 setup instructions in OHarness_Cloud/MEMORY.md. Missing R2 configuration produces a visible sync error without breaking local memory. Restart the local dev server after source changes.