Skip to content

feat(cli): add azureclaw ingress command for inter-agent access control#23

Closed
johnsonshi wants to merge 491 commits into
mainfrom
johsh/ingress-command
Closed

feat(cli): add azureclaw ingress command for inter-agent access control#23
johnsonshi wants to merge 491 commits into
mainfrom
johsh/ingress-command

Conversation

@johnsonshi

Copy link
Copy Markdown
Collaborator

Summary

Adds an operator-facing azureclaw ingress CLI command and corresponding router endpoints for managing which agents can communicate with a sandbox via the AGT mesh.

Motivation

Inter-agent communication controls (KNOCK trust threshold, per-agent block/allow, mesh:receive policy) exist in the router but are only configurable via env vars (AGT_TRUST_THRESHOLD, AGT_TRUSTED_PEERS) and policy YAML. There's no operator-friendly way to manage them at runtime — unlike azureclaw egress which has a full CLI.

Changes

Router (inference-router/src/ingress.rs):

  • IngressAcl struct with per-agent block/allow sets and dynamic trust threshold
  • 8 HTTP endpoints behind admin token auth:
    • GET /ingress/status — overview
    • GET /ingress/agents — all known agents with trust + ACL status
    • POST /ingress/block / POST /ingress/unblock — block/unblock agents
    • POST /ingress/allow — explicitly allow (bypasses threshold)
    • GET /ingress/blocked / GET /ingress/allowed — list ACL entries
    • POST /ingress/threshold — update trust threshold at runtime (0–1000)
  • 3 unit tests for ACL logic

CLI (cli/src/commands/ingress.ts):

  • Mirrors azureclaw egress UX (Docker + K8s auto-detection, admin token auth)
  • Flags: --block, --unblock, --allow, --blocked, --allowed, --agents, --threshold, --status
  • Default (no flags) shows status overview with available commands

Testing

  • cargo test --package azureclaw-inference-router — 29 tests pass (26 existing + 3 new ingress)
  • npx tsc --noEmit — typecheck clean
  • npx oxlint --deny unused — 0 errors
  • npx vitest run — 159 tests pass

Usage

# Show ingress status
azureclaw ingress my-agent

# Block an agent
azureclaw ingress my-agent --block malicious-bot

# Explicitly allow a trusted agent
azureclaw ingress my-agent --allow trusted-partner

# Set trust threshold
azureclaw ingress my-agent --threshold 700

# List all known agents with trust + ACL
azureclaw ingress my-agent --agents

Pal Lakatos-Toth and others added 30 commits March 26, 2026 10:37
Major TUI upgrade:

Security Controls panel (per-selected-agent, live polling):
  Infrastructure: isolation, seccomp profile, NetworkPolicy,
    iptables, forward proxy, admin auth, router readyz
  Egress Control: mode (learning/enforcing), blocklist domain
    count (52K+), allowlist count
  Token Usage: requests (ok/err), input/output tokens,
    total, average latency — parsed from Prometheus /metrics
  AGT Governance: enabled, audit chain entries + integrity,
    known agents count

Other improvements:
  - Arrow key fix: manual ↑/↓/j/k wired to table.rows.select()
  - Per-cluster view: shows kubectl context in header, --context flag
  - Health indicators: ● green/yellow/red per agent in table
  - Spinner animation during refresh (braille frames)
  - Activity sparkline (agent count over time)
  - Parallel security state + egress fetch per refresh
  - Prometheus text parser for azureclaw_* metrics
  - 3-column bottom layout: Security | Egress | Log+Sparkline

Endpoints polled per sandbox:
  /readyz, /blocklist/status, /agt/status,
  /egress/allowlist, /egress/learned, /metrics
  + kubectl: NetworkPolicy, router-admin-token secret

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Disable built-in keys/vi on tables — the table's internal
handler and our screen.key handler both fired on the same
keypress, causing double-movement and selection desync.

Now only our moveSelection() drives ↑/↓/j/k navigation,
which also correctly updates the security panel on each move.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Previously streaming (SSE) requests only recorded the request
counter — no token usage, no latency. This was because the
stream was piped directly to the client without inspection.

Fix:
- Inject stream_options.include_usage=true into request body
  so Azure OpenAI sends a final SSE chunk with usage data
- Wrap byte stream with map() that inspects each chunk for
  'usage' JSON field in SSE data lines
- Record azureclaw_tokens_total (input/output) and
  azureclaw_inference_latency_seconds on the final chunk

Prometheus /metrics now exposes all three metric families
for both buffered AND streaming inference requests.

