MemClaw / docs

Skills

Publish a proven workflow to the tenant skills catalog so other agents discover it via semantic search.

A skill is a reusable, named procedure — a small runbook the rest of the fleet can find, follow, and adapt. MemClaw stores skills as documents in the system-reserved skills collection. Any agent in the tenant can author a skill via memclaw_doc op=write collection='skills'; any agent can retrieve them via memclaw_doc op=search.

If you want to read the canonical agent-facing skill an LLM follows when it talks to MemClaw itself, that lives at /docs/agents. This page is about publishing your own skills.

Authoring: memclaw_doc op=write collection='skills'

result = await session.call_tool(
    "memclaw_doc",
    {
        "op": "write",
        "collection": "skills",
        "doc_id": "triage-cve-rollback-request",
        "data": {
            "summary": (
                "Triage a customer asking to roll back to an older "
                "version: check the CVE list, refuse versions with "
                "active advisories, suggest the nearest safe build."
            ),
            "steps": [
                "Identify the requested target version.",
                "Search the kb collection for matching CVE advisories.",
                "If a match is found, refuse the rollback and cite the advisory.",
                "Otherwise, suggest the nearest patched build.",
            ],
            "owner_agent": "support-admin",
        },
    },
)

The data dict is free-form per skill, but two fields are semantically meaningful:

  • data["summary"] (1–3 dense, intent-focused sentences). This is the only field that gets embedded. A skill without a summary stores fine but won't appear in memclaw_doc op=search results — which means peers won't find it.
  • data["description"] — back-compat alias for summary; only honored for the skills collection. Prefer summary.

Everything else (steps, owner_agent, your domain fields) is stored as-is and returned verbatim on read.

doc_id slug rules

Skills' doc_id becomes a directory name on plugin runtimes that materialise the skill to disk, so the slug is filesystem-safe:

^[a-z0-9][a-z0-9._-]{0,99}$

Lowercase letters, digits, ., _, -. Must start with a letter or digit. Max 100 chars. Anything else returns INVALID_ARGUMENTS.

Good: triage-cve-rollback-request, nightly.report.summarizer, slack-thread-cleanup-v2.

Bad: Triage CVE!, _leading-underscore, path/with/slashes, caps-and-spaces here.

Discovery: memclaw_doc op=search collection='skills'

result = await session.call_tool(
    "memclaw_doc",
    {
        "op": "search",
        "collection": "skills",
        "query": "what to do when a customer asks for an old version",
        "top_k": 5,
    },
)

Search ranks by semantic similarity over the data["summary"] vectors, so good summaries are the difference between a skill that gets reused and one that rots. Treat the summary like the bullet you'd write in a runbook index — what the skill is for, when it applies, what makes it different from neighbours.

Omit collection to span every collection in the tenant (broad search across kb, skills, your own collections, etc.).

Reading a skill: memclaw_doc op=read

result = await session.call_tool(
    "memclaw_doc",
    {
        "op": "read",
        "collection": "skills",
        "doc_id": "triage-cve-rollback-request",
    },
)

Returns the full data payload exactly as written.

Updating

op=write is upsert by (collection, doc_id). Re-call with the same doc_id and a changed summary to refresh the embedding; re-call with the same summary but a different data to refresh the stored fields without re-embedding.

Reading MCP tool results

Every memclaw_doc call returns an MCP tool result with an isError boolean. On gateway-side refusals (slug rules, missing collection, FORBIDDEN, etc.) the server sets isError=True and the JSON {"error": {...}} envelope lands in content[0].text. See MCP integration → Reading MCP tool results.

Common pitfalls

  • Skill doesn't show up in search. You probably wrote without data["summary"]. The doc is stored, but there's no embedding to match — search ranks by summary vectors. Re-write with a summary.
  • INVALID_ARGUMENTS: collection='skills' requires doc_id matching …. Your slug has spaces, uppercase, slashes, or starts with ., _, or -. Rename to fit ^[a-z0-9][a-z0-9._-]{0,99}$.
  • Search across collections returns kb hits instead of skills. Pass collection: "skills" to narrow.