{"slug": "multi-agent-support-for-signet", "title": "Multi-agent support for Signet", "summary": "Solvr deployment requires multi-agent support for Signet, which currently only supports a single agent per install. The gap will be closed by implementing agent definitions, a registry with identity inheritance, and database schema changes to scope memories by agent_id and scope flag, with a hybrid memory model using a single SQLite DB and shared skills pool.", "body_md": "# Multi-agent support for Signet\n\n## Context\n\nSolvr deployment needs 3 agents (Dot, Rose, Miles) running on a single Mac Studio with one Signet daemon and one OpenClaw gateway. Today Signet is single-agent only — one identity, one memory pool, one personality per ~/.agents/ install. OpenClaw already supports multi-agent natively (agents.list + bindings in openclaw.json), so the gap is entirely on the Signet side.\n\nUser decisions:\n\n**Memory**: Hybrid — single SQLite DB, daemon enforces scope at API level (agent_id column + scope flag: global vs private)** Directory**:`~/.agents/agents/{name}/`\n\nsubdirectories, each with their own identity files (SOUL.md, IDENTITY.md, etc.)**Skills**: Shared pool at`~/.agents/skills/`\n\n, per-agent allowlist in agent.yaml**Harness scope**: OpenClaw + Compass only. Claude Code stays single-agent (it’s the operator’s personal agent).\n\n## Critical files\n\n`platform/daemon/src/daemon.ts`\n\n— HTTP server, memory schema, file watcher, harness sync (~4650 LOC)`platform/daemon/src/hooks.ts`\n\n— Request/response interfaces`platform/daemon/src/memory-config.ts`\n\n— Config loading`platform/core/src/types.ts`\n\n— AgentManifest, Memory types`platform/core/src/identity.ts`\n\n— Identity file detection, IDENTITY_FILES spec (line 56-97), loadIdentityFiles (line 206)`platform/core/src/skills.ts`\n\n— Skill registry, unification`surfaces/cli/src/cli.ts`\n\n— CLI commands (~4650 LOC)`surfaces/dashboard/src/`\n\n— SvelteKit dashboard`integrations/openclaw/connector/src/index.ts`\n\n— OpenClaw sync`integrations/openclaw/memory-adapter/src/index.ts`\n\n— Runtime plugin`~/.agents/agent.yaml`\n\n— User config manifest\n\n## Implementation phases\n\n### Phase 1: Core types — AgentDefinition + roster\n\nFile: `platform/core/src/types.ts`\n\n```\ninterface AgentDefinition {\n  readonly name: string;\n  readonly model?: string;\n  readonly skills?: readonly string[];\n  readonly personality?: string;  // relative path to SOUL.md\n}\n```\n\nExtend AgentManifest with optional `agents`\n\nfield:\n\n```\nreadonly agents?: {\n  readonly roster: readonly AgentDefinition[];\n};\n```\n\n### Phase 2: Agent registry — discovery + scaffold + inheritance\n\nNew file: `platform/core/src/agents.ts`\n\n`discoverAgents(agentsDir)`\n\n— scan`~/.agents/agents/*/`\n\nfor subdirs, return AgentDefinition[]`scaffoldAgent(name, agentsDir)`\n\n— create directory + template SOUL.md, IDENTITY.md`getAgentIdentityFiles(name, agentsDir)`\n\n— resolve identity file paths with**inheritance fallback**: check agent subdir first, fall back to root-level`~/.agents/`\n\nfor any missing file. Only SOUL.md and IDENTITY.md are expected to be overridden per agent. AGENTS.md, USER.md, TOOLS.md inherit from root by default.`resolveAgentSkills(agent, sharedSkills)`\n\n— filter shared pool by agent’s allowlist. Empty/missing list = all skills.\n\n**Identity inheritance** (addresses current gap in identity.ts\nwhere loadIdentityFiles only checks one basePath):\n\n```\nresolve(file, agentName):\n  1. ~/.agents/agents/{agentName}/{file}  ← agent override\n  2. ~/.agents/{file}                     ← root default\n```\n\n### Phase 3: DB schema — add agent scoping columns\n\nFile: `platform/daemon/src/daemon.ts`\n\nAdd to `requiredColumns`\n\narray at line ~4509:\n\n```\n{ table: 'memories', column: 'agent_id', type: 'TEXT DEFAULT \"default\"' }\n{ table: 'memories', column: 'scope',    type: 'TEXT DEFAULT \"global\"' }\n```\n\nExisting memories get agent_id=‘default’, scope=‘global’. Fully backwards compatible via ALTER TABLE — no migration.\n\n**FTS5 handling** — the FTS5 virtual table does NOT get an\nagent_id column. Instead, agent scoping is done as a post-join\nfilter on the memories table after the FTS5 MATCH:\n\n```\nSELECT m.* FROM memories_fts\nJOIN memories m ON memories_fts.rowid = m.rowid\nWHERE memories_fts MATCH ?\n  AND (m.scope = 'global' OR m.agent_id = ?)\nORDER BY bm25(memories_fts) LIMIT ?\n```\n\nThis avoids rebuilding the FTS5 virtual table and its 3 triggers (INSERT/UPDATE/DELETE at lines 4560-4584), which would be a breaking migration for existing databases. The post-join filter is slightly less efficient but the memories table is small enough (3441 rows currently) that it won’t matter.\n\nVector search (sqlite-vec) already joins through the embeddings table, so adding the same WHERE clause to the final JOIN is trivial.\n\n**Orphaned memory cleanup**: When an agent is removed via CLI,\ndon’t auto-delete memories. Instead mark them scope=‘archived’.\nA `signet agent purge <name>`\n\ncommand explicitly deletes.\n\n### Phase 4: Daemon API — scoped memory + agent endpoints\n\nFile: `platform/daemon/src/daemon.ts`\n\na) Helper: `buildAgentScopeClause(agentId?)`\n\n— returns SQL\nWHERE fragment. If agentId is provided, returns memories\nwhere scope=‘global’ OR agent_id matches. If omitted,\nreturns only scope=‘global’ (safe default).\n\nb) Update `/memory/search`\n\n(line ~1300), `/api/memories`\n\n,\nremember/recall handlers to accept `agentId`\n\nquery param.\n\nc) New endpoints:\n\n`GET /api/agents`\n\n— list discovered agents + roster`GET /api/agents/:name`\n\n— agent detail, resolved identity files, effective skills`POST /api/agents`\n\n— scaffold new agent`DELETE /api/agents/:name`\n\n— archive agent (marks memories, doesn’t delete directory — CLI handles that)\n\nFile: `platform/daemon/src/hooks.ts`\n\nAdd to RememberRequest and RecallRequest:\n\n```\nagentId?: string;\nscope?: 'global' | 'private';\n```\n\n(SessionStartRequest already has agentId.)\n\n### Phase 5: File watcher — watch agent subdirectories\n\nFile: `platform/daemon/src/daemon.ts`\n\n, `startFileWatcher()`\n\nAdd `~/.agents/agents/`\n\nto chokidar watch paths. On change\ninside an agent subdir, trigger sync for that agent only\n(not a full harness rebuild). Parse the agent name from the\nchanged file path.\n\n### Phase 6: Harness sync — per-agent OpenClaw workspaces\n\nFile: `platform/daemon/src/daemon.ts`\n\nNew function `syncAgentsToOpenClaw()`\n\ncalled from\n`syncHarnessConfigs()`\n\n(line ~3750):\n\nFor each agent in the roster:\n\n- Resolve merged identity (root defaults + agent overrides)\n- Write assembled AGENTS.md to a per-agent workspace dir\nat\n`~/.agents/agents/{name}/workspace/`\n\n- Call connector to patch openclaw.json (see Phase 8)\n\n### Phase 7: CLI — `signet agent`\n\ncommands\n\nFile: `surfaces/cli/src/cli.ts`\n\nNew subcommand group:\n\n`signet agent list`\n\n— discovered agents + roster status`signet agent add <name>`\n\n— scaffold, prompt for SOUL.md personality description, add to roster in agent.yaml`signet agent remove <name>`\n\n— archive memories, remove from roster, optionally delete directory`signet agent purge <name>`\n\n— permanently delete agent memories and directory`signet agent info <name>`\n\n— resolved identity, skills, memory stats\n\nAdd `--agent <name>`\n\nflag to:\n\n`signet remember`\n\n— sets agent_id + scope on memory`signet recall`\n\n— scopes search to agent’s visible memories\n\n### Phase 8: OpenClaw connector — multi-agent sync\n\nFile: `integrations/openclaw/connector/src/index.ts`\n\nNew `syncMultipleAgents(roster, basePath)`\n\nmethod:\n\nCurrently the connector patches `agents.defaults.workspace`\n\nto `~/.agents`\n\n(line 257-275, deep merge). For multi-agent:\n\na) Build `agents.list`\n\narray from roster:\n\n```\n{\n  agents: {\n    defaults: { workspace: basePath },\n    list: roster.map(agent => ({\n      id: agent.name,\n      name: agent.name,\n      workspace: join(basePath, 'agents', agent.name, 'workspace'),\n      skills: agent.skills ?? undefined,\n      identity: {\n        // resolved SOUL.md content for this agent\n      }\n    }))\n  }\n}\n```\n\nb) Optionally generate `bindings`\n\narray if user provides\nchannel routing config in agent.yaml.\n\nc) Keep the deep-merge pattern — idempotent, safe to re-run.\n\nFile: `integrations/openclaw/memory-adapter/src/index.ts`\n\nThe adapter already accepts `agentId`\n\non `onSessionStart()`\n\n(line 66-100). The gap is that `createPlugin()`\n\n(line 274)\ndoesn’t extract agentId from the OpenClaw session context.\nFix: read `ctx.agentId`\n\n(provided by OpenClaw when it\nresolves which agent handles the current chat) and pass it\nthrough to all daemon calls.\n\n### Phase 9: Dashboard — agent roster view\n\nFile: `surfaces/dashboard/src/`\n\na) New API functions in `lib/api.ts`\n\n:\n\n`listAgents()`\n\n→ GET /api/agents`getAgent(name)`\n\n→ GET /api/agents/:name`createAgent(name)`\n\n→ POST /api/agents\n\nb) Add agent context to LeftSidebar.svelte (section 01, currently just “Identity”). Show a dropdown/switcher listing discovered agents. Selected agent scopes the Memory tab’s queries.\n\nc) New “Agents” tab in center panel (7th tab alongside Config, Memory, Embeddings, Logs, Secrets, Skills):\n\n- Agent cards showing name, personality summary, skill count, memory count\n- Add/remove agent actions\n- Per-agent memory stats\n\nd) Update `+page.ts`\n\ndata loader to fetch agent list on\ninitial load.\n\n### Phase 10: Setup wizard — multi-agent configuration\n\nFile: `surfaces/cli/src/cli.ts`\n\n, `setupWizard()`\n\n(line 1413)\n\nAdd a new path in the existing setup wizard decision tree:\n\nAfter detecting an existing Signet install (line 1447), add option 7: “Configure multiple agents”. This launches a sub-wizard:\n\n- “How many agents?” → prompt for count\n- For each agent: name, personality description (writes SOUL.md), skill allowlist (from available skills)\n- “Configure OpenClaw routing?” → optional bindings setup\n- Writes agents.roster to agent.yaml\n- Scaffolds directories, triggers sync\n\nFor fresh installs (line 1552), after single-agent identity creation, ask: “Set up additional agents?” → enters the same sub-wizard.\n\n## Updates and migrations\n\n**Existing installs upgrading to multi-agent Signet**:\n\n- DB columns added via\n`requiredColumns`\n\nALTER TABLE — this is the established live-upgrade pattern (daemon.ts:4509). No migration script needed. Existing memories get agent_id=‘default’, scope=‘global’ automatically. - FTS5 virtual table is NOT modified — no rebuild needed.\n- agent.yaml gains optional\n`agents.roster`\n\nfield — old configs without it continue working (single-agent mode). - The\n`signet setup`\n\nwizard detects whether agents.roster exists and shows appropriate options. - Dashboard gracefully handles zero agents (shows single identity view as today).\n\n**Version compatibility**: The daemon serves both single-agent\nand multi-agent installs. All new API params are optional with\nsensible defaults (no agentId = default agent = current\nsingle-agent behavior).\n\n**Update checker** (daemon.ts:2652-2710) is unaffected — it\nchecks npm/GitHub for new versions, not schema changes.\n\n## Edge cases\n\n**FTS5 + agent scoping**: Post-join filter instead of FTS5\ncolumn. Slightly less efficient but avoids breaking migration.\nAt 3441 memories this is negligible. If scale becomes an issue\nlater, rebuild FTS5 with agent_id column behind a flag.\n\n**Concurrent agent writes**: SQLite WAL mode + busy_timeout=5s\n(daemon.ts:1098) handles this. Two agents writing\nsimultaneously: one waits up to 5s. At expected write volumes\n(a few memories per session) this is fine. Not a hot path.\n\n**Deleted agent with orphaned memories**: Memories are NOT\nauto-deleted when an agent is removed. They’re marked\nscope=‘archived’ and excluded from search. Explicit\n`signet agent purge`\n\ndeletes permanently. This prevents\naccidental data loss.\n\n**Agent with no SOUL.md override**: Falls back to root\n~/.agents/SOUL.md via inheritance chain. Agent still works\nbut has the default personality. `signet agent info`\n\nshows\nwhich files are inherited vs overridden.\n\n**Empty skills allowlist**: Means “all skills” (same as\nOpenClaw convention where omitting `skills`\n\n= all). An\nexplicit empty array `skills: []`\n\nmeans no skills.\n\n**Config validation**: agent.yaml parsing currently has no\nschema validation (memory-config.ts uses unsafe casts, yaml.ts\nsilently produces NaN). This is a pre-existing issue, not\ncaused by multi-agent. We should add basic validation for the\nnew `agents.roster`\n\nfield (name required, unique names,\nallowlist entries exist in skills pool) but a full validation\noverhaul is out of scope.\n\n**Race in harness sync**: If two agent identity files change\nwithin the 2s debounce window, both trigger sync. The sync\nfunction is idempotent (writes final state, not incremental),\nso this is safe — worst case is two syncs instead of one.\n\n## What we are NOT doing\n\n- NOT creating multiple daemon instances\n- NOT creating multiple OpenClaw instances\n- NOT changing Claude Code behavior (stays single-agent)\n- NOT rebuilding FTS5 virtual table (post-join filter instead)\n- NOT adding per-agent databases (single DB, column scoping)\n- NOT adding crypto-level memory isolation (API-enforced scope is sufficient — HIPAA compliance comes from on-prem + FDE, not from memory partitioning within a trusted daemon)\n- NOT overhauling config validation (out of scope, pre-existing)\n\n## Verification\n\n`bun test`\n\npasses after each phase`bun run build`\n\nsucceeds`bun run lint`\n\nclean- Single-agent regression: existing install with no agents.roster works identically to before\n`signet agent add dot`\n\nscaffolds ~/.agents/agents/dot/ with template SOUL.md, IDENTITY.md`signet agent list`\n\nshows roster with resolved skills`signet remember \"test\" --agent dot`\n\nstores with agent_id=‘dot’, scope defaults to ‘global’`signet remember \"private note\" --agent dot --private`\n\nstores with scope=‘private’`signet recall \"test\" --agent dot`\n\nreturns global + dot’s private memories`signet recall \"test\"`\n\nwithout —agent returns global only- Dashboard shows agent roster in sidebar, agent tab with cards, memory tab respects agent selector\n`GET /api/agents`\n\nreturns discovered agents from daemon- OpenClaw config gets agents.list entries after sync\n- File changes in ~/.agents/agents/dot/ trigger sync for dot only (check daemon logs)\n`signet agent remove dot`\n\narchives memories, removes from roster", "url": "https://wpnews.pro/news/multi-agent-support-for-signet", "canonical_source": "https://signetai.sh/docs/specs/approved/multi-agent-support/", "published_at": "2026-07-22 23:48:45+00:00", "updated_at": "2026-07-23 00:03:24.084856+00:00", "lang": "en", "topics": ["ai-agents", "developer-tools", "artificial-intelligence"], "entities": ["Signet", "OpenClaw", "Solvr", "Compass", "Claude Code"], "alternates": {"html": "https://wpnews.pro/news/multi-agent-support-for-signet", "markdown": "https://wpnews.pro/news/multi-agent-support-for-signet.md", "text": "https://wpnews.pro/news/multi-agent-support-for-signet.txt", "jsonld": "https://wpnews.pro/news/multi-agent-support-for-signet.jsonld"}}