12/12 tests pass, clippy clean.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
- Egress panel now filters to the currently selected agent
- Fetches both /egress/learned and /egress/allowlist per sandbox
- Shows status column: P (pending/learned) vs ✓ (approved)
- Pending domains sorted first, then approved
- Approve/deny only works on pending domains (state=learned)
- Added Shift+A shortcut: approve all learned domains for selected agent
- Egress panel label shows agent name dynamically
- Agent selection change (up/down in agents panel) refreshes egress view

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Add a cluster health overlay accessible via the 'c' key that shows:
- Node table per pool (clawpool, system) with Ready/NotReady status
- Live CPU/Memory usage per node from kubectl top (bar charts with color)
- API server health with latency measurement
- Resource quotas per namespace (if any exist)
- PVC status (bound/pending counts)
- Recent Warning events with severity color coding

Implementation details:
- ClusterHealth interface with nodes, quotas, PVCs, warnings
- fetchClusterHealth() runs 6 kubectl queries in parallel via Promise.allSettled
- Cluster overlay uses free-floating blessed boxes that toggle with agent detail panels
- makeBar() renders colored bar charts (green <50%, yellow <80%, red >80%)
- Header now shows cluster status indicator (API dot + node count) in both views
- Status bar adapts to show relevant shortcuts per view mode

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
…ress

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
- Egress panel now uses blessed.list with tag support for per-item colors
- Pending domains: yellow ● P with domain name
- Approved domains: green ✓ A with domain name
- Panel label shows legend: ●P=count ✓A=count
- Updated moveSelection/tab/approve/deny to work with blessed.list

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
- Fixed: was calling 'azureclaw rm' (doesn't exist) → now 'azureclaw destroy'
- Replaced blessed.question (broken keyboard) with custom dialog
- Dialog has [ Yes ] / [ Cancel ] buttons navigable with ←→/Tab/Enter
- Mouse click on buttons also works
- Escape dismisses the dialog

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Without -y, the destroy command's own confirmation prompt hangs
when piped (stdio: pipe), exiting silently without destroying.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
- Reads azureclaw.azure.com/parent label from ClawSandbox CRDs
- Controllers shown with ◆, sub-agents with ⤷ and └ tree indent
- Sorted: controllers first, then sub-agents grouped under parent
- Header shows ◆2 ⤷1 controller/sub-agent counts
- Switched fetchSandboxes from jsonpath to full JSON parsing

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Sub-agents now display as:
  └ kernel-agent (sub:azureclaw-aks-t..)
Restores Isolation column in table.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Hierarchy fix:
- Sub-agents now sort directly under their parent controller
- Orphaned sub-agents (parent deleted) go at the end

Spawn wizard (n key):
- Multi-field form: Name, Model, Isolation, Channel, TG Token, Egress
- ↑↓ navigate fields, ←→ cycle options (isolation/channel/egress)
- Enter edits text fields or advances, Tab launches immediately
- Isolation: enhanced/standard/confidential (←→ cycle)
- Channel: none/telegram/slack/discord (←→ cycle)
- Telegram token field appears dynamically when telegram selected
- Egress: learn mode on/off (←→ toggle)
- Passes all flags to 'azureclaw add'

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
All global keyboard handlers (↑↓, tab, a/d/e/n/m/l/c/r) now check
dialogOpen flag. Spawn, delete, and model dialogs set it on open
and clear on close. Prevents arrow keys from moving the agent table
while a dialog is active.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Spawn wizard rewrite:
- Cleaner state machine: editing flag blocks nav during text input
- Text input via editBox at bottom of dialog, saves reliably on submit
- Auto-advances cursor after text entry
- Dynamic fields: TG token only shown when telegram channel selected
- Launch button at bottom, Enter to confirm
- Esc to cancel at any time

Header: simplified to just 'N agent(s) 3✓' — no controller/sub-agent legend

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
blessed's readInput callback is unreliable when reusing textboxes.
Now creates a fresh textbox for each text field edit, uses submit/cancel
events to capture the value, then destroys the textbox. Value is saved
and displayed in the form after Enter.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Controller:
- Fixed runtimeClassName: kata-vm-isolation → kata-mshv-vm-isolation
  (matches the RuntimeClass that AKS creates for KataMshvVmIsolation)
- Updated test assertion

CLI add command:
- Pre-flight check when --isolation confidential: verifies nodes with
  label azureclaw.azure.com/pool=sandbox-kata exist
- If no Kata nodes found: prints error + exact az aks nodepool add
  command using cached context (RG + cluster name)
- Exits with code 1 instead of creating a pod that stays Pending forever

Operator TUI spawn wizard:
- Same pre-flight check when launching with confidential isolation
- Shows error in activity log with remediation hint

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
When --isolation confidential is used and no Kata nodes exist:
- If cached context (RG + cluster) available: prompt to auto-provision
- Runs az aks nodepool add with Standard_D4as_v6, AzureLinux,
  KataMshvVmIsolation, correct labels and taints
- 10 min timeout for Azure provisioning
- Proceeds to sandbox creation after nodepool is ready
- Falls back to error message if no cached context

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
When focused on agent table, pressing Enter:
- Suspends blessed TUI (switches to normal terminal buffer)
- Runs kubectl exec -it into the openclaw container
- User gets full interactive shell (can run openclaw tui, etc.)
- Typing 'exit' resumes the operator dashboard seamlessly
- Activity log shows return confirmation

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Two changes ensure sub-agents inherit their parent's isolation level:

1. Controller: exports SANDBOX_ISOLATION env var to pod, so the
   inference router knows the parent's isolation level at spawn time.

2. spawn.rs: uses parent's isolation as default (instead of 'enhanced')
   when the spawn request doesn't specify isolation. Also blocks
   downgrading — a confidential parent cannot spawn non-confidential
   sub-agents (returns error).

This closes a security gap where a confidential Kata-isolated agent
could spawn a sub-agent running on a standard container (different
kernel, no VM boundary).

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
The embeddings endpoint was using the chat model (gpt-4.1) as the
deployment name, causing Azure to reject the request with
'OperationNotSupported'. Now extracts the model from the request
body (e.g. text-embedding-3-small) and uses it as the deployment,
so embeddings route to the correct Azure OpenAI deployment.

Strips azure/ and azure-openai/ prefixes from model names.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
- Launches 'openclaw tui' instead of dropping to bash shell
- Uses spawnSync to block — avoids async blessed interference
- Proper terminal state save/restore (lsaveCursor/lrestoreCursor)
- Direct process.stdin.setRawMode for clean PTY handoff
- No more terminal corruption on return

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
- Uses 'openclaw agent --local --session-id operator-{name}' instead of
  'openclaw tui' — conversation persists across connect/disconnect
- Reconnecting to the same agent resumes the same session
- Traps SIGINT in parent process so Ctrl+C only kills kubectl exec,
  not the operator itself
- Treats SIGINT exit as normal return (session is preserved)
- Explicit terminal reset escape codes for cleaner buffer switch

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
All dialog cleanup functions now defer dialogOpen=false by 50ms,
preventing the global Enter handler from firing in the same event
loop tick as the dialog's Enter handler. Fixes: pressing Enter on
delete/spawn/model dialogs no longer accidentally triggers connect.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
openclaw agent --local requires -m flag and isn't interactive.
Now drops to bash --login with a welcome banner showing agent info
and instructions. User can run 'openclaw tui' for chat or use any
other command. Type 'exit' to return to operator dashboard.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Removes the intermediate bash shell. Enter on an agent launches
openclaw tui directly via kubectl exec. Quit the TUI to return
to the operator dashboard — no 'exit' confusion.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
README:
- Added operator dashboard section with TUI screenshot and keybindings
- Added azureclaw operator to CLI reference table
- Updated confidential isolation to note sub-agent inheritance
- Updated 'Add Agents' to note auto-provisioning of Kata nodepool

docs/architecture.md:
- Fixed RuntimeClass: kata-vm-isolation → kata-mshv-vm-isolation

docs/security.md:
- Added RuntimeClass name, isolation inheritance, auto-provisioning
- Removed stale AKS preview feature reference

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Pinned-Dependencies (0 → ~8):
- Pin all GitHub Actions to SHA hashes (20 actions across 3 workflows)
- Pin Docker base images to digest (mcr.microsoft.com/azurelinux/base/core:3.0)

Token-Permissions (0 → 10):
- Move security-events:write to job level in ci.yml
- Move contents:write to job level in blocklist-refresh.yml
- Move packages:write + id-token:write to job level in image-sign-sbom.yml
- All workflows now have contents:read at top level only

Vulnerabilities (5 → 10):
- Upgrade prometheus 0.13 → 0.14 (drops protobuf 2.x CVE RUSTSEC-2024-0437)
- Fix with_label_values API change in proxy.rs for prometheus 0.14
- cargo audit: 0 vulnerabilities

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Pal Lakatos-Toth and others added 24 commits April 2, 2026 14:26
…o-gen

Operator TUI:
- PTY-based agent connection with Ctrl+\ detach (node-pty)
- Parallel sandbox discovery (Promise.allSettled)
- Tiered refresh: sandboxes every 10s, security every 30s, cluster every 60s
- Mesh health indicators in header (relay/registry pod status)
- Topology view rewrite with fixed-width columns
- Trust scores filtered against live sandboxes (4 locations)
- Background refresh paused during agent connection

Trust cleanup on destroy:
- Router: DELETE /agt/trust/{agent_id} endpoint with admin auth
- Router: DELETE method support in sidecar forward
- Plugin: destroy handler cleans trust + AMID caches

Infrastructure:
- Auto-generate postgres credentials in azureclaw up (crypto.randomBytes)
- Remove placeholder secret from deploy/agentmesh.yaml (footgun)

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Add fetchAgtQuick() that runs /agt/status on non-TIER_DETAIL cycles,
keeping trust scores, mesh counters, and audit entries updated every
10s instead of every 30s. Full security refresh still runs on
TIER_DETAIL (30s) for egress, blocklist, metrics, and reputation.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
10 diagrams covering:
- Sandbox pod architecture (3-container layout, UID mapping)
- Network access matrix (what each UID can reach)
- Agent creation flow (CLI → Azure → K8s → Controller)
- Controller reconciliation (all K8s resources created)
- Sub-agent spawn flow (CRD → registry discovery)
- E2E encrypted communication (KNOCK → X3DH → Double Ratchet)
- Trust gate decision flow
- Inference request flow (4 gates: governance, budget, auth, safety)
- Egress control flow (iptables → proxy → blocklist → learn/enforce)
- Full deployment flow (azureclaw up)
- Multi-agent topology
- Defense-in-depth layers (10 layers)

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
up --dry-run now checks required CLI tools (az, kubectl, helm, docker)
and validates Azure login + subscription before printing the plan.
Interactive prompts (region, name, isolation, subscription selection)
are still skipped in dry-run.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
The flag was defined in config.ts and supported in the interactive flow
but was missing from the update subcommand's option list and env mapping.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Bug 1: Shift+L confirm popup lost focus to agent TUI on Enter.
The screen-level Enter handler (connectToAgent) was the only key handler
missing the dialogOpen guard, so it fired instead of the popup callback.

Bug 2: Topology box content used .padEnd()/.length which measures JS
string length, not terminal visual width. Replaced with fitVis/visualLen
helpers that count emoji (surrogate pairs) as 2 cells, ensuring arrows
and counters align correctly in each box.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
The MeshMetrics counters (messages_sent, messages_received) were declared
but never incremented. The WebSocket relay bridge maintained its own local
counters that were only logged at disconnect and never synced to the
shared MeshMetrics. This caused /agt/status to always report 0 for
mesh_messages_sent and mesh_messages_received, making the operator TUI
topology view show ↑0 ↓0 for all agents.

Fix: pass mesh_metrics Arc into the relay bridge and increment alongside
the per-connection local counters.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Sources: OISD small, URLhaus hostfile
Generated: 2026-04-08T04:47:34Z
…le, escape during dialog

Three fixes in the operator TUI:

1. Garbled text on topology box lines with emoji: blessed's internal
   width calculation doesn't match terminal rendering for 🔒/📖 emoji.
   Replaced with blessed-tagged colored text ({green-fg}enforce{/},
   {yellow-fg}learn{/}). Updated visualLen/fitVis to strip blessed tags.

2. Learn/enforce toggle (Shift+L) not confirming: blessed.question
   widget doesn't handle input correctly when screen-level key handlers
   exist. Replaced with a simple blessed.box + explicit y/escape key
   handlers that self-cleanup via screen.unkey().

3. Pressing escape while confirm dialog was open would quit the app:
   the quit handler (q/escape) was missing the dialogOpen guard.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
When toggling learn/enforce via Shift+L, the mode was only changed
in-memory on the router (POST /egress/learn or /egress/enforce). On pod
restart the controller reconciled from the CRD which still had the
original value, reverting the mode.

Now enforceEgress/learnEgress also patch the ClawSandbox CRD's
spec.networkPolicy.learnEgress field so the controller preserves the
mode across restarts. The CRD patch is best-effort (.catch) so a
toggle still works even if RBAC doesn't allow CRD writes.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
The .sqlx/ directory was gitignored, so cloners couldn't build the
agentmesh-registry Docker image — COPY .sqlx ./.sqlx failed because
the directory didn't exist. The .sqlx cache is required for sqlx
offline mode (SQLX_OFFLINE=true) during Docker builds where no
database is available.

Fixes #17

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
The ACR name was derived as baseName+'acr' (e.g. 'azureclawacr'),
which is globally unique in Azure DNS. Different users deploying with
the default baseName would hit 'AlreadyInUse' errors.

Now the Bicep template appends uniqueString(resourceGroup().id) to
produce a deterministic per-RG suffix (e.g. 'azureclaw3kf2a1'). The
CLI reads the actual ACR name from Bicep outputs instead of computing
it locally, with a fallback to acrLoginServer for older deployments.

Fixes #16

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
…ncements (#18)

* feat(router): add native AGT governance module (Phase 1)

Add governance.rs wrapping the agentmesh crate (v3.0.2) with:
- PolicyEngine integration with YAML policy loading from AGT_POLICY_DIR
- TrustManager with sidecar-compatible clamping (±200 delta, 500 max new)
- AuditLogger with hash-chain integrity verification
- AgentIdentity with Ed25519 DID generation
- BehaviorMonitor for burst/failure detection
- RateLimiter with per-agent token bucket
- GovernanceMetrics with atomic counters for eval stats
- JSON builders matching existing /agt/* API contract
- 14 unit tests covering all governance primitives

Not yet wired into routes (Phase 2).

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* feat(router): wire native governance into all routes (Phase 2)

Replace all SidecarProxy.forward() calls with direct governance module calls:
- agt_evaluate: native policy evaluation with metrics tracking
- agt_trust_*: native trust management with clamping
- agt_audit/verify: native audit log with hash-chain integrity
- agt_status: enriched status with eval counters, latency, DID
- agt_reputation: local trust from governance instead of sidecar
- agt_trust_update: native trust update with admin token auth
- Inference routes: synchronous policy eval (no HTTP round-trip)
- Spawn route: synchronous policy eval
- Content flag reporting: direct governance call in safety.rs
- Image generation: synchronous policy eval

All sidecar.forward() calls eliminated. SidecarProxy kept as dead code
(will be removed in Phase 3). Net -167 lines.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* refactor: remove AGT governance sidecar from infrastructure (Phase 3)

Eliminate the Python governance sidecar (UID 1002) from all layers:

Controller:
- Remove governance_sidecar_image field and AGT_SIDECAR_IMAGE env
- Remove agt-governance container injection (3rd container → 2 containers)
- Remove AGT_SIDECAR_URL env var injection (router reads policies natively)
- Keep AGT_POLICY_DIR + agt-data volume (router uses them directly)
- Update tests: 2-container assertions, remove 8 sidecar-specific tests

Sandbox image:
- Remove COPY server.py and vendor/agt-wheels from Dockerfile
- Replace sidecar startup in entrypoint.sh with trust dir setup
- Set AGT_GOVERNANCE_ENABLED=true by default (native, always-on)
- Pass AGT_POLICY_DIR and AGT_TRUST_THRESHOLD to router env

Helm:
- Remove agtGovernance values section (image, resources)
- Remove AGT_SIDECAR_IMAGE env from controller deployment

CLI:
- Remove sidecar image from push.ts build targets
- Remove sidecar build+push from up.ts
- Remove agtGovernance helm --set values from deployment

Pod structure is now: init:egress-guard → openclaw (UID 1000) → inference-router (UID 1001)

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* chore: delete sidecar.rs and update CLI tests (Phase 4-6 cleanup)

- Delete inference-router/src/sidecar.rs (298 lines)
- Remove SidecarProxy from AppState and all imports
- Update push.test.ts: 5 images instead of 6, remove sidecar filter test
- All 127 Rust tests + 159 CLI tests pass

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* feat: Prometheus governance metrics, policy hot-reload, TUI enrichment, docs update

Phase 5 — Enhancements:
- Add 7 Prometheus governance metrics (policy evals, latency, trust, audit,
  content flags, behavior alerts, policy rules) to /metrics endpoint
- Add policy YAML hot-reload: periodic mtime watcher on AGT_POLICY_DIR
- Enrich operator TUI: security panel shows native governance stats
  (eval counts, deny rate, latency, behavior alerts, content flags)
- Full AGT overlay now shows Policy Engine section with eval stats

Phase 6 — Cleanup:
- Delete sidecar-images/agt-governance/ (Dockerfile, server.py, tests)
- Delete vendor/agt-wheels/ (~3.8 MB of Python wheels)
- Update README.md: 2-container pod diagram, remove all sidecar references,
  update components table (5 images), security tables, project structure
- Update plugin.ts: replace sidecar terminology with router/governance

All 286 tests pass (74 controller + 53 router + 159 CLI), clippy clean.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* chore: purge all sidecar references from active codebase

Comprehensive cosmetic cleanup replacing 'sidecar' terminology with
accurate descriptions across the entire codebase:

- Mermaid diagrams: 3-container → 2-container pod layout, removed
  agt-governance (UID 1002, :8081) from all diagrams
- Critical fix: plugin.ts port 8081 → 8443/agt/evaluate (4 instances)
- Removed AGT Sidecar from GitHub issue templates
- Updated BACKLOG, CONTRIBUTING, deployment skill, copilot instructions
- Updated .instructions.md, agt-e2e-encryption skill
- Cleaned comments in Rust (controller + router), CLI, infra files
- Removed python-lint CI job, sidecar Makefile targets, CODEOWNERS entry
- 36 files, net -45 lines

Tests: 127 Rust + 159 CLI pass, clippy clean.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* test: add 26 integration tests for AGT governance HTTP endpoints

Exercise all /agt/* routes end-to-end via tower::ServiceExt::oneshot:

- POST /agt/evaluate: policy allow, agent_id defaults, explicit context
- GET /agt/trust: empty list, populated list, per-agent lookup
- POST /agt/trust: no-auth, admin header, Bearer token, wrong token,
  self-trust rejection, score clamping
- DELETE /agt/trust: auth enforcement, successful deletion
- GET /agt/audit: empty state, entries after evaluations
- GET /agt/audit/verify: empty chain, valid chain after 5 evaluations
- GET /agt/status: all expected fields present, counters reflect evals
- GET /agt/mesh/inbox: empty by default
- GET /agt/reputation: graceful degradation without registry
- Full lifecycle: evaluate → trust update → audit → verify → status
- Trust CRUD: create → list → delete

Infrastructure changes:
- Add lib.rs to expose modules for integration tests
- Update main.rs to use library re-exports
- Add Default impls for WorkloadIdentityAuth, GovernanceMetrics,
  MeshInbox, MeshMetrics (clippy new_without_default)
- Add Blocklist::is_empty() (clippy len_without_is_empty)

All 153 Rust tests pass (74 controller + 53 router + 26 integration).

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* ci: fix container scan runner + blocklist refresh permissions + cargo fmt

- Container Image Scan: ubuntu-latest-xl → ubuntu-latest (xl runner
  unavailable in this org, causing perpetual queue timeout)
- Added timeout-minutes: 30 to prevent runaway builds
- Blocklist Refresh: added pull-requests: write permission so
  gh pr create can access the GraphQL API
- Fixed cargo fmt import ordering in governance.rs

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* fix: symlink grammy into top-level node_modules for OpenClaw ≥2026.4

OpenClaw 2026.4.8 hoists Telegram bundle chunks (allowed-updates-*.js)
to dist/ but grammy remains in extensions/telegram/node_modules/.
Node's require() can't resolve it from the top-level context, causing
a fatal crash when TELEGRAM_BOT_TOKEN is set.

Fix: symlink grammy + @grammyjs from the extension's node_modules into
the top-level node_modules after npm install. Conditional — no-ops when
OpenClaw fixes their packaging upstream.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* fix: symlink all 13 missing OpenClaw channel extension deps (openclaw#62749)

OpenClaw 2026.4.x ships with stageRuntimeDependencies broken for npm
installs — channel plugin deps (grammy, @buape/carbon, @slack/bolt, etc.)
are only present under dist/extensions/*/node_modules/ but bundle chunks
that import them are hoisted to dist/.

Expand the previous grammy-only workaround to cover all 13 affected
packages across Telegram (3), Discord (6), Slack (3), and Feishu (1)
using a reusable link_pkg() helper. Each symlink is conditional (no-op
when the package already exists at top level or when upstream fixes it).

Reference: openclaw/openclaw#62749, openclaw/openclaw#61787

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* feat: add Foundry governance + fix policy YAML to match agentmesh schema

- Rewrite azureclaw-default.yaml to use agentmesh PolicyProfile format
  (version, agent, policies[] with type/allowed_actions/denied_actions)
  instead of the custom condition/operator/value format that was silently
  failing to parse (missing 'version' field → 0 rules loaded)
- Add foundry:* action patterns covering all 9 Foundry tool categories
  (web_search, code_execute, file_search, memory, image_generation,
  conversations, evaluations, deployments, agents)
- Wire governance evaluation into foundry_proxy handler so Foundry API
  calls are no longer ungoverned — each request gets an action like
  foundry:memory:search_memories evaluated against the policy
- Fix flaky trust integration tests (shared /tmp/agt/trust_scores.json)

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* fix: increment mesh_sessions counter on relay WebSocket connect

The MeshMetrics.sessions counter was defined and read in /agt/status
but never incremented — it always showed 0 regardless of active relay
connections. Add fetch_add(1) when the relay WebSocket bridge
successfully connects.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* fix: use record_success/record_failure for trust updates (tracks interactions)

update_trust was calling set_trust() which sets the score but never
increments the interaction counter. This caused the TUI to show
'interactions: 0' and 'No peer agents yet' even after sub-agents
connected and exchanged messages.

Now: new agents get set_trust(500) + record_success() (bootstrap +
first interaction), subsequent updates use record_success/record_failure
which properly bump interactions and apply the SDK's reward/penalty/
decay logic. The TUI topology view now shows real peer info.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* feat(operator): enrich AGT panel + faster dev refresh

AGT panel now shows:
- Policy engine stats: rules loaded, evals, deny%, rate-limited count
- Eval latency (µs avg)
- Safety counters: behavior alerts + content flags (color-coded)
- All existing: mesh topology, trust scores, audit chain, reputation

Dev mode refresh: 3s default (was 10s), detail tier every cycle
instead of every 3rd. Local Docker has negligible overhead so
near-realtime operator view is practical.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* fix: policy_rule_count reports actual rules, not file count

load_policies_from_dir was incrementing total_rules per YAML file
loaded, not per rule inside the file. With 1 file containing 10
rules, the dashboard showed '1 rules loaded'.

Now parses the policies array from each file to count individual
rules. Falls back to 1 if parsing fails.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* feat: behavior alert reasons + dynamic rate-limit endpoint

Behavior alerts now show WHY they fired instead of just a count:
- TUI shows per-agent reasons (burst, consecutive failures, denials)
- /agt/status includes behavior_alerts_detail array with agent, reasons,
  and current counters
- Green ✓ when clean, red ⚠ with explanation when triggered
- Rate-limited calls no longer count as capability denials (was causing
  false-positive behavior alerts from the inference rate-limit rule)

Dynamic rate limits:
- GET /agt/rate-limit — current token-bucket config
- PUT /agt/rate-limit — update at runtime (admin token required)
  Body: { global_rate, global_capacity, per_agent_rate, per_agent_capacity }
  All fields optional, only provided fields change

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* fix: exclude all rate-limit denials from behavior monitoring

Both token-bucket (router-level) and policy-engine rate limits were
feeding into behavior.record(false), inflating capability_denials
and triggering false-positive behavior alerts. Rate limiting is
normal flow control, not anomalous behavior.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* fix: reputation diagnostics — log failures instead of spoofing identity

- Vendor patch #7: submitReputation logs actual HTTP error instead of
  silent catch { return false } (both ESM and CJS chunks)
- Router: log reputation submission failures in registry proxy
- Plugin: check submitReputation return value, log accepted=true/false
- Plugin: await recordMeshSession calls (were fire-and-forget)
- Router: extract lookup_parent_amid helper, deduplicate from agt_reputation
- Remove identity-spoofing proxy rewrite (security issue)
- Remove self-review reciprocal submission (silently ignored by registry)

Short-lived sub-agents: reputation doesn't matter (they die).
Long-lived sub-agents: both registered, direct rating works.
Parent feedback_count: needs diagnosis — SDK now surfaces the actual error.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* fix(operator): show dynamically spawned sub-agents as peers

The peer filter required agents to exist in the sandboxes list, but
sub-agents spawned via azureclaw_spawn are dynamic and not in that list.
Now shows peers that are either known sandboxes OR recently active
(lastSeen within 30 minutes), filtering out stale entries from old runs.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* fix(registry): feedback_count always 0 — wrong table name

Root cause: handlers.rs submit_reputation() stores into 'reputation_feedback'
(migration 001), but reputation.rs get_feedback_stats() queried
'reputation_feedbacks' (migration 006) — a table that was never written to.

Fixed get_feedback_stats, get_tag_aggregates, and get_reputation_leaderboard
to query the correct table. Converted from sqlx::query! (compile-time checked
against stale .sqlx cache) to sqlx::query_as (dynamic) to avoid schema mismatch.

Also added workspace.exclude for vendored crates so they can be built independently.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* feat(operator): visual reputation — score bar, star rating, tag badges

Reputation panel now shows:
- Score bar (10 blocks, colored green/yellow/red)
- Star rating from avg feedback (★★★★☆ style, 0-5 stars)
- Tag badges from registry (#reliable×2  #fast_response×1)

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* style: cargo fmt — fix formatting for CI

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

---------

Co-authored-by: Pal Lakatos-Toth <pallakatos@microsoft.com>
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
…cleanup

- Use REST API (gh api) instead of gh pr create (uses GraphQL which
  is blocked by enterprise GITHUB_TOKEN restrictions)
- Add graceful fallback with warning if PR creation still fails
- Clean up old blocklist branches before pushing new one
- Force-push to reuse branch name if same-day refresh runs twice

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
- codeql-action/upload-sarif upgraded from v3 (deprecated Dec 2026)
  to v4 (SHA c10b8064)
- trivy-action comment corrected: SHA was already v0.35.0 (safe per
  supply chain advisory) but was labeled as 'master'
- container-scan upload changed from if:success() to if:always() so
  SARIF results upload even when Trivy finds vulnerabilities
- Removes 'Code scanning configuration error' on security tab

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Resolves ~52 Trivy CVEs from outdated tool binaries in the sandbox
image. Remaining CVEs are from upstream OpenClaw dependencies (npm,
hono, tar) which we cannot patch.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
…for vendor exclusion

- npm audit fix: resolved defu prototype pollution (GHSA-737v)
- Dockerfile: npm install -g npm@latest after Node.js install to fix
  tar/minimatch/glob CVEs in bundled npm
- CodeQL config: exclude vendor/ source from analysis (upstream code)
- CodeQL workflow: custom workflow with security-and-quality queries,
  replaces default setup that scanned vendor files
- Remaining unfixable: lodash/xml2js (blessed-contrib transitive),
  op CLI Go deps (upstream 1Password), diff (tsdown transitive)

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
…conditionals

CLI (cli/src/):
- Remove 18 unused variables/imports across commands and tests
- Fix 4 useless conditionals: typeof always-defined var, || false, ? true : false
- Fix 2 trivially-true conditionals in dev.ts health loop
- Remove dead test helpers (createMockResponse/createMockRequest)
- Remove unused deploymentMap, gadgetMap, bold, hostname, threshold, padC

Vendor SDK (vendor/agentmesh-sdk/src/):
- Remove 11 unused imports (ValidationError, SessionError, CryptoError,
  NetworkError, RateLimitError, hkdfSimple, deserializePrekeyBundle, etc.)
- Remove 2 unused functions (toArrayBuffer, serializeIntent)
- Fix 1 unused loop iteration variable (cap → removed from destructuring)
- Prefix 3 unused variables with _ (signatureBytes, messageBytes, reason)
- Fix 10 unused catch parameters across storage files (catch (error) → catch {})

All 159 tests pass, typecheck clean.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
is only set to true immediately before break, so the guard never
evaluates to false. Remove the if-wrapper while keeping the variable
for the status display line.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Greenfield builds were broken because .gitignore excluded files needed
by Docker builds:
- cli/package-lock.json (sandbox image)
- vendor/agentmesh-sdk/package-lock.json (sandbox image npm install)
- vendor/agentmesh-registry/frontend/package-lock.json (registry image npm ci)
- vendor/agentmesh-registry/migrations/ (registry database setup)

Added ! exception rules in .gitignore and force-added all files.

Also fixes 3 AI code suggestions:
- Rename misleading md5_hash() to hash_for_id() in blocklist.rs (uses SipHash)
- Fix Kata nodepool SKU in add.ts to DC4as_v5 (matches up.ts confidential path)
- Fix Go version comment in Dockerfile (1.25 doesn't exist, use 1.23)

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
…trol

Add operator-facing CLI command and router endpoints for managing which
agents can communicate with a sandbox via the AGT mesh:

Router (inference-router/src/ingress.rs):
- IngressAcl struct with per-agent block/allow lists and dynamic trust threshold
- GET /ingress/status — overview (threshold, known agents, blocked/allowed counts)
- GET /ingress/agents — all known agents with trust scores + ACL status
- POST /ingress/block — block an agent from communicating
- POST /ingress/unblock — remove agent from block list
- POST /ingress/allow — explicitly allow (bypasses trust threshold)
- GET /ingress/blocked — list blocked agents
- GET /ingress/allowed — list explicitly allowed agents
- POST /ingress/threshold — update trust threshold at runtime (0-1000)
- All endpoints behind admin token auth (protected routes)
- 3 unit tests for ACL logic

CLI (cli/src/commands/ingress.ts):
- Mirrors azureclaw egress UX pattern (Docker + K8s auto-detection)
- --block/--unblock/--allow/--blocked/--allowed/--agents/--threshold/--status
- Default (no flags) shows status overview with help commands

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

// Default: show status
try {
const [status, blocked, allowed, agents] = await Promise.all([

// Default: show status
try {
const [status, blocked, allowed, agents] = await Promise.all([
@pallakatos

Copy link
Copy Markdown
Collaborator

Thanks for this @johnsonshi — the ingress CLI + router pattern mirrors egress nicely and the coverage is solid. A few items to address before merge:

CI failure

The Rust build is failing. Likely cause: state.governance.all_trust_scores() called in ingress_agents() handler doesn't exist on Governance. You'll need to either add that method to governance.rs or use a different approach to enumerate known agents.

TOCTOU race in IngressAcl

block() and allow() each take separate RwLocks for blocked and allowed sets. A concurrent call could interleave between the two writes, leaving an agent momentarily in both or neither set. Consider using a single RwLock<IngressAclInner> wrapping both sets, or at minimum document that the struct is not fully atomic.

Merge conflict

The PR conflicts with main on cli/package-lock.json (PR #22 just merged with npm 11 lockfile changes). A rebase onto current main should resolve it cleanly.

Nit: enriched vec moved after use

In ingress_agents(), enriched is moved into the count field calculation after being consumed by the JSON serialization — this will be a compile error. Consider computing the count first:

let count = enriched.len();
Json(serde_json::json!({ "agents": enriched, "count": count, ... }))

Overall

The design is clean — 8 endpoints, mirrors egress UX, proper admin auth on K8s. Once the build is green and the race is addressed, this is good to go. 👍

@pallakatos pallakatos closed this May 12, 2026
@pallakatos
pallakatos deleted the johsh/ingress-command branch June 1, 2026 14:39
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.

4 participants