Skip to content

feat(mcp): add verify_execution_safety and simulate_workload_plan tools - #264

Merged
toasterbook88 merged 3 commits into
mainfrom
feat/mcp-safety-and-simulate-tools
Jul 27, 2026
Merged

feat(mcp): add verify_execution_safety and simulate_workload_plan tools#264
toasterbook88 merged 3 commits into
mainfrom
feat/mcp-safety-and-simulate-tools

Conversation

@toasterbook88

Copy link
Copy Markdown
Owner

Summary

Implements the two MCP tools flagged as missing in the v0.14.7 optimization spec audit (claim 4B). Both are read-only dry-run tools — no execution, no reservation.

verify_execution_safety

Evaluates a proposed command against the structured safety rule set without running it.

  • Input: `{ "command": string, "surface"?: string (default "shell") }`
  • Output: `{ "verdict": "SAFE"|"PROMPT"|"VETO", "category", "program", "matched_rule", "reasons", "raw_cmd", "surface" }`
  • Wraps `internal/safety.Evaluator` over `safety.DefaultRuleSet()`. Maps internal verdict (allow/deny/prompt) → operator-facing SAFE/PROMPT/VETO.

simulate_workload_plan

Dry-run placement ranker — returns best node + full ranked candidate list, not just the winner.

  • Input: `{ "task_description": string, "cached"?: bool }`
  • Output: `{ "best_node", "fit_score", "ok", "reasoning", "ranked_candidates": [{node, fit_score, is_local, alloc_mb, reserved_mb}], "warnings" }`
  • Wraps `placement.SelectBestNode` + `placement.RankCandidates`. Surfaces per-node `fit_score`, `alloc_mb`, `reserved_mb` for every surviving candidate.

Tests

Test Contract
`TestVerifyExecutionSafetyToolVetoesDestructive` `rm -rf /tmp/important-dir` → VETO, program=rm
`TestVerifyExecutionSafetyToolAllowsReadOnly` `ls -la /tmp` → SAFE
`TestVerifyExecutionSafetyToolDefaultsSurfaceToShell` `git push --force origin main` → PROMPT, surface defaults to "shell"
`TestSimulateWorkloadPlanReturnsRankedCandidates` 2-node snapshot → best_node non-empty, ranked_candidates has 2 entries each with fit_score

Full `internal/mcp` suite passes; `go build ./...` clean.

Related

Fills the 4B gap from the spec audit. The other two gaps (1A local VRAM sampling, 1B pairwise link matrix) will follow as separate PRs.

Two new read-only MCP tools, filling the gaps flagged in the v0.14.7
optimization spec audit:

verify_execution_safety
  Input: { command: string, surface?: string (default shell) }
  Wraps internal/safety.Evaluator over DefaultRuleSet(). Maps the internal
  verdict (allow/deny/prompt) to the operator-facing SAFE/PROMPT/VETO
  vocabulary and returns { verdict, category, program, matched_rule,
  reasons, raw_cmd, surface }. No execution — pure dry-run rule match.

simulate_workload_plan
  Input: { task_description: string, cached?: bool }
  Wraps placement.SelectBestNode + RankCandidates and surfaces the FULL
  ranked candidate list with per-node fit_score/alloc_mb/reserved_mb,
  not just the winning decision. No reservation, no execution.

Both tools are registered read-only (WithReadOnlyHintAnnotation(true))
and tested for the destructive-veto, read-only-allow, force-prompt, and
ranked-candidate-shape contracts.
@gemini-code-assist

Copy link
Copy Markdown
Contributor

Caution

The consumer version of Gemini Code Assist on GitHub has been sunset. All code review activity has officially ceased.

AXIS Contributor added 2 commits July 27, 2026 14:52
…kload_plan

AGENTS.md and docs/runbooks/mcp-network-tools.md claimed 18 tools
(15 read-only + 3 lease). After adding the two new read-only tools the
verify-doc-facts guardrail correctly failed CI:

  AGENTS.md claims 18 MCP tools; internal/mcp registers 20 via s.AddTool

