# Memory

> Canonical path: `specs/memory/memory.md`
> Status: living product and architecture spec
> Scope: agent-local recovery memory, MeshX managed Memory resources, repository-backed Memory detail, and permission boundaries.

## 1. Purpose

AgentTeam uses the word Memory for two different surfaces:

1. **Agent-local memory**: files inside an agent workspace used for deterministic task recovery.
2. **Managed Memory**: a MeshX workspace resource with explicit grants and an AGS-backed repository.

These surfaces must stay separate. Agent-local memory is runtime recovery state. Managed Memory is a shareable workspace resource.

## 2. Agent-local Memory

Agent-local memory belongs to one concrete agent workspace.

Canonical layout:

```text
<agent-workdir>/
├── MEMORY.md
├── state/
├── kb/
└── archive/
```

Semantics:

- `MEMORY.md` is a bounded index and recovery entry point.
- `state/` is current-task recovery state.
- `kb/` is durable local knowledge for the same concrete agent.
- `archive/` is historical fallback material.

Portable role assets are not agent-local memory. Portable role knowledge belongs in role packages such as `IDENTITY.md`, skills, or portable knowledge files.

Spawn and role-sync compatibility flows materialize portable role assets only. They must not copy another agent's `MEMORY.md`, `state/`, `kb/`, or `archive/` as default portable state.

## 3. Managed Memory

Managed Memory is a MeshX workspace resource.

It has:

- MeshX metadata;
- explicit human grants;
- at most one owner-controlled agent binding;
- a single canonical AGS repository;
- repository-backed product tabs for browsing and maintaining content.

Managed Memory does not replace agent-local recovery files. A runtime or agent integration may use managed Memory only after MeshX authorizes that access.

`internal/store/resource/memory` owns persistence for managed resource-runtime
Memory assets, human grants, the one-to-one agent binding projection, collection
pins, and agent-asset access-change outbox records. Authorization and lifecycle
orchestration remain in the MeshX Memory service, and HTTP contracts remain in
the MeshX API owner. This store is not interchangeable with AgentTeam server
`internal/service/agentmemory`, daemon runtime-local or recovery Memory,
generic agent state, or API DTOs.

## 4. Repository Model

Each managed Memory has one canonical repository. Repository ownership follows
the selected storage scope:

```text
personal storage:  <user_id>/<memory_slug>.mem
workspace storage: <workspace_id>/<memory_slug>.mem
```

The repository owner segment is always the owning user or workspace identity
itself. It must not carry a Memory-specific suffix such as `.mem`; the Memory
suffix belongs only to the repository name.

Creation rules:

1. MeshX validates create permission.
2. MeshX resolves the storage owner from the requested storage scope.
3. MeshX ensures the canonical repository under that owner.
4. MeshX records the mapping in `resource_repositories`.
5. MeshX creates Memory metadata and grants.
6. MeshX records the initiating user's product owner grant and, only when a
   product surface explicitly needs human AGS execution-plane access, records a
   durable access intent for reconciliation. Creation must not rely on an
   implicit permanent AGS collaborator grant.

The Memory repository mapping must exist for a newly created Memory to be usable. Repository identity lives in `resource_repositories`, not in ad hoc Memory fields or inferred route names.

Repository lifecycle rules:

- MeshX mutates AGS repositories only through AGS APIs. It must not read or write AGS database tables directly for Memory repository lifecycle.
- Metadata updates do not rename, move, or infer repository identity. Repository identity remains the `resource_repositories` row until an explicit repository lifecycle operation changes it.
- Archive and hard delete first delete the managed AGS repository through the AGS repository API, treating missing repositories as already deleted. Only after the AGS delete succeeds may MeshX mark the Memory deleted and remove `resource_repositories` / repository-access mapping rows.
- If AGS deletion fails, MeshX must keep the Memory metadata and repository mapping so the operation can be retried.
- If AGS was changed but MeshX cannot persist the follow-up metadata or mapping cleanup, MeshX records the failure in `resource_repository_lifecycle_events` for compensation and operator repair.
- If repository creation succeeded but Memory metadata or mapping persistence fails, MeshX records a `create_bind` lifecycle failure for the created repository identity.

New managed Memory repositories are initialized with a localized starter
template. The canonical starter paths are:

