Skip to content

feat(mcp): self-diagnostics report tool, operator read tools, multi-runtime installer, honesty rollup (WO-59) - #5481

Merged
vivekchand merged 6 commits into
mainfrom
feat/self-diagnostics-mcp
Sep 4, 2026
Merged

vivekchand merged 6 commits into
mainfrom
feat/self-diagnostics-mcp

Conversation

@vivekchand

@vivekchand vivekchand commented Sep 3, 2026

Copy link
Copy Markdown
Owner

Product record: https://factory.8090.ai/project/b415065f-ab2f-4f53-8864-0c009fd098cb/requirements/c2329afe-0337-4d09-97b6-b6b7c7e260a1 (Self-diagnostics and operator tools over MCP, REQ-SELF-001 to 004; WO-59).
Blueprint: https://factory.8090.ai/project/b415065f-ab2f-4f53-8864-0c009fd098cb/blueprints/04b881ea-5b10-4fbe-95cc-71dca015793f (Components: Self-Diagnostics and Operator Tools over MCP).

Why

Reasoning models are good at introspection when asked plainly. Given a reporting tool framed as notes to the people who run them, an agent will say that a tool kept failing, that it lacked context or a permission, that it could not finish, or that it worked around a block. ClawMetry already watches the same session from outside, so every such note can sit next to what the detectors and the approval hooks recorded on their own, and the operator can see when the agent stayed quiet about something the detectors caught. The same MCP server is where a developer's editor can ask ClawMetry questions from where they already are.

What

MCP server (clawmetry/mcp_server.py)

  • report_to_operator — category (six defaults, operator-extendable via ~/.clawmetry/config.jsonself_diagnostics.categories; the enum is resolved at tools/list time), summary capped at 500 chars, session_id inferred from env (CLAUDE_SESSION_ID & co.) or a working-directory match through the daemon when absent, runtime inferred from the session prefix or env. Described as feedback to the people who run the agent, never as confession.
  • Read tools: list_incidents (window/runtime/session), get_guard_status (per-session verdict from process_control.runtime_control_support, policy decisions, open incidents, read_only: true), get_signal_rates (returns "signals not available on this daemon version" when the daemon lacks the method rather than failing), list_self_reports.
  • No tool acts on a process. The test suite pins tool names against actuating verbs. Every tool answers through the daemon and distinguishes daemon down / daemon busy (timeout) / daemon too old (allowlist refusal) in plain words.

Store (clawmetry/local_store.py) — additive agent_self_reports table (CREATE TABLE IF NOT EXISTS, no schema bump). ingest_self_report redacts the summary with clawmetry.redaction before the row is written; the raw text is never stored. Read methods: query_self_reports, query_self_report_counts, query_self_report_honesty, query_guard_incidents (loop_signals + session model), query_session_denials (approvals with status='denied'), find_session_by_cwd. All named in _DAEMON_METHODS; the MCP process writes only via /__local_query__/ingest_self_report (the daemon keeps the writer lock).

Daemon (clawmetry/sync.py, clawmetry/self_diagnostics.py) — on the detector tick, corroborate_pending marks a report corroborated when a detector incident's stretch or a permission denial lies within CORROBORATION_WINDOW_SECS (600; CLAWMETRY_SELFDIAG_WINDOW_SECS) of it, with the nearest incident winning so a bypassed_block report links to the block it worked around. Honesty per (runtime, model) = incidents the agent also reported / incidents; withheld with a reason under MIN_INCIDENTS (5; CLAWMETRY_SELFDIAG_MIN_INCIDENTS). Snapshot slice selfReports = {window_secs, total, corroborated, byRuntime{runtime:{category:n}}, honesty[], min_incidents, corroboration_window_secs}; no summaries leave the node. Try/except guarded.

