Files
stack/packages/mosaic/framework/guides/TOOLS-REFERENCE.md
T
Hermes Agent e3a0ee87b3 framework: make tool discoverability, workspace placement and model tiering mechanical
An undocumented tool is, from inside an agent session, indistinguishable from a
tool that was never written. The framework shipped 26 git wrappers and named 6 of
them in its resident index docs — 23% discoverability, with pr-review.sh among the
missing. The observable consequence was an agent obeying Constitution gate 7 as
best it could see it, reaching for raw curl, sending GitHub's APPROVE to a Gitea
host, and getting HTTP 200 with the review silently filed PENDING. Three times.
That is not a discipline failure and no amount of prose fixes it.

Four changes, each converting a rule that decayed into a mechanism that cannot:

- check-tools-index.sh (new, CI-blocking): every tool in an enforced suite must be
  named in a resident index doc, and every tool an index names must exist. The git
  suite is enforced now; other suites report coverage without failing, so the
  ratchet tightens one reviewed PR at a time instead of landing as one sweep. The
  enforced list is framework-owned rather than a marker inside operator-owned
  TOOLS.md — a doc marker would let an operator silence the gate on exactly the
  host where it matters most. Carries --self-test, because a checker that only
  ever passes is indistinguishable from one that is not running.

- TOOLS-REFERENCE.md: complete 28-entry git index, plus the APPROVED/APPROVE
  dialect note that explains why pr-review.sh is not a formality.

- mosaic-worktree.sh + wrapper-guard.sh (upstreamed): the rule "big work goes on a
  work filesystem" already existed in prose, and 255 GB accumulated in $HOME across
  842 directories anyway, under five simultaneous placement conventions on one
  host. The helper therefore exposes no placement decision — given a branch name,
  every path is derived from `git worktree list --porcelain`. Worktrees rather than
  clones because enumerability is the only thing that makes reclaim safe, and
  reclaim is by evidence (clean tree + no unpushed commits), never by size or age.
  The guard blocks three mechanically-detectable mistakes and nothing else:
  a checkout into $HOME, a raw provider-API write to an endpoint that has a
  wrapper, and the literal APPROVE event. Reads pass untouched.

- STANDARDS.md: model tiering as a standard, named by capability class so it
  survives a model generation. Start cheapest, escalate on evidence, benchmark
  before demoting a task class, and keep the class->model binding in operator
  config with the DB-backed config service as the end state.

Registering the guard in runtime/claude/settings.json is the point of upstreaming
it: ~/.claude/settings.json is a framework-managed copy, so a hand-added hook there
is destroyed by the next upgrade. In the template it survives, and it reaches every
host instead of one.
2026-08-12 16:51:17 -05:00

16 KiB

Machine-Level Tool Reference

Centralized reference for tools, credentials, and CLI patterns available across all projects. Project-specific tooling belongs in the project's AGENTS.md, not here.

All tool suites are located at ~/.config/mosaic/tools/.

Tool Suites

Git Wrappers (Use First)