```text
MEMORY.md
system/goals.md
system/identity.md
profiles/user.md
profiles/collaborators/example-collaborator.md
knowledge/concepts/example-concept.md
knowledge/workflows/example-workflow.md
events/example-event.md
reflections/insights.md
```

The starter template is a writing scaffold only. Users may rename, move, or
replace these files after creation; repository content remains the source of
truth for Browse.

## 5. Permissions

Managed Memory access is fail-closed.

Human roles exposed by the product:

| Role     | View | Maintain content | Manage grants | Delete |
| -------- | ---- | ---------------- | ------------- | ------ |
| `owner`  | Yes  | Yes              | Yes           | Yes    |
| `manage` | Yes  | Yes              | Yes           | Yes    |
| `view`   | Yes  | No               | No            | No     |
| `none`   | No   | No               | No            | No     |

`edit` is a legacy compatibility value and must not be shown in new UI or
created by new grants. Human grants control Memory product visibility, product
content maintenance, and manage access. Direct human AGS repository access is
not implied by any Memory role; if a product route needs AGS execution-plane
access, MeshX must materialize it from an explicit durable access intent.

Memory Settings is visible only to `owner`, `manage`, or `canManage=true`
viewers. `view` users must not see a Settings tab or a disabled Settings page;
direct Settings URLs must replace to Browse.

Agent access is not modeled as a generic `memory_grants(subject_type=agent)`
grant. It is modeled as an explicit Memory-agent binding:

- one Memory repository can be bound to at most one agent;
- one agent can be bound to at most one Memory repository;
- only the Memory owner can bind or unbind the owner's own agent;
- the binding stores an explicit branch name; clients must not derive a branch
  such as `agents/<agent_id>`;
- the console may default the branch input to `main`, but the API contract still
  treats branch as explicit binding configuration;
- the binding may request one-time import of existing local memory content into
  the clone; this option must be described as existing-local-content import, not
  as an old-data migration mode.

Browser create-agent flows must create the owner-controlled Memory binding for
newly created agents and request existing-local-content import before the create
flow is considered complete. If the managed Memory binding cannot be created,
the agent creation flow must fail rather than leave a newly created agent without
managed Memory. Existing non-archived agents on active machines must converge to
the same default through a server-side backfill that creates the owner-controlled
Memory binding without requiring the agent daemon to be online. Opening the
Agent Memory panel may still repair a missing binding for a can-manage viewer,
but UI repair is not the migration mechanism for disconnected agents. The Agent
Memory panel must present the bound Memory contents, history, snapshots, and
refresh controls without exposing a separate "save to cloud" toggle. Binding
management belongs to an owner-level managed Memory surface rather than a
per-page transient switch in the Agent Memory panel.

Sharing a Memory with another person does not directly bind that person's agent.
The recipient can view/fork according to human grants. After forking, the
recipient becomes owner of the fork and may bind their own agent to that fork.

The server exposes fork as `POST /api/v1/workspaces/:workspaceId/memory/{memory_id}/fork`. The caller
must have a visible human role (`view`, `manage`, or `owner`) on the
source Memory. Forking creates a new caller-owned Memory row, provisions a new
canonical Memory repository, copies the source canonical repository into that
new repository, and records the new repository mapping. The source Memory row,
grants, bindings, and repository mapping are not mutated by the fork.

Agent-facing integrations must resolve the single binding for an agent. They
must not return or consume arrays of Memories for one agent in the current
contract.

AGS repository access is required for repository operations but does not replace MeshX Memory grants. Memory AGS repositories are not workspace-directory resources by default and should not be exposed as a raw AGS operation surface unless an explicit product spec and durable access intent requires it.

Self-demotion protection is required for browser-facing Memory permission
writes. When a workspace member changes their own Memory grant from a higher
role to a lower role, including `manage` to `view` and `view` to `none`, the UI
must block the write until the member explicitly confirms the downgrade. The
confirmation must explain that the member may lose Memory visibility, content
maintenance, grant management, and any Memory-owned AGS repository
materialization tied to durable access intent; an owner or another manager may
need to restore access later. The API request must carry an explicit
confirmation flag for the confirmed submission, and the server must reject
unconfirmed self-demotion attempts defensively so the rule is identical to KB
and Skill permission changes.

## 5.1 Desktop Binding Behavior