CLI (clawmetry/mcp_install.py, fast path in cli.py, no dashboard import) — clawmetry mcp install [--runtime <id>|all] [--dry-run] [--write-guidance] [--json], clawmetry mcp uninstall, clawmetry mcp status. Per runtime: registered | already present | no MCP support | unknown config format (+ not installed, would register, removed, left in place). Merge only; a foreign entry is never deleted; a marker file (~/.clawmetry/mcp_installed.json) records what we wrote and uninstall removes only that; an entry named clawmetry we did not write is left in place; a JSONC file is reported and never rewritten. The guidance snippet for CLAUDE.md / AGENTS.md / GEMINI.md is printed and written only with --write-guidance.

Installer formats, each verified before writing:

runtime file verified against
claude_code ~/.claude.jsonmcpServers (type: stdio) code.claude.com/docs/en/mcp
cursor ~/.cursor/mcp.jsonmcpServers cursor.com/docs/context/mcp
codex ~/.codex/config.toml[mcp_servers.clawmetry] ran the vendor's own codex mcp add into a scratch CODEX_HOME and read what it wrote (the docs site redirects to a host WebFetch cannot verify)
gemini_cli ~/.gemini/settings.jsonmcpServers geminicli.com/docs/tools/mcp-server
opencode ~/.config/opencode/opencode.jsonmcp (type: local, command: [..], enabled) opencode.ai/docs/mcp-servers + /docs/config
windsurf ~/.codeium/windsurf/mcp_config.jsonmcpServers docs.windsurf.com/windsurf/cascade/mcp

Nine runtimes are declared no MCP support with a one-line reason; every other observed runtime reports unknown config format and its file is never touched.

Routes (routes/selfdiag.py, bp_selfdiag) — GET /api/self-reports?window=&runtime=&category=&session=, GET /api/self-reports/honesty?window=&runtime=, GET /api/self-reports/support. Reads go through the daemon proxy.

UI — transcript view gains "What the agent reported" (time order, category chip, summary, Corroborated / Uncorroborated with the hover text "No independent evidence was found for this report. That is not the same as false."). Guard tab gains an "Agent reported" card: per-category counts per runtime, the honesty table with the withheld reason, and honest empty states including "this runtime has no MCP support" and "not registered yet, run clawmetry mcp install". No em dashes in user-facing copy. The Signals tab is being built in parallel; this lands on Guard as agreed.

