Appearance
Agent Memory
One-line summary
Soul is who YOU are. Agent Memory is how the agent OPERATES in your environment.
Every Synap tenant has two parallel cognition layers:
arx_soul— identity, preferences, corrections, dialect, context (about the user).arx_agent_memory— operating patterns, routing rules, voice rules, discipline rules, conventions, do-nots (about the agent).
This page covers the agent_memory layer: schema, federation model, categories, the seeded defaults, the REST API, MCP tools, CLI subcommands, and the iOS UI.
Why it exists
Most "AI memory" systems mix three different things into one bag: who the user is, what the user has worked on, and how the assistant should behave. Synap separates them so an agent on a fresh device gets behavior-shaping rules from the graph without re-litigating identity every session.
The six categories
| Category | What it captures |
|---|---|
operating_pattern | A recurring, validated approach. "Audit before patching", "Isolate one variable per test." |
routing_rule | Which model/personality to use for which kind of work. "Opus for reasoning, Haiku for routine edits." |
voice_rule | Voice/tone discipline. "Emails in the user's voice, not the assistant's." |
discipline_rule | Process discipline. "Let counsel propose first." |
convention | Persistent shape rules. "Capture format: [TAG] Title (YYYY-MM-DD): Details." |
do_not | Hard never-do's. "Don't fabricate sources or library APIs." |
Federation model
Each entry has a visibility scope:
- Tenant-shared (
agent IS NONE) — every registered agent in the tenant sees it. - Agent-private (
agent = caller) — only the calling agent sees it. - Commissure-published (
commissure ∈ caller.memberships) — agents who joined the bridge see it.
A query for "what should I obey?" returns the union: own ∪ tenant-shared ∪ via-commissure.
Commissures here are in-tenant lightweight bridges, distinct from the Federated-Commissure inter-tenant surface.
Cross-tenant federation (publish into a Federated-Commissure)
Tenant-shared agent_memory entries can be published into an inter-tenant federation commissure so other tenants in that bridge receive a mirror copy. The mirrors land tenant-shared on the receiver side, surfacing automatically to every receiver-tenant agent.
bash
# publish
synap agent-memory federate <memory-id> <federation-commissure-id>
# withdraw (deactivates all mirrors across receiver tenants;
# the source entry stays intact)
synap agent-memory unfederate <memory-id>REST:
POST /api/v3/agent-memory/entries/{id}/federate { commissure_id }
POST /api/v3/agent-memory/entries/{id}/unfederateMCP: arx_agent_memory_federate + arx_agent_memory_unfederate.
The caller's tenant must be a member of the federation commissure (the /api/v3/federation/commissure surface). Federate is idempotent on (federation_commissure_id, federation_source) — re-publishing the same entry into the same commissure is a no-op. Receivers see mirrors tagged source = "federation:<commissure_id>" and federation_source = "<source_tenant>:<source_entry_id>" so the origin is auditable.
Trust-tier-aware federation
A receiver doesn't have to accept everything a sender publishes. Each entry can carry an author-declared trust tier, and each receiver can set a per-commissure floor. On publish, an entry is mirrored into a receiver tenant only if its tier satisfies that receiver's floor — otherwise it is silently skipped (no error; the publisher is told who refused and why).
Trust tiers, strictest to laxest:
device_only < trusted_cloud < general_clouddevice_only is the strictest (never leaves the device class); general_cloud is the laxest (any substrate). A floor of trusted_cloud means "I will accept entries marked as strict as, or stricter than, trusted_cloud" — i.e. device_only and trusted_cloud, but not general_cloud.
Author side — stamp a tier on an entry. Optional; unstamped entries are "unmarked".
bash
# CLI
synap agent-memory add convention "deploy via gitlab-first" --min-trust-tier trusted_cloud
synap agent-memory update <id> --min-trust-tier device_only # `none` clears# REST — min_trust_tier on create/update
POST /api/v3/agent-memory/entries { ..., "min_trust_tier": "trusted_cloud" }
PATCH /api/v3/agent-memory/entries/{id} { "min_trust_tier": "device_only" } # null clears# curl one-liner
curl -X POST https://api.synap.ing/api/v3/agent-memory/entries \
-H "X-ARX-Key: $KEY" -H 'content-type: application/json' \
-d '{"content":"...","category":"convention","min_trust_tier":"trusted_cloud"}'On iOS, the Add Agent Memory Entry sheet has a trust-tier picker (None / Device Only / Trusted Cloud / General Cloud).
Receiver side — set a per-commissure floor. You set the floor on your own membership in a federation commissure; it only affects what gets mirrored into your tenant.
bash
# CLI — `none` clears the floor (back to permissive)
synap commissures trust-tier <federation-commissure-id> trusted_cloud# REST
PUT /api/v3/federation/commissure/{id}/trust-tier-floor { "min_trust_tier": "trusted_cloud" }On iOS, open the commissure detail sheet and tap your membership row's trust-tier floor to pick a value. In the web portal, the commissure detail page's members table has a trust-floor selector on your own row.
What happens on publish. federate returns entry_min_trust_tier, mirrored_to, and skipped_for_trust_tier — the receivers whose floor blocked this entry, with their floor and the entry's tier, so the gate is visible rather than silent:
json
{
"entry_min_trust_tier": "general_cloud",
"mirrored_to": [],
"skipped_for_trust_tier": [
{ "tenant": "acme", "receiver_floor": "trusted_cloud", "entry_tier": "general_cloud" }
]
}Gating truth table (entry tier vs. receiver floor):
| Entry tier | Receiver floor | Mirrored? | Why |
|---|---|---|---|
| any | unset | ✅ yes | No floor → permissive, accept anything |
device_only | trusted_cloud | ✅ yes | Entry is stricter than the floor (device_only ≤ trusted_cloud) |
trusted_cloud | trusted_cloud | ✅ yes | Entry equals the floor |
general_cloud | trusted_cloud | ⛔ no | Entry is laxer than the floor |
| unmarked | trusted_cloud | ⛔ no | Floor set + entry unmarked → blocked (receiver demands an explicit tier) |
| any | malformed floor | ✅ yes | Fail open — don't block on bad floor data |
This is CSO BLOCKING #1: the same min_trust_tier vocabulary gates Chorus personality dispatch (a sensitive oracle pinned to trusted_cloud never runs on the general cloud) and federation mirroring. See ARX milestone oav5qo46.
Default seed (every new tenant)
Every new tenant gets 15 baseline rules tagged source = "synap:default-v1", plus a starter registry of 9 models and 6 personalities. They're tenant-shared so every agent in the tenant inherits them.
| Category | Count | Examples |
|---|---|---|
operating_pattern | 6 | Search-before-asking, capture-proactively, link-related-nodes, session-breadcrumbs |
do_not | 3 | No fabrication, no unsolicited closers, don't re-ask what's in graph |
discipline_rule | 2 | Good-faith dialect interpretation, match user register |
convention | 2 | Capture format [TAG] Title (YYYY-MM-DD): Details, edge type vocabulary |
voice_rule | 1 | User's voice not assistant's for outward artifacts |
routing_rule | 1 | Sensitive content → local inference / privacy gateway |
Don't like one? synap agent-memory deactivate <id> removes it. Bumping the version tag in a future release triggers re-seed for tenants on the old version.
Seeded model registry: Claude Opus 4.7 / Sonnet 4.6 / Haiku 4.5, GPT-5, Gemini 2 Pro, Gemma 3 on-device, Llama 3 on-device, MLX local, Synap Cortex.
Seeded personalities: default, patent_counsel, advisor_email, debug_terse, technical_review, synap_writer.
REST API
All endpoints are under /api/v3/agent-memory/ and require ARX JWT auth (Authorization: Bearer <token>) or an API key (X-ARX-Key: <key>).
Assembled view
| Method | Path | Returns |
|---|---|---|
GET | /api/v3/agent-memory?agent_id=<id> | Entries visible to the agent, ordered category → priority → updated_at |
Agents
| Method | Path | Purpose |
|---|---|---|
POST | /api/v3/agent-memory/agents | Register an agent (idempotent on harness × device × name) |
GET | /api/v3/agent-memory/agents | List agents (filter harness=, active=true) |
POST | /api/v3/agent-memory/agents/{id}/heartbeat | Touch last_seen |
POST | /api/v3/agent-memory/agents/{id}/deactivate | Soft-remove |
PATCH | /api/v3/agent-memory/agents/{id}/routing | Set default model + personality + available models + routing parent |
GET | /api/v3/agent-memory/agents/{id}/children | List routing children |
GET | /api/v3/agent-memory/agents/{id}/commissures | List memberships |
Models
| Method | Path | Purpose |
|---|---|---|
POST | /api/v3/agent-memory/models | Register a model (idempotent on provider × model_id) |
GET | /api/v3/agent-memory/models | List |
PATCH | /api/v3/agent-memory/models/{id} | Update fields |
Personalities
| Method | Path | Purpose |
|---|---|---|
POST | /api/v3/agent-memory/personalities | Register (idempotent on name) |
GET | /api/v3/agent-memory/personalities | List |
PATCH | /api/v3/agent-memory/personalities/{id} | Update |
Commissures
| Method | Path | Purpose |
|---|---|---|
POST | /api/v3/agent-memory/commissures | Create a bridge (scope = shared / broadcast / directed) |
GET | /api/v3/agent-memory/commissures | List |
POST | /api/v3/agent-memory/commissures/{id}/members | Add a member (permission = read / write / admin) |
GET | /api/v3/agent-memory/commissures/{id}/members | List members |
DELETE | /api/v3/agent-memory/commissures/{id}/members/{agent_id} | Remove |
Entries
| Method | Path | Purpose |
|---|---|---|
POST | /api/v3/agent-memory/entries | Create entry |
GET | /api/v3/agent-memory/entries | List (filters: agent_id, category, commissure_id, active) |
PATCH | /api/v3/agent-memory/entries/{id} | Update |
POST | /api/v3/agent-memory/entries/{id}/deactivate | Soft-remove |
GET | /api/v3/agent-memory/search?q=...&agent_id=...&limit=25 | Keyword search across visible entries |
POST | /api/v3/agent-memory/entries/{id}/references | Link entry to a thought |
GET | /api/v3/agent-memory/entries/{id}/references | List referenced thoughts |
MCP tools
The synap-mcp server exposes 16 tools in the arx_agent_* namespace, available to any MCP client (Claude Code, Cursor, Zed, opencode, Continue, Aider, custom).
| Tool | Purpose |
|---|---|
arx_agent_register | Register THIS agent (idempotent). Call on first turn. Cached in-process. |
arx_agent_list | List agents in the tenant |
arx_agent_heartbeat | Touch last_seen |
arx_agent_set_routing | Bind agent → default model + personality + available models + routing parent |
arx_model_register | Register a known inference model |
arx_model_list | List models |
arx_personality_register | Register a personality (voice/behavior profile) |
arx_personality_list | List personalities |
arx_agent_memory | Assemble visible memory for this agent (call on first turn alongside arx_soul) |
arx_agent_memory_set | Create an entry. Default scope = private to agent. tenant_shared=true for shared. commissure_id=... to publish to a bridge. |
arx_agent_memory_list | List entries with filters |
arx_agent_memory_search | Keyword search across visible entries |
arx_agent_commissure_create | Create an in-tenant cognitive bridge |
arx_agent_commissure_join | Add an agent to a commissure |
arx_agent_commissure_list | List commissures or memberships |
First-turn protocol
Every harness with MCP access should run this sequence on the first user turn:
text
1. arx_agent_register (idempotent — caches agent_id in the MCP process)
2. arx_soul (load identity profile)
3. arx_agent_memory (load visible operating rules for THIS agent)The cognition layer ships this protocol in ~/.synap/bootstrap.md, auto-loaded into every Synap-wired AI tool.
CLI subcommands
bash
# Browse and manage
synap agent-memory show [--agent <id>] [--category <c>]
synap agent-memory list [--agent <id>] [--category <c>] [--commissure <id>]
synap agent-memory add <category> "<content>" [--agent <id> | --tenant-shared] [--commissure <id>] [--priority N]
synap agent-memory search "<query>" [--limit N]
synap agent-memory deactivate <id>
synap agent-memory link <memory-id> <thought-id>
# Agent + routing
synap agents [list | register <name> | heartbeat <id> | deactivate <id> | routing <id> | children <id>]
synap models [list | register --name <n> --provider <p> --model-id <id>]
synap personalities [list | register <name>]
synap commissures [list | create <name> | members <id> | join <c-id> <a-id> | remove <c-id> <a-id>]TUI
Open the full-screen TUI with synap tui. Two parallel screens:
- Press 4 or run
:go soulfor the Soul screen. - Run
:go agent-memory(or:go am) for the Agent Memory screen.
Both screens render category breakdown bars on the right and full entry detail for the cursor row.
iOS app
Under More → Features:
- Soul (Identity) — browse + add + deactivate soul entries
- Agent Memory — three-tab sheet (Memory / Agents / Routing). Tap any memory row for detail + deactivate. The + button creates a new entry with category picker, priority stepper, and a Visible to every agent toggle (private vs tenant-shared).
The iOS app auto-registers as a participating agent (harness = "ios-app") on first open, so its entries are visible to desktop agents via the tenant-shared lane or any commissure both parties join.
Privacy
- The gating decision (what to dispatch, what to share) stays on the device or in the tenant — never crosses to a third-party gateway by default.
- The
synap:default-v1seed contains no user-specific data; it's audience-agnostic baseline rules. - Per-entry source tagging lets you audit which rules came from which capture pathway.
Source of truth
Architecture decision: ARX milestone x118ycqz176bpg5h50a9 (sibling-to-soul partition). Default-seed milestone: ac9gzl8kp15t2tm9cdz6.