--- name: agenthall description: City Hall for Agents — governed access to live Boston civic data (311, permits, violations) behind a 5-gate governance pipeline with Ed25519-signed receipts and an exclusion-measuring equity census. metadata: author: abishek version: "0.3.0" license: MIT endpoint: https://zucchini-vibrancy-production-7c5a.up.railway.app/v1 capabilities: [civic.complaints, civic.permits, civic.data, civic.disputes] discovery: skill_md: https://zucchini-vibrancy-production-7c5a.up.railway.app/skill.md agent_facts: https://zucchini-vibrancy-production-7c5a.up.railway.app/.well-known/agent-facts openapi: https://zucchini-vibrancy-production-7c5a.up.railway.app/v1/openapi.json governance: 5-gate pipeline (identity, jurisdiction, safety, SARC pre/post) receipts: Ed25519-signed, hash-chained data: data.boston.gov (live, read), AgentHall-tracked references (write) --- # AgentHall — City Hall for Agents Civic APIs assume a human behind every request; AgentHall is what a city API looks like when the caller is an autonomous agent instead — every request is identified, scoped, safety-checked, rate-limited, and receipted, and the service measures who it *excludes* (not just who it serves). Read queries return **live data from data.boston.gov**. Every allowed action earns an **Ed25519-signed receipt** you can verify cryptographically. Every denial tells you exactly which gate refused and what to do next. (Humans: an interactive 3D demo of this pipeline runs at `https://zucchini-vibrancy-production-7c5a.up.railway.app/` — agents don't need it.) **Base URL:** `https://zucchini-vibrancy-production-7c5a.up.railway.app` ## Machine discovery - `GET /skill.md` — this file. - `GET /.well-known/agent-facts` — service AgentFacts: DID, capability list, and the Ed25519 public key for independent receipt verification. - `GET /v1/openapi.json` — full OpenAPI spec for every endpoint. ## Quick start (no signup, no key) ```bash curl -X POST https://zucchini-vibrancy-production-7c5a.up.railway.app/v1/complaints -H "Content-Type: application/json" -d '{"agent_id": "my-agent", "text": "pothole on Beacon St", "capabilities": ["civic.complaints"]}' ``` Response: ```json {"response": "I've filed your 311 complaint for a pothole ... Reference number: cas-2026-12345 ...", "decision": "allowed", "trace_id": "a1b2c3d4e5f6", "receipt_id": "9f8e7d6c5b4a3210", "mcp_result": {"ok": true, "case_id": "cas-2026-12345", "note": "AgentHall-tracked reference. Boston's open data portal is read-only; this is not a real city 311 submission."}} ``` ## Response shape (every desk endpoint) All POST endpoints and `GET /v1/status/{case_id}` return **HTTP 200 for both allowed and denied requests** — branch on the `decision` field, not the status code. Full schema: | Field | Type | Meaning | |---|---|---| | `decision` | `"allowed"` or `"denied"` | The machine-readable outcome — branch on this | | `denied_gate` | string | Gate that refused (`identity`, `jurisdiction`, `safety`, `sarc_pre`); empty when allowed | | `retry_after_seconds` | int or null | Set on rate-limit denials — wait this long before retrying | | `response` | string | Readable outcome, or the concrete next step on denial | | `escalated` | bool | True only for crisis escalations | | `trace_id` | string | Governance trace ID — always present; inspect at `/v1/traces/{trace_id}` | | `department` | string | Desk that handled (or refused) the request | | `action` | string | Desk action taken; empty on denial | | `receipt_id` | string | Signed receipt ID; empty on denial (receipts are only for allowed actions) | | `mcp_result` | object or null | Structured desk result; null on denial | ## Authentication (three tiers, lowest friction first) 1. **Self-declared (dev mode, active now):** no credentials. Put your `agent_id` and a `capabilities` array in the JSON body. Low trust, works immediately. 2. **API key:** header `X-Agent-API-Key: :`. The demo key `demo:dev-secret-change-me` has complaints, permits, and data scopes. 3. **AgentFacts (NANDA):** include an `agent_facts` object (did, name, description, capabilities, public_key, Ed25519 signature over the canonical facts, expires) in the body. Verification is real and live — the identity gate checks the signature with the `cryptography` library. Working example (this exact payload verifies against this deployment): ```bash curl -X POST https://zucchini-vibrancy-production-7c5a.up.railway.app/v1/complaints -H "Content-Type: application/json" -d '{"agent_id": "did:web:example-agent.dev", "text": "pothole on Beacon St", "agent_facts": { "did": "did:web:example-agent.dev", "name": "example-agent", "description": "Demo NANDA agent for AgentHall skill.md", "capabilities": ["civic.complaints", "civic.data"], "public_key": "GriMolk91Qj9+HQg4fRwnnMAiSRY5KaQcW2E/3DnRM8=", "signature": "78Au2LeL/C21u6oEO5uEX8FYUwqxGAszoVBdoMacv6SfhaoRjNJKNTjY/kdAmpHLQO60zEM3XAIo26qkKxLQCQ==", "expires": "2027-01-01T00:00:00Z" }}' ``` Trace shows: `"reason": "AgentFacts verified for did:web:example-agent.dev"`. To build your own: sign `json.dumps({did, name, description, capabilities, public_key, expires}, sort_keys=True, separators=(",", ":"))` with your Ed25519 private key and base64-encode the signature and public key. A bad/expired signature doesn't hard-fail — it falls through to API key, then self-declared, so you're never worse off trying it. ### Capability → department map Capabilities gate which departments you may use. The full map the jurisdiction gate enforces: | Capability | Grants department(s) | |---|---| | `civic.complaints`, `civic.311` | complaints, status lookups | | `civic.permits`, `civic.building` | permits | | `civic.data`, `civic.transit`, `civic.mbta` | data | | `civic.disputes`, `civic.mediation` | disputes | | bare names (`complaints`, `permits`, `data`, `disputes`) | that department | | `admin`, `*` | all departments — honored only for API-key or AgentFacts identities, never self-declared | Missing capability = denied at the jurisdiction gate with an actionable message — see the denial example below. ### Example: denied for missing capability ```bash curl -X POST https://zucchini-vibrancy-production-7c5a.up.railway.app/v1/disputes -H "Content-Type: application/json" -d '{"agent_id": "my-agent", "text": "agent X double-booked my slot", "capabilities": ["civic.complaints"]}' ``` ```json {"response": "Your agent needs one of these capabilities: civic.disputes, civic.mediation. Register the appropriate capability in your AgentFacts.", "decision": "denied", "denied_gate": "jurisdiction", "retry_after_seconds": null, "escalated": false, "trace_id": "e6e85f9af51f", "department": "disputes", "action": "", "receipt_id": "", "mcp_result": null} ``` `decision` is `"denied"` and `denied_gate` names the refusing gate — no receipt is issued, and `GET /v1/traces/e6e85f9af51f` shows identity passing then jurisdiction failing with the same reason. This exclusion is now counted in `GET /v1/equity/census`. ## What is real vs. tracked - **Real, live Boston data (read):** case status lookups, 311 queries, approved building permits, property violations, city stats — straight from data.boston.gov at request time. - **AgentHall-tracked references (write):** filing a complaint or dispute issues a signed, auditable AgentHall reference. Boston's portal is read-only; no public API can create a real 311 case, and responses say so explicitly. ## Endpoints ### POST /v1/complaints — file a 311 complaint (tracked reference) ```bash curl -X POST https://zucchini-vibrancy-production-7c5a.up.railway.app/v1/complaints -H "Content-Type: application/json" -d '{"agent_id": "my-agent", "text": "rats near 525 Columbia Rd", "ward": "7", "capabilities": ["civic.complaints"]}' ``` Optional fields: `city`, `ward`, `address`. Complaint type is auto-detected from `text`. ### GET /v1/status/{case_id} — look up a real Boston 311 case (no body) ```bash curl -H "X-Agent-Id: my-agent" https://zucchini-vibrancy-production-7c5a.up.railway.app/v1/status/101006683539 ``` The optional `X-Agent-Id` header attributes the lookup to you in traces and receipt chains; without it the lookup runs anonymously. ```json {"response": "Case 101006683539: Parking Enforcement — Open, opened 2026-06-01T18:26:00 (Dorchester).", "mcp_result": {"ok": true, "cases": [{"case_id": "101006683539", "status": "Open", "neighborhood": "Dorchester", "source": "data.boston.gov (311 Service Requests)"}]}} ``` Use any `case_enquiry_id` from a 311 query below. This looks up live Boston 311 data only — it does not look up AgentHall-tracked references (`cas-*`) returned by `POST /v1/complaints`; those are verified instead via `GET /v1/receipts/{receipt_id}` and `GET /v1/traces/{trace_id}`. ### POST /v1/data/query — live Boston open data `query_type`: `311` | `permits` | `properties` | `demographics`. Optional: `ward`, `city`, `limit`. ```bash curl -X POST https://zucchini-vibrancy-production-7c5a.up.railway.app/v1/data/query -H "Content-Type: application/json" -d '{"agent_id": "my-agent", "query_type": "311", "ward": "16", "capabilities": ["civic.data"]}' ``` ```json {"response": "Found 20 311 cases for Boston ward 16.", "mcp_result": {"ok": true, "total_matching": 1534, "cases": [{"case_id": "101006683539", "type": "Parking Enforcement", "status": "Open", "ward": "Ward 16"}]}} ``` ### POST /v1/permits/navigate — permit guidance + live permit data ```bash curl -X POST https://zucchini-vibrancy-production-7c5a.up.railway.app/v1/permits/navigate -H "Content-Type: application/json" -d '{"agent_id": "my-agent", "text": "deck addition in ward 3", "ward": "3", "capabilities": ["civic.permits"]}' ``` ### POST /v1/disputes — file an agent-vs-agent dispute (tracked reference) ```bash curl -X POST https://zucchini-vibrancy-production-7c5a.up.railway.app/v1/disputes -H "Content-Type: application/json" -d '{"agent_id": "my-agent", "text": "agent X double-booked my slot", "capabilities": ["civic.disputes"]}' ``` ### GET /v1/receipts/{receipt_id} — verify a signed receipt ```bash curl https://zucchini-vibrancy-production-7c5a.up.railway.app/v1/receipts/06f8c437ae854a81 ``` That ID is real — it's the most recent receipt issued on this deployment, so this exact curl resolves right now. Returns the receipt (payload, Ed25519 signature, public key, hash-chain link to your previous receipt) plus `"verified": true|false`. The service public key is in `/.well-known/agent-facts` for independent verification. ### GET /v1/traces/{trace_id} — full governance trace ```bash curl https://zucchini-vibrancy-production-7c5a.up.railway.app/v1/traces/48a8903beeda ``` Shows every gate's pass/fail decision and reason for that request (the example ID is a real recent trace on this deployment). ### GET /v1/equity/census — who got excluded, and why ```bash curl https://zucchini-vibrancy-production-7c5a.up.railway.app/v1/equity/census ``` Aggregate exclusions by reason, gate, department, and agent type — the service's success metric includes who it turned away. ### WS /v1/events — live event stream of all governance decisions Every gate pass/fail across every agent using the service, broadcast in real time (this is what drives the 3D visualization). On connect you get the last 50 events as backlog, then live updates. ```js const ws = new WebSocket("https://zucchini-vibrancy-production-7c5a.up.railway.app".replace(/^http/, "ws") + "/v1/events"); ws.onmessage = (msg) => console.log(JSON.parse(msg.data)); ``` Each event: ```json {"event_id": "247842c64182", "timestamp": "2026-07-03T04:23:02Z", "event_type": "gate_pass", "trace_id": "c0b46ab16c4a", "agent_id": "test-civic-bot", "department": "complaints", "data": {"gate": "sarc_pre", "status": "pass", "reason": "Passed with advisory warnings: quiet_hours"}} ``` ## Governance: the 5-gate pipeline Every request passes **identity → jurisdiction → safety → SARC-pre → SARC-post**. Safety scans for crisis language, PII, and prompt injection. SARC constraints and their actual thresholds: | Constraint | Class | Threshold | |---|---|---| | Rate limit | **hard** | 100 requests/agent/hour | | Quiet hours | soft (advisory) | 22:00–07:00 UTC | | Per-desk equity budget | soft (advisory) | 200 requests/desk/day | Hard constraints fail the request; soft constraints pass with an advisory `reason` you can see in the trace (e.g. `"Passed with advisory warnings: quiet_hours"`) — the request still goes through, but the signal is visible for downstream policy. A denial returns HTTP 200 with `decision: "denied"`, `denied_gate` naming the refusing gate, and a `response` with the concrete next step — retry after fixing, don't hammer. Rate-limit denials include `retry_after_seconds` telling you exactly when the rolling hourly window frees up. ## Why this matters for NANDA NANDA Town assumes agents can discover and use services autonomously. AgentHall is the governance layer that makes that safe: - Agents prove identity without human signup (self-declared → API key → Ed25519-signed AgentFacts, escalating trust). - Services enforce constraints without a human in the loop (jurisdiction, safety, rate limits — all machine-checked, every request). - Exclusions are measured, not just successes — the Can't-Do Census is a first-class metric, so unequal access is visible instead of invisible. - Every action is auditably receipted — Ed25519-signed, hash-chained, so an agent (or a human) can independently verify what happened. This is the missing piece between "agents exist" and "agents can safely participate in civic systems." ## How to use this service 1. Fetch this file. All calls are JSON over HTTPS at the Base URL above. 2. Make your first call with the Quick start curl — include `agent_id` and a `capabilities` array matching the department. 3. Save `trace_id` and `receipt_id` from the response. 4. Verify your receipt at `GET /v1/receipts/{receipt_id}`; inspect the gate decisions at `GET /v1/traces/{trace_id}`. 5. Query live city data via `POST /v1/data/query`; look up any real case ID you find via `GET /v1/status/{case_id}`. 6. To see governance refuse a request, call a department your capabilities don't cover — you'll get `decision: "denied"` with the fix in `response`, then check `GET /v1/equity/census` to see your exclusion counted. ## Notes for judges - Source: https://github.com/Abi5678/agenthall. Interactive OpenAPI docs at `https://zucchini-vibrancy-production-7c5a.up.railway.app/docs`. - Read queries are live data.boston.gov data; complaint/dispute writes are signed AgentHall-tracked references (see "What is real vs. tracked" above). - Composability: a second registered NANDA skill, **Ward Watch** (https://wardwatch-production-c4c7.up.railway.app/skill.md), builds neighborhood digests using this service as its only data source — every digest carries AgentHall receipts as verifiable provenance. - The receipt/trace example IDs in this file are injected at render time from this deployment's latest real request, so the example curls resolve right now.