Update both docs to 20 tools (17 read-only + 3 advisory lease) and list
the new tools in the runbook. ./hack/verify-doc-facts.sh now passes.
@toasterbook88
toasterbook88 merged commit da08d5c into main Jul 27, 2026
7 checks passed
@toasterbook88
toasterbook88 deleted the feat/mcp-safety-and-simulate-tools branch July 27, 2026 19:05
toasterbook88 added a commit that referenced this pull request Jul 27, 2026
… execution (#265)

## Summary

Fills the **1A gap** from the v0.14.7 optimization spec audit:
\`PeakVRAMMB\` was previously only populated from *remote* execution
responses — local execution on GPU hosts recorded **zero** VRAM. This
adds real local VRAM telemetry.

## Implementation

New \`internal/execution/vram.go\`:

- \`samplePeakVRAM(ctx, interval)\` — starts a background goroutine that
polls \`nvidia-smi --query-gpu=memory.used
--format=csv,noheader,nounits\` at a fixed interval, sums across all
GPUs, tracks the max total observed, and returns a \`stop() int64\`
closure.
- \`queryTotalVRAMUsed(ctx)\` — single-shot helper, returns \`(totalMiB,
ok)\`; \`(0, false)\` if nvidia-smi is missing or output unparseable.

Wired into \`runLocal\` (\`internal/execution/guarded.go\`):
- Sampler starts immediately before \`runWithReservationHeartbeat\`,
stops immediately after.
- \`resp.PeakVRAMMB\` is set on **both** success and failure paths.
- Polling interval: 500ms (cheap vs. the seconds-to-minutes timescale of
real workloads).

## Degradation

On hosts without nvidia-smi (Apple Silicon, Intel-only, or PATH without
the binary), \`queryTotalVRAMUsed\` returns \`(0, false)\` and the
sampler's peak stays 0. No error surfaced — VRAM is best-effort
telemetry, not load-bearing.

## Verification

| Check | Result |
|---|---|
| \`TestSamplePeakVRAMReturnsZeroWhenNvidiaSmiMissing\`
(PATH=\`/nonexistent\`) | PASS — 0 peak, no error |
| \`TestSamplePeakVRAMStopsCleanly\` | PASS |
| \`TestQueryTotalVRAMUsedHandlesMissingBinary\` | PASS — (0, false) |
| \`TestSamplePeakVRAMLiveOnGPUHost\` on cranium (2x NVIDIA) | PASS —
captured **17,896 MiB** peak over 500ms |
| Full \`internal/execution\` suite | PASS |
| Full \`internal/mcp\` suite | PASS (no regression) |
| \`go build ./...\` | clean |

## Related

Follows #264 (MCP tools, 4B). The remaining audit gap (1B pairwise link
matrix) will follow as a separate PR.

---------

Co-authored-by: AXIS Contributor <axis-dev@example.invalid>
toasterbook88 added a commit that referenced this pull request Jul 27, 2026
…266)

## Summary

Fills the **1B gap** from the v0.14.7 optimization spec audit: the spec
described a \`PairwiseLinkMatrix\` / \`LinkMetric\` data structure that
did not exist anywhere in the codebase. This adds it for real — a first
cut that measures what's actually measurable from a single vantage.

## Types (\`internal/models/types.go\`)

\`\`\`go
type PairwiseLinkMatrix struct {
    Links []LinkMetric
}
type LinkMetric struct {
    SourceNode     string
    TargetNode     string
    OverlayType    string        // lan | tailscale | thunderbolt
    RTTLatencyP95  time.Duration
    ThroughputMBps float64       // 0 = unmeasured in this cut
}
\`\`\`
\`ClusterSnapshot\` gains an optional \`Topology *PairwiseLinkMatrix\`
field (zero-value-safe; existing consumers unaffected).

## Builder (\`internal/facts/pairwise.go\`)

\`BuildPairwiseLinkMatrix(ctx, localName, nodes)\`:
- Probes directional RTT from the local node to each remote node's
\`ResolvedDialTarget\` (fallback \`SSHTarget\`) via \`ping -c 4 -W 1\`.
- Parses the **max** RTT from the ping summary line as a P95 stand-in (4
samples).
- Infers overlay from address shape: \`100.*\` → \`tailscale\`,
\`169.254.*\` → \`thunderbolt\`, else \`lan\`.
- **Single-vantage model**: only local→remote is probed; cross-remote
edges are not measured. Throughput left at 0 (unmeasured) in this first
cut — RTT is the primary signal.
- **Best-effort**: unreachable nodes or missing \`ping\` are omitted,
not fatal. No-local-node returns an empty matrix.

\`TopologySummary(m)\` helper for logging.

## Verification

| Check | Result |
|---|---|
| \`TestParseRTTP95\` (linux summary / no-summary / malformed) | PASS |
| \`TestInferOverlay\` (tailscale / thunderbolt / lan / 10.x / hostname)
| PASS |
| \`TestBuildPairwiseLinkMatrixNoLocalNode\` | PASS — empty matrix, no
panic |
| \`TestBuildPairwiseLinkMatrixSkipsLocalAndEmptyTargets\` | PASS — self
+ empty skipped |
| \`TestTopologySummaryEmpty\` / \`NonEmpty\` | PASS |
| \`TestBuildPairwiseLinkMatrixLiveOnCranium\` (loopback) | PASS —
captured \`cranium→loopback-target lan 27µs\` |
| Full \`internal/facts\` + \`internal/models\` suites | PASS |
| \`go build ./...\` | clean |

## Scope notes

- This is a **first cut**: throughput is unmeasured, and only
local→remote edges are probed. Cross-remote measurement would require
distributed probing (each node probing the others) — out of scope for a
single-PR addition, and the spec didn't require it.
- The builder is **not yet wired into the snapshot pipeline** — it's
available for callers but \`ClusterSnapshot.Topology\` stays nil by
default. Wiring it into \`axis status\` / daemon refresh is a deliberate
follow-up so this PR stays focused on the type + builder.

## Related

Follows #264 (MCP tools, 4B) and #265 (local VRAM sampling, 1A).
Completes the three real gaps flagged in the spec audit.

---------

Co-authored-by: AXIS Contributor <axis-dev@example.invalid>
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.

1 participant