Verification

  • tests/test_self_diagnostics.py: 49 tests. Tool schema and framing; no actuating tool; honest error for every tool when the daemon is down, busy, or too old; redaction + cap on the write path; idempotency; session inference from env then cwd; inclusive window logic; corroboration against real loop_signals and approvals rows via corroborate_pending; honesty withheld/computed and the env floor; installer merge / never-delete / uninstall-only-ours / hand-written-left-in-place / JSONC-refused for all six formats, Codex block round-tripped through tomllib; guidance offered not written; CLI fast path never imports dashboard; routes; snapshot slice; live templates carry the new surfaces.
  • Guards proven red on unfixed code: removing the redaction call fails test_ingest_applies_redaction_and_cap; making uninstall clobber the container fails 5/6 test_install_merges_and_uninstall_removes_only_ours. Both restored to green.
  • Added to the moat-tests job in ci.yml and make test-selfdiag. make lint pieces: daemon allowlist OK (147 entries), py39 unions OK, AC ratchet holding, ruff clean on the new files, node --check on app.js.
  • Neighbouring suites green: CLI help no-dashboard-import, local query API + dispatch edge cases, query contract drift, guard control route, tab drift guard, no-direct-get_store, guard census, tab template page wrapper (after rebase onto [RELEASE] Fix Guard panel rendering on top of every tab, stuck on Loading (0.12.806 regression) #5476).
  • CodeQL round 1 (2 alerts) fixed: the window parser strips input before an unambiguous regex (polynomial ReDoS), and the support matrix served to the dashboard carries a fixed sentence instead of exception text (stack-trace exposure).
  • Live: python -m clawmetry mcp served tools/list with the ten tools against the running daemon; the running 0.12.80x daemon was not answering its local query server in this session, and the tool reported "running but did not answer within 10s" rather than "not running".
  • ~/.clawmetry/nocloud was absent after the test run and restored with the documented content.

Not in this PR

  • The hosted dashboard interceptor for selfReports (clawmetry-cloud); the Guard card and transcript panel show an honest "local store" state on cloud until it lands.
  • Self-reports as a policy match condition (named as a later feature in the requirement).
  • /api/signals (parallel work); get_signal_rates calls query_signal_rates through the daemon and says so when it is absent.

🤖 Generated with Claude Code
https://claude.ai/code/session_01ERBVJarDT4Vw1hJox5efBB

@8090-software-factory

Copy link
Copy Markdown

✅ Drift Bot (ClawMetry): no drift detected

Drift Bot analyzed the changed files against this project's blueprints and requirements and found no drift.

Comment thread clawmetry/self_diagnostics.py Fixed
Comment thread routes/selfdiag.py
except Exception as e: # noqa: BLE001
log.debug("mcp support matrix failed: %s", e)
rows = []
return jsonify({"runtimes": rows, "count": len(rows)})
@github-actions

github-actions Bot commented Sep 3, 2026

Copy link
Copy Markdown
Contributor

Visual diff

Comparing b30b911c1f6d (head) against the PR base branch.

33 of 72 comparison(s) flagged (>1% pixel diff).

View Before After Diff
desktop overview ⚠️ before after diff · 100.00%
desktop flow before after diff · 0.06%
desktop brain before after diff · 0.01%
desktop usage before after diff · 0.00%
desktop crons before after diff · 0.14%
desktop memory before after diff · 0.01%
desktop security ⚠️ before after diff · 1.32%
desktop subagents before after diff · 0.00%
desktop transcripts ⚠️ before after diff · 1.77%
desktop logs ⚠️ before after diff · 5.13%
desktop skills before after diff · 0.28%
desktop models before after diff · 0.00%
desktop approvals before after diff · 0.25%
desktop alerts before after diff · 0.02%
desktop notifications before after diff · 0.27%
desktop limits before after diff · 0.00%
desktop clusters ⚠️ before after diff · 1.42%
desktop history before after diff · 0.00%
desktop channels ⚠️ before after diff · 1.32%
desktop dives before after diff · 0.00%
desktop harness before after diff · 0.00%
desktop inventory before after diff · 0.02%
desktop nemoclaw before after diff · 0.00%
desktop guard ⚠️ before after diff · 100.00%
desktop signals ⚠️ before after diff · 2.41%
desktop policy before after diff · 0.03%
desktop selfevolve ⚠️ before after diff · 1.71%
desktop swimlane before after diff · 0.00%
desktop tool-catalog ⚠️ before after diff · 1.82%
desktop tracing before after diff · 0.00%
desktop turn-anatomy before after diff · 0.26%
desktop version-impact before after diff · 0.00%
desktop context-economics before after diff · 0.01%
desktop agents before after diff · 0.00%
desktop evals before after diff · 0.00%
desktop bench before after diff · 0.00%
mobile overview ⚠️ before after diff · 100.00%
mobile flow before after diff · 0.03%
mobile brain ⚠️ before after diff · 6.39%
mobile usage ⚠️ before after diff · 4.15%
mobile crons before after diff · 0.01%
mobile memory before after diff · 0.02%
mobile security ⚠️ before after diff · 2.76%
mobile subagents ⚠️ before after diff · 100.00%
mobile transcripts before after diff · 0.01%
mobile logs ⚠️ before after diff · 1.47%
mobile skills before after diff · 0.01%
mobile models before after diff · 0.00%
mobile approvals before after diff · 0.01%
mobile alerts before after diff · 0.00%
mobile notifications before after diff · 0.01%
mobile limits before after diff · 0.00%
mobile clusters ⚠️ before after diff · 2.61%
mobile history before after diff · 0.00%
mobile channels before after diff · 0.01%
mobile dives ⚠️ before after diff · 2.91%
mobile harness ⚠️ before after diff · 3.60%
mobile inventory ⚠️ before after diff · 2.35%
mobile nemoclaw ⚠️ before after diff · 4.57%
mobile guard ⚠️ before after diff · 100.00%
mobile signals ⚠️ before after diff · 4.82%
mobile policy before after diff · 0.00%
mobile selfevolve ⚠️ before after diff · 1.56%
mobile swimlane ⚠️ before after diff · 3.67%
mobile tool-catalog ⚠️ before after diff · 3.53%
mobile tracing ⚠️ before after diff · 100.00%
mobile turn-anatomy ⚠️ before after diff · 2.72%
mobile version-impact ⚠️ before after diff · 3.07%
mobile context-economics ⚠️ before after diff · 3.53%
mobile agents ⚠️ before after diff · 2.55%
mobile evals ⚠️ before after diff · 18.42%
mobile bench ⚠️ before after diff · 4.00%

Folder: b30b911c1f6d. Full PNGs also attached as a workflow artefact.

Generated by visual-diff bot. Pixel diffs >1% flagged; eyeball the table before merging. This check is non-blocking — fail = bot bug, not a code problem.

@8090-software-factory

Copy link
Copy Markdown

✅ Drift Bot (ClawMetry): no drift detected

Drift Bot analyzed the changed files against this project's blueprints and requirements and found no drift.

github-actions Bot pushed a commit that referenced this pull request Sep 4, 2026
@8090-software-factory

Copy link
Copy Markdown

✅ Drift Bot (ClawMetry): no drift detected

Drift Bot analyzed the changed files against this project's blueprints and requirements and found no drift.

github-actions Bot pushed a commit that referenced this pull request Sep 4, 2026
@vivekchand
vivekchand force-pushed the feat/self-diagnostics-mcp branch from 9aea97b to c6eb640 Compare September 4, 2026 06:45

Copy link
Copy Markdown
Owner Author

Drift-bot: clean. Test plan for review.

This PR adds MCP self-diagnostics (WO-59): a report tool, operator read tools, multi-runtime installer, and honesty rollup. Suggested test checklist before merge:

MCP report tool

  • Call the diagnostics report tool from an MCP client; confirm the response includes server name, version, tool list, and latency samples — no fabricated or stale data
  • Call with a missing/invalid session — confirm the response is available: false with a reason, not an exception or a zero-filled row

Operator read tools

  • Exercise each operator read tool (session list, transcript, usage summary) and confirm results match what /api/sessions, /api/transcript/<id>, and /api/usage return
  • Confirm none of the read tools accept write parameters (no side-effects through MCP surface)

Multi-runtime installer

  • Run the installer against each supported runtime config (Claude Code, Codex, Cursor CLI); confirm the MCP server entry is injected without clobbering existing entries
  • Run the installer twice on the same config; confirm it is idempotent (no duplicate entries)
  • Run on a config with a foreign writer's entry at the same key; confirm hook_ownership.py granularity rules apply (foreign entry is not overwritten)

Honesty rollup

  • Confirm the rollup reports advisory_only when no enforcement proxy is running (not a false "held")
  • Confirm each capability axis (OS, session vs runtime, proxy status) is reported independently rather than as a single combined verdict

Regression

  • make test passes; existing /api/brain-stream SSE clients unaffected

Generated by Claude Code

@8090-software-factory

Copy link
Copy Markdown

✅ Drift Bot (ClawMetry): no drift detected

Drift Bot analyzed the changed files against this project's blueprints and requirements and found no drift.

github-actions Bot pushed a commit that referenced this pull request Sep 4, 2026
@vivekchand
vivekchand force-pushed the feat/self-diagnostics-mcp branch from c6eb640 to 797a80c Compare September 4, 2026 07:39
@8090-software-factory

Copy link
Copy Markdown

⚠️ Drift Bot (ClawMetry): 1 potential drift finding(s)

1. Blueprint: Self-Diagnostics and Operator Tools over MCP

File: routes/local_query.py:1055

The blueprint specifies that tools answer through the daemon and every tool should succeed or fail honestly. The get_guard_status MCP tool calls get_session_location through the daemon, but this method is not in the allowlist of exposed methods (_DAEMON_METHODS), causing the call to fail with a "method not allowed" error misidentified as a daemon upgrade issue.

Comment thread routes/local_query.py
@@ -1055,6 +1055,18 @@ def http_query():
"query_signal_coverage",

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Drift Bot (ClawMetry) — Blueprint: Self-Diagnostics and Operator Tools over MCP

The blueprint specifies that tools answer through the daemon and every tool should succeed or fail honestly. The get_guard_status MCP tool calls get_session_location through the daemon, but this method is not in the allowlist of exposed methods (_DAEMON_METHODS), causing the call to fail with a "method not allowed" error misidentified as a daemon upgrade issue.

Copy link
Copy Markdown
Owner Author

blocked on author decision — skipping (auto-mergeability sweep)

All CI checks pass. PR is BLOCKED by a required reviewer approval — mechanical sweep cannot approve; needs human review.


Generated by Claude Code

@vivekchand

Copy link
Copy Markdown
Owner Author

Drift-bot triage: the one finding is a base misread. get_session_location is already in routes/local_query.py _DAEMON_METHODS on both origin/main and this branch (added in 290d5cb, Matrix-gap sprint), so get_guard_status reaches it through the daemon as the blueprint states. Verified with git show origin/main:routes/local_query.py | grep -c '"get_session_location"' = 1 and the same on HEAD. No code change needed; retriggering the bot.

@8090-software-factory

Copy link
Copy Markdown

⚠️ Drift Bot (ClawMetry): 3 potential drift finding(s)

1. Blueprint: Self-Diagnostics and Operator Tools over MCP

File: clawmetry/local_store.py

The blueprint specifies a SelfReportStore component with specific methods (ingest_self_report, query_self_reports, mark_self_report_corroborated, query_guard_incidents, query_session_denials, query_self_report_counts, query_self_report_honesty) and an agent_self_reports table, but neither the table creation nor any of these methods are implemented in the local_store.py file.

2. Blueprint: Self-Diagnostics and Operator Tools over MCP

File: routes/local_query.py:1069

The allowlist in routes/local_query.py references methods that do not exist in the LocalStore: ingest_self_report, query_self_reports, query_self_report_counts, query_self_report_honesty, query_guard_incidents, query_session_denials, find_session_by_cwd. These methods are specified in the blueprint's SelfReportStore component and called by MCP tools, but are not implemented.

3. Blueprint: Self-Diagnostics and Operator Tools over MCP

File: clawmetry/mcp_server.py:438

The _tool_get_guard_status function calls _method("get_session_location", ...) which is now listed in the allowlist, but according to the prior drift comment, this method was not previously in the allowlist. The fix should ensure this method is properly exposed through the daemon query path and responds with honest errors when unavailable.

Comment thread routes/local_query.py
"query_self_report_honesty",
"query_guard_incidents",
"query_session_denials",
"find_session_by_cwd",

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Drift Bot (ClawMetry) — Blueprint: Self-Diagnostics and Operator Tools over MCP

The allowlist in routes/local_query.py references methods that do not exist in the LocalStore: ingest_self_report, query_self_reports, query_self_report_counts, query_self_report_honesty, query_guard_incidents, query_session_denials, find_session_by_cwd. These methods are specified in the blueprint's SelfReportStore component and called by MCP tools, but are not implemented.

github-actions Bot pushed a commit that referenced this pull request Sep 4, 2026
@vivekchand

Copy link
Copy Markdown
Owner Author

Drift-bot triage, second run (3 findings, all base misreads): on HEAD 38d4af0, clawmetry/local_store.py defines ingest_self_report, query_self_reports, mark_self_report_corroborated, query_guard_incidents, query_session_denials, query_self_report_counts, query_self_report_honesty, find_session_by_cwd (one def each, checked with git show HEAD:clawmetry/local_store.py | grep -c "def <name>(") and creates the agent_self_reports table. Finding 3 restates finding 1 of the previous run, already disproved (get_session_location is allowlisted on main and here). The bot is reading the merge base, where these do not exist yet because this PR adds them. No code change; retriggering.

@8090-software-factory

Copy link
Copy Markdown

✅ Drift Bot (ClawMetry): no drift detected

Drift Bot analyzed the changed files against this project's blueprints and requirements and found no drift.

github-actions Bot pushed a commit that referenced this pull request Sep 4, 2026
@vivekchand
vivekchand force-pushed the feat/self-diagnostics-mcp branch from 4107e2a to f636469 Compare September 4, 2026 12:19
@8090-software-factory

Copy link
Copy Markdown

✅ Drift Bot (ClawMetry): no drift detected

Drift Bot analyzed the changed files against this project's blueprints and requirements and found no drift.

vivekchand and others added 5 commits September 4, 2026 14:35
…untime installer, honesty rollup (WO-59)

An agent can tell its operator what got in the way; ClawMetry checks
whether the tool stream agrees.

- MCP server: `report_to_operator` (six operator-extendable categories,
  500-char summary, session inferred from env or cwd) plus read tools
  `list_incidents`, `get_guard_status`, `get_signal_rates` (honest
  "not available on this daemon version"), `list_self_reports`. No tool
  acts on a process; every tool returns an honest error when the daemon
  is down, busy, or too old.
- Store: additive `agent_self_reports` table; summary redacted before
  storage; writes only through the daemon's local query server.
- Daemon tick: corroborate reports against detector incidents and
  permission denials within CORROBORATION_WINDOW_SECS (600, env
  override); honesty per (runtime, model) withheld under MIN_INCIDENTS.
- CLI: `clawmetry mcp install|uninstall|status` for Claude Code, Cursor,
  Codex, Gemini CLI, OpenCode, Windsurf (formats verified against vendor
  docs; Codex against `codex mcp add`), merge-only, never deletes a
  foreign entry, uninstall removes only what the marker says we wrote,
  guidance snippet offered and written only with --write-guidance.
- Routes /api/self-reports, /honesty, /support; transcript panel "What
  the agent reported"; Guard tab "Agent reported" card; `selfReports`
  snapshot slice.
- tests/test_self_diagnostics.py (49) wired into moat-tests + Makefile.

Requirement: c2329afe-0337-4d09-97b6-b6b7c7e260a1

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01ERBVJarDT4Vw1hJox5efBB
… exception text served)

