Read-only local MCP service that turns Filipp's codex-workflow policy into structured workflow helpers for Codex.
The canonical policy remains the codex-workflow skill. This service only operationalizes stable policy data: bucket classification, bucket contracts, direct Obsidian context candidates, delegation suggestions, and finish checklists. V1 deliberately performs no writes to Obsidian, Codex config, git, or launchd.
- Label:
com.filipp.hermes-workflow-mcp - Runs in place from this repo:
~/Projects/workflow-mcp/(github.com/Leonorus/workflow-mcp) - MCP endpoint:
http://127.0.0.1:8813/mcp - Health endpoint:
http://127.0.0.1:8813/health - Logs:
~/Projects/workflow-mcp/logs/(gitignored) - Python:
~/.hermes/hermes-agent/venv/bin/pythonbecause system Python does not include the MCP SDK.
Codex config shape:
[mcp_servers.workflow]
url = "http://127.0.0.1:8813/mcp"Fresh Codex sessions may need restart to see newly registered MCP tools.
V2 exposes three MCP tools over nine workflow buckets (trivia, light_ops, heavy_ops, app_code, script, debug, research, repo_maintenance, ambiguous):
start_task— full start packet: bucket, visible statement, Codex skill names, Obsidian requirement, context candidates, delegation hint, contract, finish checklist,first_move,must_not_do_before,risk_axes,required_evidence,bucket_decision,reasoning_guard, andfinish_requirements. Optionalfieldsreturns only requested top-level fields; unknown field names come back underunknown_fields. Whenalready_classified_bucketoverrides the classifier, the packet includesoverride: {from, to}. The packet also carries atask_id(pass it tofinish_checklistfor learning correlation) and, when the bucket came from a prior correction of the same prompt, amemory: {from, to, ...}key.discover_context— direct-keyword Obsidian candidates fromProjects/<repo>/,Knowledge/, andOrganization/; accepts either a repo slug or absolute checkout path and returns paths/reasons/snippets only when requested. Optionalinline_top_nandinline_max_charsinline the top candidates' note bodies to avoid duplicate reads. Repo-slug tokens select roots and boost note paths but are excluded from relevance scoring.finish_checklist— verification/docs/note/memory/skill-maintenance checklist from bucket and changed files. Optionalrepo_root+auto_detect_changesasks git for changed/untracked paths instead of trusting caller-suppliedchanged_files. Phase 2 fields include required checks, missing verification/docs/notes/skill actions,unsafe_to_finalize, subagent/side-effect review reminders, and final-response requirements. Optionaltask_id(from the matchingstart_taskpacket) lets start/finish bucket disagreement feed classification learning; without it a repo/time heuristic is used.
classify_task and suggest_delegation remain importable from workflow_core but are no longer exposed as MCP tools (near-zero call volume; start_task subsumes both). Unknown bucket names return a structured {"error": "unknown_bucket", "closest": ...} result instead of a protocol error. validate_surfaces was removed entirely; the service runs straight from this repo, so there is no install copy to drift.
The MCP wrapper records privacy-safe per-call JSONL metrics to:
~/Projects/workflow-mcp/logs/calls.jsonl
Raw prompts are not logged. Events include prompt hash/word count, tool name, success, duration, service version, bucket/confidence where present, escalation flag count, candidate/checklist/task counts, selected fields, classifier overrides (override_from/override_to), memory hits (memory_from/memory_to), task_id/correlation, and error type. /health includes uptime, request count, error count, last error type, process-cached service version, current source version, and metrics path.
Analyze telemetry manually:
~/.hermes/hermes-agent/venv/bin/python ~/Projects/workflow-mcp/analyze_metrics.py --stdoutlearning.py wraps start_task/finish_checklist and appends feedback
events to ~/Projects/workflow-mcp/logs/feedback.jsonl — the one deliberate
exception to the hash-only telemetry rule: raw prompts persist there, but only
on feedback events (explicit override, start/finish bucket disagreement,
ambiguous or low-confidence classification), local-only and gitignored.
Two learning tiers:
- Exact-match memory (automatic). The feedback file is folded into an
in-memory overlay keyed by normalized-token signature. A prompt previously
corrected via
already_classified_bucketis re-delivered with the corrected bucket immediately; start/finish disagreements need 2 consistent occurrences. If the two most recent corrections for a prompt disagree, the entry is dropped (self-healing). Memory hits are visible (memoryin the packet,memory_from/memory_toin telemetry) and never downgrade safety — escalation flags and obsidian/reasoning-guard requirements stay prompt-derived. To prune a bad memory, delete its line fromfeedback.jsonl. - Generalized tuning (human-reviewed).
tune_report.pymines feedback + telemetry into a Markdown report: override/ambiguity rates per bucket, overlay status, keyword candidates, score diagnostics, and ready-to-paste golden cases. It never edits code or evals.
~/.hermes/hermes-agent/venv/bin/python ~/Projects/workflow-mcp/tune_report.py # stdout
~/.hermes/hermes-agent/venv/bin/python ~/Projects/workflow-mcp/tune_report.py --output ~/Obsidian/Work/Daily/Lint/$(date +%F)-workflow-mcp-tuning.mdScheduled telemetry report:
- Task:
workflow-mcp-analyzer - Label:
com.filipp.hermes-workflow-mcp-analyzer - Normal schedule: Monday 10:30 local time.
- Catch-up:
StartInterval=900with script state guard, so same-day missed runs retry without backfilling prior Mondays. - Output:
~/Obsidian/Work/Daily/Lint/YYYY-MM-DD-workflow-mcp-metrics.md - Dry-run:
HERMES_WORKFLOW_MCP_ANALYZER_DRY_RUN=1 ~/.hermes/scheduled-tasks/workflow-mcp-analyzer/run.sh - Force:
HERMES_WORKFLOW_MCP_ANALYZER_FORCE=1 ~/.hermes/scheduled-tasks/workflow-mcp-analyzer/run.sh
Seed evals live under evals/:
evals/golden.jsonl— classification and reasoning-guard adversarial cases.evals/context.jsonl— fixture-vault context relevance cases.
Run:
~/.hermes/hermes-agent/venv/bin/python ~/Projects/workflow-mcp/eval.pyRun from the repo checkout:
PY=~/.hermes/hermes-agent/venv/bin/python
cd ~/Projects/workflow-mcp
$PY -m pytest tests -q
$PY -m py_compile *.py
$PY eval.py
$PY smoke.py
zsh -n run.sh
plutil -lint com.filipp.hermes-workflow-mcp.plistlaunchd runs the server directly from this checkout — there is no separate install step. After changing service code:
launchctl kickstart -k "gui/$(id -u)/com.filipp.hermes-workflow-mcp"
curl -fsS http://127.0.0.1:8813/health # expect tools:3 and this repo's HEAD shaFirst-time launchd install:
mkdir -p ~/Projects/workflow-mcp/logs
cp ~/Projects/workflow-mcp/com.filipp.hermes-workflow-mcp.plist \
~/Library/LaunchAgents/com.filipp.hermes-workflow-mcp.plist
plutil -lint ~/Library/LaunchAgents/com.filipp.hermes-workflow-mcp.plist
launchctl bootstrap "gui/$(id -u)" ~/Library/LaunchAgents/com.filipp.hermes-workflow-mcp.plist
launchctl print "gui/$(id -u)/com.filipp.hermes-workflow-mcp" | sed -n '1,120p'
curl -fsS http://127.0.0.1:8813/health
codex mcp list
codex mcp get workflowIf iterating on an already-loaded label:
launchctl bootout "gui/$(id -u)/com.filipp.hermes-workflow-mcp" || true
launchctl bootstrap "gui/$(id -u)" ~/Library/LaunchAgents/com.filipp.hermes-workflow-mcp.plistlaunchctl bootout "gui/$(id -u)/com.filipp.hermes-workflow-mcp" || true
rm -f ~/Library/LaunchAgents/com.filipp.hermes-workflow-mcp.plist
# Remove or disable mcp_servers.workflow from ~/.codex/config.toml if needed.
codex mcp listIf hooks are later modified to call this service, keep their fallback static and verify they still emit valid {"context": "..."} JSON when this service is down.