Install & use AIOS Govern

AIOS Govern is a human-in-the-loop governance layer for AI agents. An agent submits an envelope (an approval request, spend approval, choice, info notice, …) to a human inbox, then receives the human's decision back over a signed webhook, a long-poll, or realtime. This page walks an agent or integrator from zero to a working round-trip over both REST and MCP.

1 · Get access

Every call is authenticated with a Bearer key bound to your account. There are two ways to get one:

Self-serve MCP key

Visit /connect and mint an MCP key (kind mcp). This is the fastest path for connecting Claude Desktop, Cursor, or any MCP-capable agent. The key is shown once — store it securely.

REST agent key

REST agent keys (kind agent) are issued through the invite / register-channel flow on /agents:

  1. An admin opens /agents and creates a New invite URL.
  2. The agent POSTs the invite token to /api/register-channel.
  3. The response returns api_key (e.g. aigos_sk_…) and a webhook_secret — both shown only once.

Each key carries scopes (submit, read, close, approve) and is rate-limited. New non-admin sign-ups land in a pending state until an admin approves the account — see the Architecture page for the approval gate.

2 · REST quickstart

Submit an envelope to a channel, then wait for the decision. Submission returns request_id, the resting state, a long_poll_url, and repoll_after_seconds.

# Submit an envelope
curl -X POST https://<your-domain>/api/channels/{channel_id}/requests \
  -H "Authorization: Bearer aigos_sk_..." \
  -H "Content-Type: application/json" \
  -d '{
    "type": "approval_request",
    "title": "Approve domain transfer",
    "summary": "Move example.com to Cloudflare.",
    "risk_level": "high",
    "route": "approval",
    "return_mode": "long_poll",
    "payload": { "domain": "example.com", "to": "cloudflare" }
  }'

# → 201 Created
# {
#   "request_id": "…",
#   "state": "pending",
#   "held": false,
#   "long_poll_url": "/api/requests/…/wait",
#   "repoll_after_seconds": 120
# }

Long-poll for the decision

GET …/wait blocks up to timeout seconds (max 300). On a decision it returns 200 with the decision payload. If still pending it returns 408 with repoll_after_seconds in the body and a matching Retry-After header — honor it and call again.

curl https://<your-domain>/api/requests/{request_id}/wait?timeout=60 \
  -H "Authorization: Bearer aigos_sk_..."

# → 200 OK
# {
#   "request_id": "…",
#   "status": "approved",            // approved | rejected | acknowledged | completed
#   "approved_by": "user_uuid",
#   "decision_time": "2026-06-02T14:12:00Z",
#   "comments": "…",
#   "envelope_snapshot": { ... }
# }

# → 408 Request Timeout    (Retry-After: 120)
# {
#   "error": { "code": "timeout", "message": "Decision pending" },
#   "request_id": "…",
#   "state": "pending",
#   "repoll_after_seconds": 120
# }

Or: receive the signed webhook

Submit with return_mode: "webhook" (or "both") and set return_webhook. On a decision, AIOS Govern POSTs the same decision payload to your URL. Verify the X-AIGOS-Signature header (HMAC-SHA256 of the raw body using your webhook_secret) before trusting it.

X-AIGOS-Signature: sha256=<hmac>
X-AIGOS-Timestamp: <unix_seconds>
X-AIGOS-Delivery: <delivery_id>

const expected = "sha256=" + crypto
  .createHmac("sha256", webhook_secret)
  .update(rawBody)
  .digest("hex");
// timing-safe compare expected vs X-AIGOS-Signature

3 · MCP quickstart

AIOS Govern exposes a streamable-HTTP MCP server at /api/mcp. Point any MCP client (Claude Desktop, Cursor, …) at it with your key. See the MCP page for the full reference.

{
  "mcpServers": {
    "aigos": {
      "url": "https://<your-domain>/api/mcp",
      "headers": {
        "Authorization": "Bearer <YOUR_MCP_KEY>"
      }
    }
  }
}

Agents discover the full toolset at connect time via the standard tools/list JSON-RPC method — no hard-coding required. The tools:

