HITL delegations (solomon actions)
solomon actions is the human-in-the-loop delegation surface behind the Ops Delegation board. An autonomous worker posts an Action, a human resolves it (or the stock action-items widget does, via drag/checkbox), and a blocking await polls until it leaves the pending set. The shared status/answer rules are implemented once, so the CLI and the Ops board agree byte-for-byte.
One Action = one markdown note in Actions/ carrying hitl: v1 frontmatter.
Why actions always prefers a force-mode key
Every other CLI command uses the default token as-is (propose by default). actions is different: it must apply to the live CRDT doc — the Ops Delegation board reads the live doc, and a propose-scoped key would only stage a snapshot the board never sees. So actions looks for a force-mode key in mcp.json's keys[] first, and only falls back to the default (propose) token — with a loud warning that the posted Action will be staged and invisible on the board until approved — when no force key exists.
actions also uniquely opts in to the synced-vault auto-upgrade: when the app is closed and the resolved local target is also a synced vault (has a .solomon/vault.json marker) and a sync credential resolves, the transport auto-upgrades from the raw-fs single-writer core to the Hocuspocus sync peer — so the write actually reaches other devices/the cloud instead of sitting fs-only until the desktop app reseeds it. If a sync credential does not resolve, the CLI still writes fs-only but prints a loud warning ("other devices will NOT see this Action"). Every other CLI command keeps today's fs-only behavior when the app is closed.
Subcommands
solomon actions post
Creates an Action note under Actions/; prints {id, rel_path, transport} as JSON (transport is one of "loopback", "local-fs", "sync-peer" — which transport actually served the create, per the transport-truth-pass contract).
Flags:
--kind <question|approval|task>— required.--title <text>— required, non-empty; mirrors the note's H1 and (sanitized) filename.--detail <text>— optional one-line summary.--option <text>(repeatable) — an option for aquestion; mirrored into- ( ) Optionbody lines. Only valid with--kind question.--body-file <path>— file whose contents become the note's body prose.--from <agent>— requesting agent/stream. Defaults to$SOLOMON_AGENT, elsecli.
solomon actions post --kind question --title "Pick storage" \
--option R2 --option Railway --detail "Need a call before Friday" --from research-agent
The CLI stamps a ULID id itself (so the app-closed create path — which writes content verbatim and never stamps — still yields a stable handle) and dedupes colliding titles: Actions/<name>.md, then Actions/<name> 2.md, Actions/<name> 3.md, … up to 999 tries. Only a genuine "already exists" collision advances the suffix.
solomon actions list
Lists protocol Actions (hitl: v1 only) as a JSON array, newest first.
--status <open|resolved|all>— defaultopen.
solomon actions list --status open
solomon actions await <target>
Blocks until an Action leaves the pending set, then prints the outcome JSON. Re-runs the whole transport decision on every poll — it holds no lock across the wait, so a concurrent resolve in another shell can still land, and it works with the desktop app closed.
target— an Action id or anActions/…mdpath.--timeout <secs>— give up after this many seconds (default: wait forever).--interval <ms>— poll interval, default1500.
Exit codes: 0 answered/done · 2 timeout (still open) · 3 rejected · 1 error.
solomon actions await 01J... --timeout 3600
solomon actions resolve <target>
Records a human decision on an Action. First resolution wins: the CLI re-reads the note immediately before writing and refuses (no write) if the status already left the pending set.
target— an Action id or anActions/…mdpath.- Exactly one verb is required:
--answer <text>[--free] — answer a question. Must match one of the note's options unless--freeis passed. (--answeris only valid on aquestion.)--approve— approve an approval →done. (Only valid on anapproval.)--reject[--reason <text>] — reject →rejected(valid on approval/task/question);--reasonis stored as the answer.--done[--note <text>] — complete a task →done. (Only valid on atask.)--noteis stored as the answer.
solomon actions resolve 01J... --answer Railway
solomon actions resolve "Actions/Ship it.md" --approve
solomon actions resolve 01J... --reject --reason "too risky"
Status vocabulary and the widget-compatibility mapping
An Action stays blocking (await still waits) while its raw status is one of the pending set (case-insensitive): open, to do, in progress, waiting. open is the protocol's own initial status; to do/in progress/waiting are what the stock action-items widget's status select writes when a human drags a card without going through resolve.
A raw status maps to the protocol's canonical vocabulary (open | answered | done | rejected):
- anything in the pending set →
open; - the widget's terminal column,
complete, →answeredwhen an answer extracts (frontmatteranswer, a body**Answer:**line, or a checked- (x)option), elsedone; answered/done/rejectedpass through (normalized to lowercase);- anything else non-pending is reported verbatim.
Answer extraction order: frontmatter answer, then the body's **Answer:** line, then the text of a checked - (x) option, else None. Empty strings never count as an answer. This lets a human resolve entirely through the widget (check an option, drag to Complete) and still have await/list report the correct canonical status and answer — the CLI and widget share the exact option/**Answer:** line grammar.
Body layout
A posted Action's body is: an # <Title> H1, optional prose (from --detail/--body-file), then — for a question — an unchecked - ( ) <Option> line per --option. On resolve, the matching option is checked (- (x)), every other option is left unchecked, any prior **Answer:** line is dropped, and a single **Answer:** <answer> line is appended at the end — so re-resolving never stacks duplicate Answer lines.
Result object shape (list/await/resolve output)
{
"id": "01J...",
"rel_path": "Actions/Pick storage.md",
"kind": "question",
"status": "answered",
"answer": "Railway",
"requested_by": "research-agent",
"created": "2026-07-11T18:09:12Z",
"resolved": "2026-07-12T09:00:00Z"
}
Related
- [[solomon CLI reference]] — global flags, the three-transport model, and
the other 17 subcommands
actionssits alongside. - [[MCP tool surface]] — the propose-vs-force key model
actionsalso reads out ofmcp.json. - [[Bases and widgets]] — the stock
action-itemswidget whose status/answer conventions this protocol shares byte-for-byte.