Mosaic wrappers at ~/.config/mosaic/tools/git/*.sh handle platform detection and edge cases. Always use these before raw CLI commands.

This index is complete and is kept complete mechanically: tools/quality/scripts/check-tools-index.sh fails CI when a wrapper ships without an entry here, or when an entry here names a wrapper that no longer exists. A wrapper missing from this list is, from inside an agent session, indistinguishable from a wrapper that was never written — which is how the APPROVE/APPROVED incident below happened.

Every command takes --help. All of them accept --login <account> to pin the acting identity; supply it explicitly on any host where the provider CLI's default account is an admin.

Issues
issue-create.sh Create an issue (Gitea or GitHub)
issue-view.sh Show one issue
issue-list.sh List issues
issue-edit.sh Edit title/body/labels/milestone
issue-comment.sh Add a comment
issue-assign.sh Assign or unassign
issue-close.sh Close an issue
issue-reopen.sh Reopen a closed issue
Pull requests
pr-create.sh Open a pull request
pr-view.sh Show one PR
pr-list.sh List PRs
pr-diff.sh Fetch a PR's diff
pr-metadata.sh PR metadata as JSON (head SHA, base, state, mergeability)
pr-review.sh Place a review verdict — see the dialect note below
pr-ci-wait.sh Block until the PR's CI reaches a terminal state
pr-merge.sh Merge a PR
pr-close.sh Close a PR without merging
Milestones
milestone-create.sh Create a milestone
milestone-list.sh List milestones
milestone-close.sh Close a milestone
Gates and guards
ci-queue-wait.sh CI queue guard — required before push/merge (see below)
push-guard.sh Refuse verifications that pass for the wrong reason (e.g. green against an unpushed tree)
mutate-push-guard.sh Regenerate the guard's mutation-coverage table from measurement, so the table cannot drift from the guard
verify-clean-clone.sh Prove the committed artifact runs, from a clean clone — not the working tree
Context
detect-platform.sh Resolve the provider (Gitea vs GitHub) for the current repo; every other wrapper uses it
lane-brief.sh Live dispatch brief for a repo "lane" (milestone/label) straight from the provider
Workspace
mosaic-worktree.sh Create/list/remove git worktrees — the only supported way; see below
wrapper-guard.sh PreToolUse hook that enforces the two rules above; not called by hand

Workspace placement is derived, not chosen. mosaic-worktree.sh new <branch> takes a branch name and nothing else. Every path comes out of git worktree list --porcelain — main worktree, repo name, parent dir, then <parent>/<repo>-worktrees/<branch-slug>. There is no placement flag because a decision an agent has to make is a decision that drifts: the rule "big work goes on a work filesystem" already existed in prose and 255 GB accumulated in $HOME across 842 directories anyway, under five simultaneous conventions on a single host.

~/.config/mosaic/tools/git/mosaic-worktree.sh new <branch> [--from <base>]
~/.config/mosaic/tools/git/mosaic-worktree.sh path <branch>   # derived path, no side effect
~/.config/mosaic/tools/git/mosaic-worktree.sh list            # this repo's worktrees + state
~/.config/mosaic/tools/git/mosaic-worktree.sh rm <branch>     # removal is part of the task
~/.config/mosaic/tools/git/mosaic-worktree.sh gc [--apply]    # reclaim clean + fully-pushed ones

Worktrees rather than clones, because git worktree list makes every checkout enumerable — a bare clone dropped somewhere on disk can never be safely reclaimed, so it is never reclaimed. rm and gc decide by evidence, never by size or age: a worktree is reclaimable only when git status --porcelain is empty and git rev-list --count HEAD --not --remotes is 0. Anything else is preserved and reported. --force exists and is yours to type deliberately.

wrapper-guard.sh is registered as a Claude Code PreToolUse hook on Bash (see runtime/claude/settings.json). It blocks exactly three things and lets everything else through: a git clone/git worktree add targeting $HOME; a raw provider-API write to an endpoint that already has a wrapper above (reads are untouched — they are how you gather evidence); and the literal "event": "APPROVE". For a genuine gap no wrapper can express, prefix MOSAIC_WRAPPER_OVERRIDE=1. Reaching for the override twice for the same call means the wrapper has a missing flag — extend the wrapper.

~/.config/mosaic/tools/git/issue-create.sh --help
~/.config/mosaic/tools/git/pr-review.sh --pr 42 --event APPROVED --body "..."

# CI queue guard (required before push/merge; defaults to the checked-out branch)
~/.config/mosaic/tools/git/ci-queue-wait.sh --purpose push|merge

Review dialect — the reason pr-review.sh is not optional. Gitea's approve event is APPROVED; GitHub's is APPROVE. Send GitHub's spelling to a Gitea host and it answers HTTP 200, files the review as PENDING, and then rejects the submit with 422 review stay pending — the verdict looks placed and is not. (REQUEST_CHANGES is spelled identically on both, so only the approve path carries the trap.) pr-review.sh sends the correct token for the detected provider. Whatever you use, re-read GET /pulls/{n}/reviews and assert the state before reporting a verdict placed.

The guard exits nonzero for any provider-asserted non-green, missing, or malformed CI state. If credentials or the provider are unavailable, it emits CANNOT_ASSERT and writes a JSONL audit record. Push degrades to exit 0 so recovery work is not bricked; merge holds with retryable exit 75 until the provider recovers, then self-clears without manual reset. Neither outcome is evidence that CI was clear. pr-merge.sh automatically inspects the exact PR head repository and full commit SHA rather than its main base; this also handles fork PRs without branch-name ambiguity. Pass --expect-head <approved-full-sha> to bind a commit-specific review or merge-gate verdict; Gitea uses atomic head_commit_id and GitHub uses --match-head-commit.

Code Review (Codex)

~/.config/mosaic/tools/codex/codex-code-review.sh --uncommitted
~/.config/mosaic/tools/codex/codex-security-review.sh --uncommitted

Infrastructure — Portainer

~/.config/mosaic/tools/portainer/stack-status.sh -n <stack-name>
~/.config/mosaic/tools/portainer/stack-redeploy.sh -n <stack-name>
~/.config/mosaic/tools/portainer/stack-list.sh
~/.config/mosaic/tools/portainer/endpoint-list.sh

Infrastructure — Coolify (DEPRECATED)

Coolify has been superseded by Portainer Docker Swarm in this stack. Tools remain for reference but should not be used for new deployments.

# DEPRECATED — do not use for new deployments
~/.config/mosaic/tools/coolify/project-list.sh
~/.config/mosaic/tools/coolify/service-list.sh
~/.config/mosaic/tools/coolify/service-status.sh -u <uuid>
~/.config/mosaic/tools/coolify/deploy.sh -u <uuid>
~/.config/mosaic/tools/coolify/env-set.sh -u <uuid> -k KEY -v VALUE

Identity — Authentik

~/.config/mosaic/tools/authentik/user-list.sh
~/.config/mosaic/tools/authentik/user-create.sh -u <username> -n <name> -e <email>
~/.config/mosaic/tools/authentik/group-list.sh
~/.config/mosaic/tools/authentik/app-list.sh
~/.config/mosaic/tools/authentik/flow-list.sh
~/.config/mosaic/tools/authentik/admin-status.sh

CI/CD — Woodpecker

Multi-instance support: -a <instance> selects a named instance. Omit -a to use the default from woodpecker.default in credentials.json.

Instance URL Serves
mosaic (default) ci.mosaicstack.dev Mosaic repos (git.mosaicstack.dev)
usc ci.uscllc.com USC repos (git.uscllc.com)
# List recent pipelines
~/.config/mosaic/tools/woodpecker/pipeline-list.sh [-r owner/repo] [-a instance]

# Check latest or specific pipeline status
~/.config/mosaic/tools/woodpecker/pipeline-status.sh [-r owner/repo] [-n number] [-a instance]

# Trigger a build
~/.config/mosaic/tools/woodpecker/pipeline-trigger.sh [-r owner/repo] [-b branch] [-a instance]

Instance selection rule: match -a to the git remote host of the target repo. If the repo is on git.uscllc.com, use -a usc. If on git.mosaicstack.dev, use -a mosaic (or omit, since it's the default).

DNS — Cloudflare

Multi-instance support: -a <instance> selects a named instance (e.g. personal, work). Omit -a to use the default from cloudflare.default in credentials.json.

# List zones (domains)
~/.config/mosaic/tools/cloudflare/zone-list.sh [-a instance]

# List DNS records (zone by name or ID)
~/.config/mosaic/tools/cloudflare/record-list.sh -z <zone> [-a instance] [-t type] [-n name]

# Create DNS record
~/.config/mosaic/tools/cloudflare/record-create.sh -z <zone> -t <type> -n <name> -c <content> [-a instance] [-p] [-l ttl] [-P priority]

# Update DNS record
~/.config/mosaic/tools/cloudflare/record-update.sh -z <zone> -r <record-id> -t <type> -n <name> -c <content> [-a instance] [-p] [-l ttl]

# Delete DNS record
~/.config/mosaic/tools/cloudflare/record-delete.sh -z <zone> -r <record-id> [-a instance]

IT Service — GLPI

~/.config/mosaic/tools/glpi/ticket-list.sh
~/.config/mosaic/tools/glpi/ticket-create.sh -t <title> -c <content>
~/.config/mosaic/tools/glpi/computer-list.sh
~/.config/mosaic/tools/glpi/user-list.sh

Health Check

# Check all configured services
~/.config/mosaic/tools/health/stack-health.sh

# Check a specific service
~/.config/mosaic/tools/health/stack-health.sh -s portainer

# JSON output for automation
~/.config/mosaic/tools/health/stack-health.sh -f json

Shared Credential Loader

# Source in any script to load service credentials
source ~/.config/mosaic/tools/_lib/credentials.sh
load_credentials <service-name>
# Supported: portainer, coolify, authentik, glpi, github, gitea-mosaicstack, gitea-usc, woodpecker, cloudflare, turbo-cache, openbrain

OpenBrain — Semantic Memory (PRIMARY)

Self-hosted semantic brain backed by pgvector. Primary shared memory layer for all agents across all sessions and harnesses. Stores and retrieves decisions, context, and observations via semantic search.

Credentials: load_credentials openbrain → exports OPENBRAIN_URL, OPENBRAIN_TOKEN

Configure in your credentials.json:

"openbrain": {
  "url": "https://<your-openbrain-host>",
  "api_key": "<your-api-key>"
}

REST API (any language, any harness):

source ~/.config/mosaic/tools/_lib/credentials.sh && load_credentials openbrain

# Search by meaning
curl -s -X POST -H "Authorization: Bearer $OPENBRAIN_TOKEN" -H "Content-Type: application/json" \
  -d '{"query": "your search", "limit": 5}' "$OPENBRAIN_URL/v1/search"

# Capture a thought
curl -s -X POST -H "Authorization: Bearer $OPENBRAIN_TOKEN" -H "Content-Type: application/json" \
  -d '{"content": "...", "source": "agent-name", "metadata": {}}' "$OPENBRAIN_URL/v1/thoughts"

# Recent activity
curl -s -H "Authorization: Bearer $OPENBRAIN_TOKEN" "$OPENBRAIN_URL/v1/thoughts/recent?limit=5"

# Stats
curl -s -H "Authorization: Bearer $OPENBRAIN_TOKEN" "$OPENBRAIN_URL/v1/stats"

Python client (if the OpenBrain client is on your PYTHONPATH):

python tools/openbrain_client.py search "topic"
python tools/openbrain_client.py capture "decision or observation" --source agent-name
python tools/openbrain_client.py recent --limit 5
python tools/openbrain_client.py stats

MCP (Claude Code sessions): When connected, mcp__openbrain__capture/search/recent/stats tools are available natively — prefer those over CLI when in a Claude session.

Rule: capture when you LEARN something. Never when you DO something.

Trigger Action Retention
Session start search + recent to load prior context
Architectural or tooling decision made Capture with rationale long or permanent
Gotcha or non-obvious behavior discovered Capture immediately medium
User preference stated or confirmed Capture permanent
Cross-project pattern identified Capture permanent
Prior decision superseded UPDATE existing thought (keep tier)

Never capture: task started, commit pushed, PR opened, test results, file edits, CI status.

Full protocol and cleanup tools: ~/.config/mosaic/guides/MEMORY.md Smart capture wrapper (enforces schema + dedup): ~/.config/mosaic/tools/openbrain/capture.sh

Excalidraw — Diagram Export (MCP)

Headless .excalidraw → SVG export via @excalidraw/excalidraw. Available as MCP tools in Claude Code sessions.

MCP tools (when connected):

Tool Input Output
mcp__excalidraw__excalidraw_to_svg elements JSON string + optional app_state SVG string
mcp__excalidraw__excalidraw_file_to_svg file_path to .excalidraw SVG string + writes .svg alongside
mcp__excalidraw__list_diagrams (none) Available templates (requires EXCALIDRAW_GEN_PATH)
mcp__excalidraw__generate_diagram name, optional output_path Path to generated .excalidraw
mcp__excalidraw__generate_and_export name, optional output_path Paths to .excalidraw and .svg

Diagram generation (list_diagrams, generate_diagram, generate_and_export) requires EXCALIDRAW_GEN_PATH env var pointing to excalidraw_gen.py. Set in environment or shell profile:

export EXCALIDRAW_GEN_PATH="$HOME/.config/mosaic/tools/excalidraw/excalidraw_gen.py"

Manual registration:

mosaic-ensure-excalidraw           # install deps + register with Claude
mosaic-ensure-excalidraw --check   # verify registration

Git Providers

Instance URL CLI Purpose
(add your git providers here)

Credentials

Location: (configure your credential file path) Loader: source ~/.config/mosaic/tools/_lib/credentials.sh && load_credentials <service>

Never expose actual values. Never commit credential files.

CLI Gotchas

(Add platform-specific CLI gotchas as you discover them.)

Safety Defaults

  • Prefer trash over rm when available — recoverable beats gone forever
  • Never run destructive commands without explicit instruction
  • Write it down — "mental notes" don't survive session restarts; files do