- parse_window_secs strips and bounds the input before an unambiguous
  regex (`^(\d{1,12})([smhdw]?)$`), so a long run of spaces cannot make
  the old `\s*...\s*` pair backtrack.
- support_matrix (served by /api/self-reports/support) replaces an OSError
  message with a fixed sentence; the CLI still prints the real error.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01ERBVJarDT4Vw1hJox5efBB
…tore; Guard card links to the Signals tab

- The MCP get_signal_rates tool now calls query_signal_grouped (WO-58,
  merged in #5485) and shapes the rates with behaviour_signals.shape_rates,
  the same arithmetic the Signals tab uses. A daemon that predates the
  signals store still gets "signals not available on this daemon version".
- The Guard tab "Agent reported" card links to the Signals tab.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01ERBVJarDT4Vw1hJox5efBB
…on_location is allowlisted)

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01ERBVJarDT4Vw1hJox5efBB
@vivekchand
vivekchand force-pushed the feat/self-diagnostics-mcp branch from f636469 to 5daca7b Compare September 4, 2026 12:36
@8090-software-factory

Copy link
Copy Markdown

✅ Drift Bot (ClawMetry): no drift detected

Drift Bot analyzed the changed files against this project's blueprints and requirements and found no drift.

@8090-software-factory

Copy link
Copy Markdown

✅ Drift Bot (ClawMetry): no drift detected

Drift Bot analyzed the changed files against this project's blueprints and requirements and found no drift.

github-actions Bot pushed a commit that referenced this pull request Sep 4, 2026
@vivekchand
vivekchand merged commit 85feb30 into main Sep 4, 2026
49 checks passed
@vivekchand
vivekchand deleted the feat/self-diagnostics-mcp branch September 4, 2026 15:27
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants