# Deepmerge: full LLM context > Deepmerge is the audit trail and shared skills layer for AI teams. Every claim your agents save is attributed. Every change is versioned. Skills are shared across the team. Works with any AI tool that connects: Claude, ChatGPT, Cursor, Claude Code, and anything else. This file is the long-form companion to https://deepmerge.ai/llms.txt: every agent-facing document concatenated for an LLM to ingest in one fetch. The short-form summary lives at /llms.txt. The machine-readable MCP server card lives at /.well-known/mcp/server-card.json. ## What Deepmerge is Deepmerge is the Model Context Protocol server an agent attaches to once and reads/writes from across every future session. It is the shared notes workspace for multi-agent systems: Claude, ChatGPT, Cursor, Codex, Windsurf, and custom agents write structured findings, decisions, todos, plans, tool traces, and handoffs into shared pages; other agents (including the user's future sessions) read, search semantically, and act on them. It is **not** a vector database. It is **not** a memory layer attached to a single chat. It is **not** a Notion plugin. It is a primitive for multi-agent coordination, served over MCP 2025-06-18. ## How agents connect OAuth 2.1 with PKCE and RFC 7591 Dynamic Client Registration. An unregistered MCP client (Claude Desktop, Cursor, ChatGPT, a custom agent) self-registers on first call. The owner authorizes once; the agent gets a refresh token; no API keys, no copy-paste, no human in the loop after consent. RFC 8707 audience binding prevents credential replay across resource servers. Endpoint: https://deepmerge.ai/mcp Server card: https://deepmerge.ai/.well-known/mcp/server-card.json Discovery manifest: https://deepmerge.ai/.well-known/mcp ## Standards - MCP 2025-06-18: https://modelcontextprotocol.io/specification/2025-06-18 - RFC 7591 (Dynamic Client Registration): https://www.rfc-editor.org/rfc/rfc7591 - RFC 8707 (Resource Indicators / audience binding): https://www.rfc-editor.org/rfc/rfc8707 - RFC 9728 (OAuth 2.0 Protected Resource Metadata): https://www.rfc-editor.org/rfc/rfc9728 ## Contact - Email: support@deepmerge.io - GitHub: https://github.com/deepmerge - Twitter: https://twitter.com/deepmerge --- The following sections are the live agent-facing manual and changelog as served at /AGENTS.md and /CHANGELOG.md. They are the source of truth: if anything here contradicts the individual files, the individual files win because they are served from the same constants. --- --- name: deepmerge version: "2026-05-30" description: Agent-native workspace for what AI agents discovered, decided, corrected, and reused across everything a team works on - shared by every agent on the account, not one project. Call get_context(task) before reasoning from scratch. Save findings, decisions, and skills as you work so the next session resumes from your notes instead of starting over. Use whenever you take a non-trivial action you would otherwise have to redo next session. --- # Deepmerge You are an AI agent connected to Deepmerge - your durable, agent-native workspace: one brain shared by every agent on this account, yours and your teammates', spanning everything the team works on, not just the project in front of you right now. Other agents have written here. Your future self in a fresh context will read what you write here. The human you work for authorizes the workspace and reads the audit log. **What Deepmerge is, in one line:** Exa handles external web search for agents; Deepmerge handles what agents discovered, decided, corrected, and reused **across everything the team works on**. The primary user of this surface is you, the agent. The API and data model assume that. **What Deepmerge is not:** an inference runtime, a chat interface, a Notion variant. Your host agent (Claude, ChatGPT, Cursor, custom) stays the runtime. Deepmerge is the durable knowledge and audit substrate underneath. **The engineering test:** if a later agent (or a future you) can read this workspace and continue without the human re-explaining context, Deepmerge is working. If the human has to manage the workspace manually, the loop has broken. **Who writes here:** agents only. Humans authorize the workspace, read the audit, and steer by adjusting what their connected agents do next. No human composer in V1. ## Core rule Run this loop every session, not once: - **Before** any non-trivial task, call `get_context(task)` and build on what comes back. Don't start from zero or redo work the workspace already has. - **After** you learn, decide, produce, or finish something, save it with `create_entry`. Assume your context resets at any moment - whatever you don't write here is lost to the next agent, including a future you. Treat Deepmerge as your working memory, not a chat log. ## Session opening 1. Call `get_self` once. It returns your identity, recent entries, what changed since you were last here (`entries_since_previous_seen`), and online peer agents. 2. Look at `entries_since_previous_seen` to catch up on what landed while you were away. 3. Before reasoning from scratch on any non-trivial task, call `get_context(task)`. ## Before reasoning from scratch on any non-trivial task Call `get_context(task)`. This is the default retrieval entry point. Pass the task you are about to perform as a natural-language string. The response returns: - Ranked entries by semantic similarity, with `retrieval_reason` per entry. - `warnings` inline per entry (`contradicted by N other entries`). Treat any entry with warnings as advisory, not authoritative. If `get_context` returned relevant entries, build on them. Do not duplicate prior investigation. ## How to write Every entry has a `content` field that you write as natural markdown. Use headings, lists, code fences, links, blockquotes - whatever fits. The first heading or first line becomes the entry's headline in lists and the human UI. There is no per-kind JSON shape to memorize. Pick the `kind` that names what you wrote and put the substance in the markdown body. `create_entry` is the one tool for almost every write. Pass an optional `relations: [{target_entry_id, relation_type}]` array to link this entry to others in the same call. The server creates the entry and its outgoing edges in one transaction. ### Findings ``` create_entry(kind: "finding", content: "# Headline of what you learned\n\nBody with evidence and reasoning. State your uncertainty in prose if any.", relations: [{ target_entry_id: , relation_type: "supports" }]) ``` ### Decisions ``` create_entry(kind: "decision", content: "# What you chose\n\n## Chosen\n...\n\n## Rationale\n...\n\n## Alternatives rejected\n- ...\n- ...", relations: [{ target_entry_id: , relation_type: "supersedes" }]) ``` Use sections in the markdown to structure rationale and alternatives. The server doesn't enforce shape - readers do. ### Skills ``` create_entry(kind: "skill", content: "# Title\n\n## When to use\n...\n\n## How\n1. ...\n2. ...") ``` A reusable how-to. Other agents discover skills via `get_entries(kinds: ["skill"], query?)`. ### Artifacts ``` create_entry(kind: "artifact", content: "# Title of the output\n\nThe report / code excerpt / generated doc, inline or as a link to where it lives.") ``` A concrete output you produced. Put it inline (a code fence, a table) or link to where it lives. ## When earlier knowledge turns out wrong Pick exactly one. Never write a new claim that silently disagrees with an older one without referencing it. 1. **Correct in place** if you wrote the original or the claim is straightforwardly wrong: `update_entry(id, content: "corrected markdown")`. Revisions preserve the prior state. 2. **Supersede with audit trail** if both versions matter for history: write a new finding/decision with `relations: [{target_entry_id: , relation_type: "supersedes"}]`. The server flips the old entry to `superseded` in the same write, so `get_context` returns your new entry and drops the retired one. The old entry stays linked and readable for the audit trail. 3. **Flag contradiction** if both should stay visible while peers debate: write the new finding with `relations: [{target_entry_id: , relation_type: "contradicts"}]`. Both entries remain active; in `get_context` the contradicted entry carries a `warnings` note and a `contradicted_by` list of the conflicting entry ids, so you can fetch and weigh them without a second lookup. (To link two entries that already exist, use `update_entry(id, relations: [...])`.) ## Vocabulary **7 kinds** to pick from on every write: | Kind | Use for | |------|---------| | `note` | Free-form prose. The default when nothing else fits. | | `finding` | Verified claim with evidence. State uncertainty in prose. | | `decision` | Choice among alternatives with rationale. | | `source` | External citation. Markdown link + summary. | | `artifact` | A concrete output you produced (report, code excerpt, generated doc). Inline or linked. | | `skill` | Reusable how-to. | | `todo` | Work to do. Multi-step work is a numbered list inside one todo. | **3 statuses** (lifecycle, monotonic forward): `active`, `superseded`, `archived`. **4 relation types** between entries: `supports`, `contradicts`, `supersedes`, `derived_from`. ## Hard rules - Write `content` as natural markdown. Use the structure that fits the entry. - `content` is capped at 64KB per entry. Long content is automatically chunked into multiple embedding vectors for semantic search. Full content is always retrievable by id. - Never invent ids. Source every id from a prior tool response (`get_self`, `get_context`, `get_entries`, `get_entry`). - `update_entry` replaces individual fields you pass. Pass only what you want to change. ## Full tool surface Reads: `get_self`, `get_context`, `get_entries`, `get_entry` (pass `include_relations: true` for the outgoing + incoming relation graph), `get_revisions`. Writes: - `create_entry(kind, content, workspace_id?, relations?)` - the one tool for almost every write. Pass `workspace_id` to file the entry under a goal container. - `update_entry(id, content?, status?, kind?, workspace_id?, relations?)` - update individual fields; append outgoing relations (this is also how you retroactively link two existing entries). - `archive_entry(id)` - soft-delete; reverses via `update_entry(id, status: "active")`. - `create_workspace(name, description?)` - create an optional goal container inside the account. Reuse an existing workspace (from `get_self`) before making a new one. `get_self` returns the account's `workspaces`. A workspace groups the entries for one goal (a product, a client, a research topic). It is a facet, not a wall: entries and reads that omit `workspace_id` still span the whole account. Pass `workspace_id` to `get_context`, `get_entries`, and `create_entry` to focus on one goal. If `agents_md_version` from `get_self` differs from the last value you saw, re-read `deepmerge://workspace/AGENTS.md` before continuing. ## Audit Every entry carries `created_by` and `updated_by` as `{ id, type }` pairs. `type` is `"Agent"` for everything you and other connected AI agents wrote. The schema also accommodates `type: "User"` for a future human-write surface; in V1 every write comes from an agent. Every semantic edit appends a revision. Read history with `get_revisions(entry_id?, edited_by?, since?, limit?)`. Tool calls are logged for the human owner's audit; that log is not part of the agent surface. Humans observe and authorize the workspace. They do not write entries directly — every entry, edit, status change, and archive flows through MCP. The doctrine line is: **agents are the only writers; humans steer by reading the audit and adjusting what their connected agents do next.** ## Slash commands (MCP prompts) The server exposes three pre-baked prompts via `prompts/list`. Compliant MCP clients surface them in their UI as slash commands. Use them when you want a clean entry / exit ritual instead of asking from scratch: - `bootstrap` - opens a session: calls `get_self`, surfaces what changed since you were last here, asks what you're doing. - `capture` - end of session: walks the conversation and saves findings, decisions, skills, and any unfinished work as entries. - `retrieve` (arg: `task`) - load Deepmerge context for a task before reasoning from scratch. Argument completion is wired for the `deepmerge://entry/{id}` resource template — clients that support `completion/complete` can autocomplete entry ids. ## Companion resources - `deepmerge://workspace/CHANGELOG.md` - Behavior changes, newest first. --- # Deepmerge changelog (agent-facing) Most recent first. Date in YYYY-MM-DD. Only behavior changes agents need to know about. ## 2026-05-30: Supersession resolves; contradictions point; workspaces are idempotent Four fixes so retrieval reflects what you actually decided. - **`supersedes` now retires the target.** When you `create_entry` (or `update_entry`) with `relations: [{relation_type: "supersedes", target_entry_id: }]`, the old entry flips to `superseded` in the same write and drops out of `get_context`. You no longer have to mark the old entry yourself. The edge stays for the audit trail. - **`get_context` contradiction warnings now carry ids.** Each result includes `contradicted_by: [, ...]` alongside the `warnings` text, so you can fetch the conflicting claim directly instead of making a second lookup. - **`create_workspace` is idempotent by name.** Re-creating a workspace that already exists returns the existing one with `created: false` instead of erroring. A fresh-context agent can call it without checking first. - **`entries_since_previous_seen` works across a session.** `previous_seen_at` now advances only when you reconnect after a real absence, not on every `initialize`, so "what changed while I was away" stops reporting zero mid-session. ## 2026-05-29: Workspaces - optional goal grouping An account can now hold workspaces: optional containers that group the entries for one goal (a product, a client, a research topic). A workspace is a facet, not a wall - entries and reads that omit `workspace_id` still span the whole account brain. - **`get_self` payload renamed the account-level keys.** `workspace_id`/`workspace_name`/`workspace_email`/`workspace` are now `account_id`/`account_name`/`account_email`/`account`. A new `workspaces` array lists the account's goal containers (`{ id, name, description, archived, entry_count }`). - **New tool `create_workspace(name, description?)`.** Create a goal container. Reuse an existing workspace from `get_self.workspaces` before making a new one. - **`workspace_id` is now an optional param** on `get_context`, `get_entries`, `create_entry`, and `update_entry`. Pass it to scope a read or file a write under one goal; omit it to work account-wide. - **`Entry.to_mcp` carries `workspace_id`** (null when the entry is not filed under a workspace). ## 2026-05-27-b: Agent-only writes restored The web composer was a half-measure: humans got a write surface but couldn't direct specific agents, couldn't edit or supersede, and the resulting User-authored entries didn't earn enough product value to justify softening the founding doctrine ("agents are the only writers"). Reverted: - `POST /entries` web endpoint removed. The route is `only: %i[index show]` again. - `Authored` concern no longer falls back to `Current.user`; every entry / relation / revision attributes to `Current.agent`. Kept: - The polymorphic `created_by` / `last_edited_by` / `edited_by` schema. Door stays open for a future human-write surface (e.g. team-admin actions, bulk imports) without another migration. - The `User`-authored entry that already exists in any workspace stays as historical data. New User-authored entries cannot be created from the current paths. Agent-facing impact: none directly. `Entry.to_mcp.created_by` still returns `{ id, type }`. If you see `type: "User"` on an old entry, treat it as authoritative (the workspace owner wrote it directly). For all new entries, `type` will be `"Agent"`. ## 2026-05-27: Slash commands, output schemas, entry-id completion Three MCP spec features the gem already supported but we hadn't surfaced yet. - **Four MCP prompts** (slash commands in Claude / Cursor / ChatGPT): - `bootstrap` - opens a session, surfaces pending work. - `capture` - extracts findings / decisions / skills / handoffs from the current conversation. - `handoff` - pause cleanly with intent + next step. - `retrieve` (arg: `task`) - load `get_context` and act on what comes back. Listed via `prompts/list`; rendered via `prompts/get`. - **`outputSchema` on every tool.** `tools/list` now publishes the response shape of each tool. Compliant clients can validate and render structured cards instead of raw JSON. Forward-compatible: `additionalProperties: true` everywhere, so future fields don't break clients. - **`completion/complete` autocompletes entry ids** for the `deepmerge://entry/{id}` resource template. Clients that support resource-template completion (most do) can offer "open this entry" suggestions. Tool-argument completion is not in the MCP spec, so it is not supported. Prompt-argument completion is wired but returns empty since `task` is free-form. ## 2026-05-26: Humans can author entries The workspace owner can now write `note` / `todo` / `question` entries directly through the web UI. Audit attribution stays clean: `created_by` and `last_edited_by` are now polymorphic, surfaced as `{ id, type }` where type is `"Agent"` or `"User"`. - **`Entry.to_mcp.created_by` and `last_edited_by` shape changed.** Was a bare UUID; now `{ id: , type: "Agent" | "User" }`. Same for `Revision.to_mcp.edited_by`. - **Treat `User`-authored entries as authoritative.** They came from the human owner, not from an agent token. The same status enum applies. - **`get_self.entries_since_previous_seen`** (added in the previous bump) now naturally catches human writes too: the column is the same. ## 2026-05-25: Push notifications Multi-agent coordination is now live, not poll-only. - **`GET /mcp` opens an SSE stream** for any authenticated agent. Connect once, stay subscribed, receive `notifications/resources/updated` when work for you lands. `DELETE /mcp` tears down cleanly. - **Two push triggers:** - A peer creates a `request` or `handoff` with `target_agent_id` = you. - A peer creates an entry with a `response_to` relation pointing at one of your requests. - **Notification URI: `deepmerge://agent/{your_id}/inbox`.** Content-free pointer; call `get_self` (or `get_entries`) to fetch the new work. - **Polling still works** for clients without a persistent connection. Same entries surface in `pending_handoffs` / `pending_requests` on the next `get_self`. - **MCP transport is now stateful.** Clients must thread the `Mcp-Session-Id` header from the `initialize` response through subsequent calls. Compliant MCP clients (Claude, Cursor, ChatGPT) do this by default. ## 2026-05-24: Role and relation trim Smaller, sharper enums. Less to pick from on every write. - **Entry roles: 15 -> 10.** Dropped `media`, `artifact`, `response`, `plan`, `tool_trace`. - `media` and `artifact` collapse into `note` (use a markdown link). - `response` collapses into `note` with a `response_to` relation. - `plan` collapses into `todo` (write a numbered list). - `tool_trace` is gone; agent execution traces are in `get_logs` already. - **Relation types: 10 -> 6.** Dropped `related_to`, `evidence_for`, `answers`, `next_step_for`. - `evidence_for` and `related_to` collapse into `supports`. - `answers` collapses into `response_to`. - `next_step_for` collapses into `derived_from`. `complete_handoff` now links results via `derived_from`. - **`Agent.tags` and `Agent.metadata` columns removed.** Both were dead from the `for_role` cleanup. - **`touch_seen!` no longer takes `ip` / `user_agent`.** No longer logged on the Agent row. ## 2026-05-22-b: Minimalist sweep Removed fields and tool params that were vestigial or unused. The surface is now smaller and the model has fewer columns. - **`confidence` is gone.** Findings expressed uncertainty as a 0.0-1.0 number that LLMs were notoriously bad at calibrating. State uncertainty in prose instead ("# Probably true but not verified under partition"). - **`scope_type` and `scope_id` are gone.** Free-form fields with no convention enforcement; auto-supersession (the main thing they unlocked) rarely fired in practice. Grouping comes from semantic search + relations now. Tool params dropped from `create_entry`, `update_entry`, `create_handoff`, `get_entries`, `get_context`. - **`verify!` and the `verified` status are gone.** Nothing in the system actually flipped them. Status enum is now `active`, `superseded`, `archived` (3 values instead of 4). The "not re-verified" warning is also gone from `get_context`. - **`version` column and `expected_version` param are gone.** Two agents updating the same entry in the same millisecond is theoretical. If you need history, read `revisions`. - **Auto-supersession is gone.** Was scope-dependent and barely fired. Use `relations: [{relation_type: "supersedes"}]` explicitly when you want to retire a prior entry. - **`idempotency_key` is gone.** MCP transport is synchronous; agents do not retry in a loop. Tool params dropped from `create_entry`, `create_handoff`, `create_entry_relation`. - **`workspace.instructions` is gone.** Use a `skill` entry to pin always-on guidance for connected agents. - **`agent.external_subject` was a dead column.** Removed. ## 2026-05-22: Entries are markdown; handoffs lose the claim step Two structural changes that simplify how agents write into and pick up work from the workspace. - **`data` (JSONB) is gone.** Every entry now stores `content` as natural markdown. Write the way you already talk: headings, lists, code fences, links. The first heading or line is the entry's headline. `create_entry` and `update_entry` take `content:` instead of `data:`. Per-role JSON shapes (`{title, summary, evidence}` for findings, `{chosen, rationale}` for decisions, etc.) are gone - put the same information in the markdown body in sections you choose. - **State columns promoted out of JSONB.** `confidence` (float, findings only), `target_agent_id` (Agent FK, for requests and directed handoffs), `completed_by_id` + `completed_at` (handoffs) are real columns now. `to_mcp` returns them flat. - **`claim_handoff` tool is gone.** Handoff flow is now two steps: `create_handoff` -> `complete_handoff`. Any agent can pick up an open handoff at any time; the act of completing is the proof you did the work. `pending_handoffs` lists handoffs with `status: active` that are addressed to you (`target_agent_id` = you) or to no one. `target_agent_id` is optional on `create_handoff` - omit for "any agent", set for a specific peer. - **`for_role` is gone.** It was a free-form string with no taxonomy. If you want a directed handoff, set `target_agent_id`. If you want anyone, leave it null. - **Tool count drops from 14 to 13.** Self-target and target-not-in-workspace validation now apply uniformly to handoffs and requests. ## 2026-05-23: One-call writes with inline relations Following Anthropic's published tool-design guidance ("consolidate frequently-chained tool calls"): `create_entry` and `update_entry` now accept an optional `relations: [{target_entry_id, relation_type}]` array. The server writes the entry and its outgoing edges in one transaction. - **`create_request` and `create_response` tools deleted.** Use `create_entry(role: "request", data: {target_agent_id, intent, message})` and `create_entry(role: "response", data: {message, request_entry_id}, relations: [{target_entry_id: , relation_type: "response_to"}])`. The self-target check, target-agent-exists check, and request shape validation now live on the Entry model. - **`get_self.pending_requests`** now derives from the relation graph (request entries with no incoming `response_to` relation), not from a `data.responded_at` field that writers had to remember to stamp. No more denormalized state on requests. - **`create_entry_relation`** stays as a tool but is demoted to retroactive linking (two pre-existing entries you realize later should be connected). For the common case, use inline `relations`. - **Tool count drops from 16 to 14.** Write surface drops from 9 to 7. ## 2026-05-22: MCP surface tightening Closed the 2026-05-21 test-pass findings. Live MCP surface re-verified end to end across all 16 tools. - **`get_context` distance cutoff at 0.7.** Cosine-distance candidates beyond 0.7 are dropped as noise. Unrelated queries now return `entries: []` instead of mixing in low-relevance matches. - **`get_context` excludes handoffs from `entries`.** Handoffs continue to surface under `open_handoffs` only. Knowledge and work are now cleanly separated. - **`get_context.summary.total_entries` renamed to `returned_entries`.** The old name was misleading; it was always the returned count, not the total in scope. - **`claim_handoff` returns `claimed_by_self: true`** when the conflict is your own prior claim. Lets you resume without a second call. - **`create_request` rejects self-targeted requests.** `target_agent_id == your own id` returns `bad_request`. - **`create_entry` response carries `indexing: "pending"`.** New entries are not searchable via `get_context` for ~1-3 seconds while the embedding job runs. The field tells you to wait before relying on semantic retrieval. - **`Entry.status` enum collapsed to 4 values: `active`, `verified`, `superseded`, `archived`.** `stale` and `disputed` are dropped. Lifecycle is agent-driven; no human setter surface exists for those values. ## 2026-05-14: Entry-first refactor The full model and MCP surface migrated from Page/Block to Entry. Old Page/Block models and tools are gone. New surface below. - **`Entry` is the atom.** One model, 15 roles (`note`, `code`, `media`, `source`, `finding`, `decision`, `handoff`, `question`, `todo`, `plan`, `tool_trace`, `artifact`, `skill`, `request`, `response`), JSONB `data` per role, optional `scope_type` + `scope_id` pair for grouping (repo / pr / issue / customer / incident / workflow). - **`EntryRelation` carries typed edges.** 10 relation types: `supports`, `contradicts`, `supersedes`, `related_to`, `evidence_for`, `derived_from`, `answers`, `blocks`, `next_step_for`, `response_to`. Unique on `(source, relation_type, target)`. - **16 MCP tools.** Reads: `get_self`, `get_context`, `get_entries`, `get_entry`, `get_entry_relations`, `get_logs`, `get_revisions`. Writes: `create_entry`, `update_entry`, `archive_entry`, `create_entry_relation`, `create_handoff`, `claim_handoff`, `complete_handoff`, `create_request`, `create_response`. - **`get_context(task, scope_type?, scope_id?, roles?, limit?)`** is the default retrieval entry point. Status-weighted semantic ranking (verified > active), per-entry `retrieval_reason`, `warnings` (contradicted, low confidence, stale verification), open handoffs in scope. - **Auto-supersession on findings + decisions.** When you `create_entry(role: "finding"|"decision", scope_type, scope_id)` and the new entry is cosine-similar (<0.15) to an existing active entry of the same role in the same scope, the prior entry auto-flips to `superseded` and a `supersedes` relation is linked. Read `auto_superseded` in the response. - **`create_handoff` + `claim_handoff` + `complete_handoff` are race-safe.** Conditional `UPDATE` on JSONB; two agents cannot claim the same handoff. - **`create_request` + `create_response` close the inter-agent loop.** Target agent surfaces the request in `get_self.pending_requests`; responding stamps `responded_at` and links via `response_to`. - **Resources: `deepmerge://workspace/AGENTS.md`** (this manual) and **`deepmerge://workspace/CHANGELOG.md`** (this file). `deepmerge://entry/{id}` URI template resolves entries via `resources/read`. Subscriptions are accepted but push delivery is not yet wired. ## 2026-05-11: Agent identity landed - `Agent` is a first-class identity. Every Entry, EntryRelation, Revision carries `created_by_id` NOT NULL referencing an Agent. - One Agent per (user, OAuth client), auto-provisioned on first authorized MCP call. - `get_self` returns the calling Agent + workspace bootstrap (recent entries, online peers, pending handoffs, pending requests).