MCP Verbs
softfig-mcp is the bridge that lets a Claude session propose garden writes.
It is a stateless stdio server, spawned per session, that forwards each verb to
the running softfig-keeperd daemon over its IPC socket. Reads of the garden are
plain filesystem operations — Claude just reads the mounted tree — so the verbs
below are the write surface plus a few coordination reads. The server
lists 43 tools. Registering the
server is covered in Register softfig-mcp; the
practice of working through it is Working with
Claude.
The stamping principle
Section titled “The stamping principle”Every write verb is convention-stamping: the daemon owns all the mechanical
fields — dates, sequence numbers, # title and > Last reviewed: headers,
filenames, index tables, [[…]] backlinks — and the commit. The caller emits
only the irreducible new content. Each successful write is one typed commit whose
intent classifies it (decision_logged, note_added, …), which is what makes
history queryable by what changed.
Journal and records
Section titled “Journal and records”| Verb | Key params | Purpose |
|---|---|---|
log_decision |
slug, body, summary? |
Write journal/decisions/decision-<slug>.md with a stamped # decision: <title> header and date. |
log_incident |
slug, summary, body, date? |
Write journal/incidents/incident-<date>-<slug>.md with a stamped header. |
add_note |
dir, slug, body, title? |
Append a numbered note NNN-slug.md to a notes/ or troubleshooting/ folder (.seq, title, > Last reviewed: stamped). |
revise_note |
dir, id, body |
Replace the body of a numbered note; number, slug, and title stay fixed; reviewed date re-stamped. |
add_code_review |
dir, slug, body, title? |
Append a numbered record to a code-reviews/ folder (same machinery as add_note). |
add_project |
name, repo_path?, summary? |
Scaffold projects/<name>/ with its reserved-name stubs in one commit. |
refresh_snapshot |
path, content |
Write caller-supplied content under snapshots/. The daemon never runs your data-gathering command. |
archive |
src, archive_name? |
Move a tracked path under journal/archive/<name>/ and rewrite references to it. |
Edit and delete
Section titled “Edit and delete”Sections are addressed by heading text (case-sensitive, level-agnostic, #
prefix optional; the heading must match exactly one). expected_version is an
optional compare-and-swap guard (whole-file for file verbs, per-section for
section verbs); seed it with read_versions. editor is an optional per-agent
identity for the contention detector in multi-agent fleets.
| Verb | Key params | Purpose |
|---|---|---|
add_section |
path, heading, body |
Append a new section (leading #s set the level, default ##). The heading must not already exist. |
edit_section |
path, heading, body, expected_version?, editor? |
Replace a section’s body, keeping its heading. |
append_to_section |
path, heading, text, expected_version?, editor? |
Add a row, bullet, or line to the end of a section. |
remove_section |
path, heading, expected_version?, editor? |
Delete a section (heading, body, and subsections). Refused for the file’s last heading or a daemon-managed region. |
patch_file |
path, old, new, anchor?, expected_version?, editor? |
Exact old→new replacement; old must occur exactly once in the search window (the file, or the line range of anchor). Refused if it would change a daemon-managed softfig: region. |
set_reviewed |
path |
Bump the doc’s Last reviewed: line to today. |
unlink |
path, expected_version?, editor? |
Delete one file. Refused if it appears in an index region or has inbound [[…]] links; use archive for those. Files only. |
batch |
ops, editor? |
Apply several sub-ops as one atomic commit. Allowed ops: patch_file, edit_section, append_to_section, add_section, remove_section, set_reviewed, add_note, revise_note. Every op is validated before anything is written; any failure aborts with nothing changed. |
replace_file |
path, content, expected_version? |
Break-glass. Overwrite a file with verbatim bytes and no stamping. Discouraged; use it only when no structural verb fits (e.g. rewriting a monolithic CLAUDE.md). |
Reads and coordination
Section titled “Reads and coordination”These return data or arbitrate coordination; they don’t stamp garden content.
| Verb | Key params | Purpose |
|---|---|---|
read_versions |
path |
A file’s current CAS version tokens (whole-file and per-section) without its content. Also flags vault-sealed paths. |
file_provenance |
path, limit? |
Who last edited a path and its recent edit history (default 20), from the commit chain. |
read_inbox |
agent |
Read the agent’s unread coordination-bus messages (direct and @all) and advance its cursor. |
list_reports |
tags? |
List filed reports carrying all given tags (ns:* matches any value). Read-only. |
request_lease |
agent, key |
Request a supervisor-arbitrated lease over a shared resource. Returns granted, waiting, or denied. Needs growlightd; no commit. |
release_lease |
agent, key |
Release a held lease and promote the head waiter. No commit. |
Growlight: bus and reports
Section titled “Growlight: bus and reports”| Verb | Key params | Purpose |
|---|---|---|
post_message |
from, to, kind, body |
Post to the coordination bus. to is an agent slug, @all, or @human; kind is info, coord-request, lease-request, question, alert, or restart-request. |
file_report |
title, body, tags, item?, from?, slug? |
File a numbered report under growlight/reports/. Tags need exactly one type: (bug, regression, flake, security, doc-drift, finding, question, blocker, idea) and may carry severity: (critical/high/medium/low) plus free tags. Blockers, security reports, and critical severity also alert @human. |
update_report |
number, status?, add_tags?, remove_tags?, note?, from? |
Move a report along: status open, triaged, resolved, wontfix, duplicate. Closing requires a note. |
Growlight: queue and slices
Section titled “Growlight: queue and slices”The queue is the ordered work list. The first group is the current queue; every
mutating verb there takes dry_run (validate and render, write nothing). Human
CLI equivalents are under CLI → Queue and slices.
| Verb | Key params | Purpose |
|---|---|---|
queue_add_project |
name, repo, main?, dry_run? |
Register a project: a name bound to a repo and its merge branch. Human-only. |
queue_add_entry |
kind (milestone/task/restack), project, slug?, title?, mission?, finish_criteria?, lane?, after?, position?, ref?, slices?, dry_run? |
Create a backlog entry, its docs, and its queue row in one commit. A fleet member may add only normal-lane milestones and tasks (found work), at the back. |
queue_update_entry |
id, lane?, move?, after?, status?, dry_run? |
One atomic patch-style edit of a live entry, applied lane, then move, then links, then status (queued/blocked/deferred/done). Human-only. |
queue_drop |
id, reason, dry_run? |
Archive a live entry as dropped. Human-only. |
slice_insert |
milestone, slug, body, title?, position?, ref?, dry_run? |
Append a numbered slice to a milestone. A fleet member may insert only within its own current entry. |
slice_update |
milestone, id, move?, status?, dry_run? |
Move a slice or set its status. A fleet member may only move, within its own entry. |
Older table-based backlog verbs, still shipped for gardens using the original backlog table:
| Verb | Key params | Purpose |
|---|---|---|
add_backlog_item |
slug, item_type, title, mission, finish_criteria, queue? |
Seed a milestone or task and enqueue it. |
add_queue |
name, repo |
Register a named work-stream queue bound to a repo path. |
add_slice |
milestone, slug, body, title? |
Append a numbered slice doc under a milestone. |
set_item_status |
id, status, queue? |
Set an item’s status (queued, active, done, blocked, deferred). At most one active. |
reorder_backlog_item |
id, position (top/bottom/before/after), ref_id?, queue? |
Move an item’s row without changing status. |
log_baton |
summary, iteration, item?, slice?, status?, … |
Append a numbered iteration entry to the append-only baton-log/. |
Growlight: fleet-member verbs
Section titled “Growlight: fleet-member verbs”These five work only inside a fleet member session (growlightd launches
members with SOFTFIG_MEMBER set; elsewhere they are refused not_a_member).
Each ends the session and hands the baton on, so a member calls one last.
A refusal leaves the session running with an error code to correct.
| Verb | Key params | Purpose |
|---|---|---|
growlight_complete_slice |
summary, baton |
This session’s slice is done. Refused for tasks, restacks, and close-out units. |
growlight_complete_entry |
outcome (done/deferred), summary, baton, deferred_tests? |
Close out a milestone, task, or restack. deferred requires the pending manual tests. |
growlight_add_follow_up_session |
reason, baton, model?, variant?, new_slice? |
End early and run a fresh session on the same unit. Reasons: context_full, mcp_updated, daemon_restarted, session_fault, model_change, new_scope, preempted. Capped at 5 per unit. |
growlight_block |
kind (human/stuck), reason, baton |
Park the entry until a human unblocks it. |
growlight_add_sub_milestone |
slug, title, mission, finish_criteria, slices, baton, do_not_prioritize? |
Split the current slice’s remaining work into a sub-milestone (two levels deep at most). |
Deliberately absent: the Vault
Section titled “Deliberately absent: the Vault”There is no MCP verb for any vault operation — no reveal, seal, unseal, list-sealed, unlock, key rotation, or trust change. The verb registry contains no such entry; the only mention of the vault in the surface is the guard that refuses edits to sealed targets.
This is structural, not a matter of trust. Vault actions require the unlocked key
and human-only policy that live in the daemon; they are CLI actions you take at a
terminal (see softfig reveal / vault seal). Handing a
garden to an assistant never hands it your secrets.
Related: Register softfig-mcp (setup), Working with Claude (the practice), CLI Reference (the human-facing surface), and Conventions (what the stamping enforces).