Desktop fetches the current agent's Memory binding and receives either no
binding or a single Memory repository with branch and import configuration.

Local-first write contract:

- after Desktop has authoritatively resolved a binding, the canonical writable
  Managed Memory root is always `<agent-workdir>/memory`, independent of the
  client surface that submitted the message and independent of current network
  or repository availability;
- agent-local `state/` remains the recovery owner. Root `MEMORY.md`, `kb/`, and
  `archive/` are not alternate durable write roots for a bound agent;
- workspace-root `kb/` is the independent Local Knowledge Index, not Memory.
  User preferences belong in the profile linked from canonical `MEMORY.md`
  (`profiles/user.md` in the standard template), and a successful explicit
  Memory reply must not point at a root `kb/` write;
- an explicit user request to remember, store, update, or forget Memory is a
  write task. The runtime must complete the local filesystem write before it
  confirms that the request was recorded;
- a successful local write is sufficient to say that the request was recorded.
  Cloud commit and push are asynchronous outcomes and must not be claimed until
  separately confirmed. A failed cloud push does not invalidate the local
  write;
- if no clone exists and the first clone attempt fails, Desktop keeps a pending
  local root at the same canonical path. A later successful attach imports that
  root through the existing local-content conflict policy instead of switching
  the runtime to workspace-root Memory;
- if a matching clone already exists, transient fetch, pull, authentication, or
  transport failure must not make the runtime switch roots or stop local
  Memory writes. Desktop continues from the local clone and retries remote work
  separately.
- when a previously unbound live runtime first attaches canonical Memory,
  Desktop retires that runtime after attach so its next delivery recompiles the
  thread contract against `memory/MEMORY.md`; it must not keep using stale
  workspace-root Memory instructions.

Clone/update behavior:

- first attach clones the Memory repository at the bound branch;
- if the same Memory repository switches branch, Desktop checks out/fetches/pulls
  the new branch in the existing clone instead of deleting and recloning;
- if the repository identity changes, Desktop may detach the old clone and clone
  the new repository;
- revoke/unbind stops live watch and push activity but preserves offline data.

Existing local content import:

- import is an explicit binding option, and default owner-controlled agent
  Memory bindings request it so existing agent-local memory is preserved;
- when enabled, Desktop imports existing local `MEMORY.md` and existing local
  `memory/` contents into the managed Memory clone according to the product's
  conflict policy;
- the historical root `kb/user-preferences.md` location is copied into the
  canonical user profile once per Memory binding during existing-local-content
  import. Before mutating or deleting any general import source, binding-scoped
  durable state reserves the number of bytes it consumes from the cumulative
  10 MiB budget. The state advances from `general_import_pending` to
  `legacy_cutover_ready` only after the general commit and push, so a crash or
  retry cannot restore consumed budget. It does not treat the legacy source as
  stable while a workspace-root fallback runtime can still write it. After that
  exact runtime generation has stopped, Desktop uses only the remaining budget
  for the final source probe. This stop barrier, final merge, and push must all
  finish before Spawn can dispatch a canonical initial turn. Only then does
  Desktop atomically advance the state to `done`. The same final probe closes
  the window when no legacy file exists. Launch failure or daemon restart keeps
  the binding in its durable pending phase so the operation is retried instead
  of reopening or prematurely closing the window. Later Local Knowledge content
  at that path is never imported. The root file is not deleted because root
  `kb/` has independent Local Knowledge ownership;
- when an agent has no active session, Desktop may pre-clone the bound Memory
  repository after daemon catalog sync and must include legacy latest-session
  `MEMORY.md` content in the one-time import path;
- import is bounded to 10 MiB of existing local file content; when the bound is
  exceeded, Desktop backs up the existing local content outside the clone and
  does not push it automatically;
- on same-path conflicts, the managed Memory clone's file wins at the canonical
  path and the existing local file is preserved under `.conflicts/` in the
  import commit;
- the import should commit and push through the authenticated Memory pusher
  before Desktop removes imported local sources;
- the UI and code should describe this as importing existing local content.

Desktop must not create automatic pull requests. Pushing to a branch is an agent
write behavior; opening a PR is a user-confirmed action.

Cloud synchronization is asynchronous to local Memory writes:

- the watcher commits local changes before attempting a push, and an unavailable
  remote must not make the local file write itself fail;
