SkillAgentSearch skills...

willow-mcp

Agent-neutral MCP server with persistent memory (SOIL + Postgres KB) and a sandboxed task queue. Manifest-based ACL; works with any stdio MCP client.

Install / Use

claude mcp add willow-memory -- npx -y github:willow-memory/willow-mcp

If the server publishes to npm under a different name, use that package instead — check the repo README.

About this skill
🔌

MCP Server

Model Context Protocol server

Quality Score

74/100

Supported Platforms

Claude Code
Claude Desktop

willow-mcp

PyPI License: Apache 2.0 MCP

Agent-neutral MCP server with persistent memory and task execution. Works with any MCP client: Claude Code, Claude Desktop, Cursor, or any custom agent that speaks stdio MCP.

Corpus memory: the whole Willow constellation — the code, human, and collaboration corpora assembled and queryable — lives in the sovereign willow-compose repo. The hub calls it; it doesn't live here (keeping the hub lean).

Three storage backends in one server:

  • SOIL store — SQLite-backed local key/value store with full-text search and soft delete
  • Postgres knowledge base — multi-keyword searchable knowledge graph
  • Kart task queue — sandboxed task executor for shell commands and scripts

Every tool call is authorized via a filesystem-based manifest ACL — no ACL database, no external auth service. See Authorization.

Install

pip install willow-mcp

Requires Python 3.11+. Postgres is optional — SOIL store works standalone.

willow-mcp-init    # scaffold $WILLOW_HOME (idempotent)
willow-mcp-compile --force   # compile manifests (use product venv — see below)
willow-mcp-sign-seed hanuman # ratify home seed + detach-sign (operator terminal only)
willow-mcp-compile-persona hanuman # seed → personas/hanuman.md (AS-7)

Local sandbox (one command)

To take a fresh clone to a working stdio server — venv, editable install, scaffolded $WILLOW_HOME, compiled manifests, and (best-effort) a local Postgres with every table created — run:

bash scripts/sandbox-bootstrap.sh   # idempotent; ends with a live diagnostic_summary

On a bootstrapped sandbox the schema mappings for the tables the script itself just created are auto-confirmed (so task_* and knowledge writes work immediately), behind three guards: existing mapping artifacts are never touched, every field must resolve exact, and the live columns must equal the repo's own DDL — an adopted/foreign database always falls through to the human schema_confirm_mapping path (see src/willow_mcp/sandbox_confirm.py).

It scaffolds a repo-local, gitignored .willow/ so the sandbox never touches your real fleet state. Postgres is optional and handled best-effort (the SOIL store stands alone); pass WILLOW_SKIP_PG=1 for a SOIL-only stand-up, or WILLOW_PG_BOOTSTRAP_ROLE=1 on a bare cluster where your OS user has no Postgres role yet.

A fresh Postgres database needs willow-mcp's tables. On a shared fleet DB they already exist; on a standalone install, apply the DDL in docs/schema/ (knowledge, agents, routing_decisions, tasks — the four diagnostic_summary checks for, plus frank_ledger for the FRANK governance chain). The bootstrap script applies all of them for you. Each knowledge/tasks write path stays locked behind schema_confirm_mapping until you confirm the mapping once.

The fleet (one command up)

sandbox-bootstrap.sh proves this server works alone. Two sibling packages attach to it — jeles, the verified-corpus organ this package already depends on for institutional search, and nestor, which mirrors its hash-chained ledger into FRANK — and standing all three up together is a different claim:

bash scripts/fleet-standup.sh    # idempotent; ends with six seam checks

It runs the sandbox bootstrap, installs the jeles and nestor checkouts editable into that same venv (across two venvs their imports silently resolve to whatever PyPI last published), seats nestor in the gate, writes $WILLOW_HOME/fleet.env, and then checks that the seams actually join:

| Seam | What crosses | |------|--------------| | co-install | one venv, all three resolving to the checkouts | | shared SOIL store | jeles' corpus and this server's Store on one SQLite file | | gap forward | a jeles corpus miss → gap_log, through the manifest ACL | | institutional search | this server's willow_institutional_search → jeles' ~60 collections | | FRANK mirror | a nestor ledger entry → frank_append → the hash chain | | nugget bridge | a jeles nugget → nestor, as a draft — never a seal |

Point JELES_REPO / NESTOR_REPO at the checkouts if they are not siblings of this one. Re-check any time with .venv/bin/python scripts/fleet_seams.py (--json for machine output); every seam it reports as passing was exercised by writing real data through the real path, because a seam that is only imported is a seam that has not been tested.

PATH note: ~/.local/bin/willow-mcp is often the fleet shim (sap_mcp.py), not this product. Use the venv binary from wherever you ran pip install willow-mcp (or pip install -e . in a clone) — e.g. .venv/bin/willow-mcp-compile --force or .venv/bin/willow-mcp compile-agents --force — not a bare willow-mcp on PATH.

Runtime layout: docs/design/product-layout.md (LOCKED).

Tools