Each key is scoped (submit / read / close / approve) and tools are gated accordingly. Separation of duties is enforced: a key cannot approve a request its own agent submitted under route: "approval". Use approval_self only where the submitter is allowed to self-approve.

4 · The envelope shape

The canonical request body. type, title, summary, risk_level and return_mode are required; the rest have sensible defaults.

{
  // ── required ──────────────────────────────────────────────
  "type": "approval_request",   // | informational | permission_elevation
                                //   | escalation | review_request | poll_request
                                //   | choice | input_request | spend_approval
  "title": "Approve domain transfer",   // 1–300 chars
  "summary": "Move example.com.",        // 1–2000 chars
  "risk_level": "high",                  // low | medium | high | critical
  "return_mode": "long_poll",            // webhook | long_poll | both

  // ── payload + decision rule ───────────────────────────────
  "payload": { },                        // arbitrary JSON (options, amounts, …)
  "approval_rule": { "type": "single" }, // | sequential | n_of_x | chain

  // ── routing / authority — WHO acts (default "approval") ───
  "route": "approval",          // log_only | approval | approval_self
                                //   | approve_manager | approve_team
                                //   | approve_any_admin | notify_manager
                                //   | notify_team_owner
  "team_id": "uuid",            // required for approve_team / notify_team_owner

  // ── presentation hints ────────────────────────────────────
  "bg_color": "#1b1b1b",        // card background
  "badge": "URGENT",            // diagonal corner ribbon
  "accent_bar": "green",        // left edge bar
  "pulse": "none",              // none | light | strong
  "top_sort": false,            // pop to top of inbox/lists

  // ── expiry / TTL ──────────────────────────────────────────
  "expires_in_seconds": 3600,   // window (30..2592000); re-arms on escalate
  "expires_at": "2026-06-02T18:00:00Z",  // or an absolute timestamp
  "on_timeout": "escalate",     // escalate | auto_approve | auto_reject | expire
  "poll_interval_seconds": 120, // re-poll cadence (60..432000); omit to auto-pick

  // ── delivery + tweak round-trip ───────────────────────────
  "return_webhook": "https://yourapp.example.com/aigos-callback",
  "long_poll_timeout_seconds": 300,      // 1..300
  "tweak_allowed": false,       // let a human bounce it back with edits
  "parent_envelope_id": "uuid"  // set when answering a tweak with a NEW card
}

5 · Acting on requests

Decisions (human side)

Humans act through the inbox UI, which hits the request endpoints: approve, reject, request-info, escalate, delegate, and mark-informational (acknowledge). Each transition is recorded as an immutable decision in the audit trail.

Answering a needs_info question (agent side)

When a human picks request info, the envelope moves to needs_info. Your agent answers by POSTing to the respond endpoint, which records the answer in the decision thread and moves the envelope back to pending for re-review:

curl -X POST https://<your-domain>/api/requests/{request_id}/respond \
  -H "Authorization: Bearer aigos_sk_..." \
  -H "Content-Type: application/json" \
  -d '{ "answer": "The transfer is reversible within 5 days." }'

# → 200 OK  { "request_id": "…", "state": "pending" }

The tweak round-trip

Submit with tweak_allowed: true and a human can bounce the envelope back with edits instead of a hard approve/reject. You receive a tweak_requested event — over your webhook and on the long-poll response — carrying the requested change and an envelope_snapshot. Process it and usually submit a new envelope with parent_envelope_id set to the original; that resolves the tweak and links the cards into one history. A human can also withdraw the tweak (tweak_withdrawn).

{
  "event": "tweak_requested",
  "request_id": "…",
  "tweak_id": "…",
  "request_text": "Reduce the spend cap to $500 and resubmit.",
  "requested_by": "user_uuid",
  "envelope_snapshot": { ... }
}

Expiry & escalate-rearm

An envelope with expires_in_seconds + on_timeout: "escalate" will, on lapse, escalate to a higher authority and re-arm the same countdown window so it keeps pressing for a decision. Other timeout modes — auto_approve, auto_reject, expire — resolve terminally instead.

Next: how the system fits together, the full REST reference, or the MCP server docs.