- a failed push remains `pending_sync` and retries through the same serialized
  Git pipeline with bounded exponential backoff; watcher startup also recovers
  uncommitted files and commits left ahead by an earlier session or daemon run;
- Desktop exposes process-local `ready`, `pending_sync`, and `degraded` states
  with bounded reason codes (`lookup_failed`, `auth_failed`, `clone_failed`,
  `watcher_failed`, `commit_failed`, or `push_failed`) and an explicit retry
  operation; this runtime projection is not persisted to TiDB;
- repository credential exchange and push recovery must resume after bounded
  backoff without requiring a WebSocket reconnect;
- `Last saved` / `lastSyncAt` continues to mean the time of a real commit visible
  in the canonical AGS repository. Local commit or retry state must not advance
  that timestamp.

The shared Agent Memory panel presents the runtime projection without changing
that timestamp contract:

- `ready` is shown as cloud synced;
- `pending_sync` and `degraded` state that local changes are saved, show the
  bounded reason when available, and offer the explicit retry operation;
- a renderer or older daemon that cannot provide the runtime projection shows
  cloud status as unknown and must not infer `ready` from repository metadata;
- when the daemon supports the projection but has not established per-agent
  state yet, or a status read fails transiently, the panel remains unknown and
  probes again at a bounded slow interval; an absent daemon capability does not
  poll continuously;
- machine offline is a separate condition from cloud synchronization health.

On Desktop, the panel's current Memory directory tree and file contents read
the canonical checkout directly through the loopback daemon API. They must not
traverse the selected server or runtime link, and cloud metadata/synchronization
must not block current-content first paint. Browser has no direct filesystem
authority, so its workspace-file adapter reaches the same daemon-local checkout
through the authenticated workspace-scoped server route. When that local
checkout exists, current-content reads must not fetch repository contents from
Cloud Memory. Cloud metadata, history, compare, and backup remain separate,
explicit surfaces. Browser sync status likewise comes from the daemon's
process-local projection through a workspace-scoped server-to-daemon endpoint;
it must not be inferred from Cloud Memory metadata or `lastSyncAt`.

## 5.2 Local Search Derivative

Desktop owns a daemon-local full-text search derivative over the canonical
Memory clone. The canonical clone is the source of truth; the derivative is
rebuildable state that must never be mistaken for Memory content.

Ownership and surface:

- `desktop/internal/memorysearch` is the only owner of the derivative. It
  scans, chunks, indexes, queries, and garbage-collects; other daemon packages
  must not mutate the derivative directly.
- The derivative lives in a daemon-owned sibling directory of the canonical
  Memory clone, under the same agent work directory. It must never be placed
  inside the canonical Memory clone, because the clone is itself the source of
  truth and must not carry derived state. The path layout, generation
  directory, manifest, health, pending update, and lock files are internal
  implementation details and must not be treated as stable product paths.
- The canonical clone root is resolved from the Desktop agent's work directory
  and the current binding identity. It must not be derived from an agent tool
  parameter, a session key, or a runtime-supplied path.
- The derivative is only built for a bound, authorized Memory whose canonical
  clone is present. Building, querying, or serving the derivative for an
  unbound Memory is prohibited.
- The `memory_search` agent tool and the `memory-maintenance` runtime skill
  are injected together for leader interactive runtimes with MCP tools
  enabled. The tool surface remains stable while binding/index readiness
  changes; an unavailable index fails at call time with an actionable error.

Authorization and lifecycle:

- The Memory MCP route is served only to ordinary authenticated interactive
  runtimes using an agent credential bound to the route. Token-analysis and
  unknown credential scopes are rejected before handler dispatch.
- A tool call must resolve the route's agent identifier to a canonical local
  agent identity before touching any path; it must never concatenate the raw
  route value into a filesystem path.
- `memory_search` accepts only a query and bounded filters (`pathPrefix`,
  `kinds`, `includeArchived`, `limit`, `maxChars`). It must not accept an
  `agentID`, a canonical root, a generation, a raw Bleve query, or any flag that
  would bypass source validation.
- The agent should normally pass only `query`. A query covers one focused
  topic with two to six discriminative terms and includes an exact identifier,
  symbol, path, or stable phrase when one is known. The default response limit
  is three hits and the default whole-response snippet budget is 1600 Unicode
  characters; callers expand those bounds only when a focused first search is
  insufficient.