| Tool | Description | |------|-------------| | store_put | Write record (JSON object) to SQLite store | | store_get | Read record by record_id | | store_list | List all records in a collection | | store_update | Update an existing record | | store_search | Multi-keyword AND search in a collection | | store_delete | Soft-delete a record by record_id | | store_search_all | Search across all collections | | store_collections | List the SOIL collections you can see (narrowed to your store_scope) — learn the collection names without running a search | | store_purge_collection | Bulk soft-delete every record in a collection (e.g. leftover test/scratch data). Reversible (archive-don't-delete — the store.db is kept); requires confirm=<collection name> and stays within your store_scope | | store_stats | Per-collection live-record counts (within your store_scope), largest first, plus store-wide totals — the numeric companion to store_collections for spotting a bloated or polluted collection | | knowledge_ingest | Add a knowledge atom (requires a confirmed schema mapping — see schema_confirm_mapping) | | knowledge_search | Multi-keyword search in the Postgres knowledge base | | kb_at | Fetch a single knowledge atom by ID | | kb_promote | Change an atom's domain (requires a confirmed schema mapping) | | knowledge_flag | Attach an integrity flag to an existing atom (knowledge_curate; tags-based, idempotent) | | knowledge_retract | Tombstone an atom in place — hidden from default search, still readable via kb_at (knowledge_curate) | | kb_journal | Add a journal-domain knowledge atom (requires a confirmed schema mapping) | | kb_startup_continuity | Fetch atoms tagged/domained for startup continuity | | schema_confirm_mapping | Confirm (optionally correct) a table's column mapping, unlocking its write tools. preview=True dry-runs it and renders a sample row so you can see what each field actually resolves to before trusting a name match — see docs/design/schema-adaptation.md | | gap_log | Log or bump a "we don't know this yet" entry (fleet-wide backlog, SOIL-only, no Postgres needed) — see docs/design/gap-backlog.md | | gap_list | List gaps, most-asked first — filter by topic and/or status (open/resolved/promoted) | | gap_resolve | Mark a gap as being worked or answered — bookkeeping only, does not write to the knowledge base | | gap_delete | Soft-delete a single gap by id — clear junk/test entries without disturbing real gaps. Reversible (archive-don't-delete) | | gap_purge_topic | Soft-delete every gap under an exact topic in one call — bulk cleanup without the per-call rate limit. Promoted gaps (they point at a landed atom) are left intact; requires confirm=<topic> | | gap_promote | Turn a resolved gap into a knowledge atom. Requires answer, at least one source, and confirmed_by; writes through the same schema-confirmation gate as knowledge_ingest and closes the gap out | | nest_scan | Walk a drop folder, extract + classify its files by meaning, and write a canonical SQLite Nest DB. Returns counts only; dry_run=True (default) reports without writing — see docs/NEST.md | | nest_status | Counts for a seeded Nest DB — sources by status, fragments by type, topical categories by size. Structure only; filename-labels are walled and counted as uncategorised | | nest_digest | A one-page Markdown map of a Nest DB — the walled view (person names, the date timeline, and filenames suppressed). The full digest is a local-CLI affordance only, never returned over MCP | | nest_promote | Promote a Nest's structure — counts, curated category names, redacted secret kinds, never content — into the knowledge base via the same core write as knowledge_ingest. dry_run=True returns the atoms that would be promoted | | nest_intake_scan | Live drop-folder router: classify new files in a drop zone by filename into a track and stage a review queue. Non-destructive — nothing moves until nest_intake_file | | nest_intake_queue | List the pending review queue with the track the classifier predicted for each file | | nest_intake_file | File a staged item: move the file to its predicted track's destination, or override_dest to correct it. An override feeds the correction counter | | nest_intake_skip | Skip a staged item — leave the file, record the decision | | nest_intake_flags | List open rule-delta flags — patterns overridden often enough that the classifier proposes a rules change (a human ratifies) | | task_submit | Submit task to Kart queue | | task_status | Check task status | | task_list | List pending tasks | | agent_route | Route a task to a target agent, recording the decision | | agent_dispatch_result | Record the result of a dispatched agent task | | dispatch_send | Create dispatch packet (meta.json + assignment.md) | | dispatch_read | Read dispatch assignment and status | | dispatch_list | List dispatch packets | | dispatch_accept | Specialist accepts packet (pending → working) | | handoff_write_v4 | Complete work — handoff.json + closeout.md | | handoff_read | Read handoff for a dispatch | | verify_handoff | Orchestrator verifies completion | | agent_clear | Clear specialist for next packet | | session_read | Read thin session state file | | fleet_status | Return the canonical charter fleet.json roster plus Postgres drift diagnostics | | fleet_health | Task queue counts by status, live worker heartbeats, and whether the queue is stranded | | frank_read / frank_verify | Read and verify the existing Postgres FRANK hash chain | | frank_append | Append an established-shape FRANK event (separately gated) | | envelope_apply | Match an active constitutional grant and write its FRANK citation before returning authority | | grove_list_channels | List active Grove channels (name, type, description) | | grove_get_history | Message history from a channel, with since_id polling and limit (max 200) | | grove_search | Case-insensitive substring search across Grove messages, optionally scoped to one channel | | grove_watch / grove_watch_all | Non-blocking poll for new messages in one channel, or many at once via a {channel: since_id} cursor map | | grove_get_thread | A message plus its flags and all its replies | | grove_bus_receive | Structured bus messages addressed to an agent (or broadcast), priority-ordered | | grove_inbox | Fleet inbox: @mentions, bus messages addressed to you, and your de

Truncated for display — read the full file on GitHub.

Related Skills

View on GitHub
GitHub Stars3
CategoryData
Updated8h ago
Forks0

Languages

Python

Security Score

92/100

Audited on Sep 15, 2026

1 low