- If a focused query has no confident match, the agent may retry once with
  fewer, more literal terms or split mixed intents. It then falls back to
  bounded grep or bounded file traversal. When the caller already knows a
  unique identifier, symbol, or path, bounded grep may be cheaper than semantic
  search.
- When the authoritative binding state is `Bound`, the derivative is kept
  consistent and the tool and skill may be injected.
- When the authoritative state is `Unknown`, Desktop retains the last trusted
  binding and the current derivative. It must not revoke, delete, or rebuild
  from an unverified source. A tool call may return a stale indicator so the
  caller can choose a narrower action while Desktop retries reconciliation.
- When the authoritative state is `Unbound`, Desktop must fail closed: the
  serving surface is removed first, watchers and reconciliation for that agent
  are stopped, and already-running sessions lose the tool and skill after their
  next launch refresh. The on-disk derivative is held under bounded fenced
  retention and is removed by explicit purge (Desktop reset, agent delete, or
  the Advanced Clear control) or by the retention timeout. Desktop must not
  distinguish revoke from disable when the server gives no reason code: both
  follow the same fail-closed path.
- Watcher production and teardown are lifecycle-owned operations, not detached
  best effort. Access lookup, clone/rebind, watcher construction/start, and
  publication hold one generation-bearing mutation owner. Shutdown closes that
  admission before taking its atomic session snapshot, joins admitted mutation
  and session-start owners, drains every published or already-detached watcher
  through `FlushAndClose` and final push, and only then disables the search
  registry. Bounded daemon shutdown may return at its caller deadline, but the
  fenced background drain must continue and may not publish a late watcher.
- A same-agent session replacement is one transaction under the per-agent
  lookup fence: fence and fully drain the old watcher before the
  new clone/watcher touches the shared workdir. If launch fails before session
  publication, the old watcher may be restored only while the old session is
  still current, non-terminal, backed by a Process that still accepts delivery,
  and watcher mutation admission remains open. Final removal/reset installs a
  watcher-publication fence before its slow drain; cleared watcher fields do not
  mean cleanup is complete while a detached-drain owner still exists.

Response contract:

- Every hit returned by `memory_search` is a locator plus a bounded snippet,
  not the stored indexed body. Before a hit is returned, the owner must reopen
  the canonical file through a confinement root, verify the current content
  hash matches the manifest, and regenerate snippet and line bounds from the
  current file content. Hits whose canonical file no longer matches must be
  dropped, not downgraded.
- The agent-facing success payload is one compact JSON text object. It does not
  duplicate the payload in MCP `structuredContent`:

  ```json
  {"results":[{"path":"knowledge/concepts/example.md","lines":[4,18],"snippet":"bounded current-file excerpt"}]}
  ```

- `results` is always present and ordered by relevance. Each result contains
  exactly the canonical Memory-relative `path`, a one-based inclusive
  `[start,end]` `lines` range for the next bounded read, and a bounded
  `snippet` regenerated from the current canonical file. The wire response does
  not expose the canonical absolute root, headings, source kind, archived
  state, match diagnostics, raw score, generation id, source revision,
  manifest fields, on-disk size, or file counts.
- A normal no-match is `{"results":[]}`. The payload adds `stale:true` only
  when verified results may be incomplete because reconciliation is in
  progress, and `truncated:true` only when the snippet budget shortened or
  removed result snippets. Default false flags are omitted.
- An unavailable index, unavailable binding, authorization failure, or
  unexpected runtime failure is an MCP tool error, not a successful search
  payload. Errors must not expose internal paths or derivative state.

Maintenance and failure:

- The owner serializes writes per agent through a single-writer actor and a
  cross-process file lock; a second Desktop instance that cannot acquire the
  lock must report degraded rather than write concurrently.
- A same-agent binding-identity transition is also a derivative transaction.
  Desktop first synchronously stops the old reconciler, then holds the
  cross-process owner lock while it durably records the old fingerprint as
  non-active and removes `pending` only when that journal still carries the
  exact old fingerprint. A stale old-binding finalizer must not overwrite a
  newer lifecycle sidecar or remove a newer binding's journal. If a crash lands
  between the durable lifecycle fence and journal removal, replacement startup
  may discard the mismatched journal only when a valid lifecycle sidecar names
  that exact pending fingerprint and records a non-active state; missing,
  corrupt, active, or unrelated lifecycle evidence fails closed. This recovery
  path still runs the normal orphan-generation sweep.
- Reconciliation is idempotent. A crash during an incremental update must leave
  either a previously committed manifest that matches the indexed revision, or
  no claim of readiness; the next reconciliation completes the state.
- Rebuild and Clear are user-initiated maintenance actions. Rebuild creates a
  side-by-side generation and swaps only after smoke validation. Clear is an
  explicit purge of the derivative and does not delete the canonical Memory.
- Size and disk-pressure caps protect the host. When space is low, the owner
  sheds non-current generations, fenced bindings, and rollback generations
  before failing new builds; ordinary current searches must continue while the
  current generation remains valid.

## 6. Memory Detail Surface

Managed Memory detail is a repository-backed product surface.

The product tabs are:

- **Browse / 浏览**: repository file browser.
- **History / 历史**: timeline of Memory content changes.
- **Snapshot / 快照**: repository release publishing workflows presented as
  Memory snapshots.
- **Settings / 设置**: manager-only Memory metadata, backing repository settings,
  grants, and the current owner-controlled agent binding. When an owned agent is
  bound, Settings must show the agent and explicit branch and provide an inline
  unbind action; unbind keeps the managed Memory itself intact and follows the
  Desktop retention behavior in §5.1.

There is no top-level Insights tab for Memory. Opening a Memory defaults to Browse.
In the Browse tab, repository context controls such as the tree label, branch
selector, and commit-history entry belong at the top of the left file-tree
column. The right detail pane should stay focused on the selected file or
overview content.

History is the user-facing label for repository commit history in the Memory
context. It should describe how the Memory content evolves over time, not expose
implementation terms as the primary explanation. The History list must be
bounded and paginated; clients must not assume the complete repository history
can be loaded in one request.

Memory repository activity is projected from the canonical repository only. The
shared resource activity projector tracks repository commits for History and
repository releases for Snapshot. Projection is cache-first and read-through:
activity APIs return stored activity, refresh only repositories for Memories
already visible to the caller, skip repositories refreshed within roughly three
minutes, and do not rely on a background full-repository scan.

Browse offers two left-column display modes:

- a document map, which is the friendly default and renders known Memory
  template directories and stable starter filenames as localized document names;
- a raw directory tree, which preserves the underlying repository directory and
  filename view.

The document map must not mutate repository paths or call Skill-specific
repository scanning. It reads repository tree/content data and projects those
paths into document labels only in the UI. It must include localized labels for
the canonical starter paths in §4 and for stable Memory section names such as
system, identity, profiles, collaborators, knowledge, concepts, workflows,
events, reflections, and insights. The legacy `contacts` name may remain
recognized as a display alias for older repositories, but new starter templates
use `profiles/collaborators/`.

Unknown directory or file names fall back to a readable title-cased label when
possible, or the original name when not. Top-level document entries should align
with top-level expandable sections; selection should be indicated with normal
foreground text and emphasis rather than faded text.

Browser routes are product-surface routes. The data layer may call AGS
repository APIs such as tree, commits, releases, and contents. This path/API
decoupling is part of the product contract.

## 7. Navigation

Memory routes:

```text
/memory
/memory/:memoryId
/memory/:memoryId/browse
/memory/:memoryId/history
/memory/:memoryId/releases
/memory/:memoryId/settings
```

The `releases` route segment hosts the user-facing Snapshot tab.

When the user opens Memory from another workspace surface:

- if a Memory has an active selection, the detail opens that Memory;
- otherwise the Memory landing surface opens the recent activity or list experience;
- the right-side content must not remain on an unrelated KB source, repository, or previous resource detail.

Switching between Memories should preserve the detail shell and update content with stable loading states instead of blanking the whole right-side surface.

## 8. Clean Boundary

Do not mix managed Memory with:

- agent-local `MEMORY.md` recovery semantics;
- KB source repository roles;
- Project references;
- repository name inference as authority;
- workspace-wide fallback access for agents.

The clean model is: local recovery memory stays in agent workspace files; managed Memory lives in MeshX metadata, Memory grants, `resource_repositories`, and AGS repository content.
