MAQPNADocs

Debug and observe

Find out what a session did and why a call was denied, with the timeline, the audit ledger, sandbox logs, events and OpenTelemetry GenAI traces.

When an agent misbehaves, you need two views: what the agent process did (its logs) and what the gateway decided (the audit ledger). MAQPNA keeps them apart on purpose: the logs are yours, the ledger is append-only and hash-chained. This page shows where each view lives, locally and in a cluster, and a step-by-step workflow for the most common question: why was this call denied?

flowchart TD
    Q["A call was denied<br/>or behaved oddly"] --> E["1 Read error.data:<br/>policy, rule, reason"]
    E --> R["2 Find the audit record<br/>timeline / audit tail"]
    R --> P{"reason?"}
    P -->|"policy_denied, default_action,<br/>no_applicable_policy"| PT["3a maqpna policy test --explain"]
    P -->|revoked| RV["3b maqpna revocations list"]
    P -->|budget_exceeded| BU["3c maqpna budgets list --exceeded"]
    P -->|"dlp:*"| DL["3d record ext.dlp*, DLP profile"]
    P -->|"tool_definition_changed"| PN["3e maqpna mcp tools --drift-only"]
    P -->|"taint in rule"| TA["3f maqpna taint get"]
    P -->|"missing_scope, token_*"| TK["3g maqpna token inspect"]

Where to look#

Question Local (maqpna dev) Cluster
What did the session do, step by step? maqpna dev timeline --last or --session S maqpna session describe S -n NS, the console's session page
Every decision, with policy and rule maqpna audit tail --session S maqpna audit tail --gateway URL --session S -n NS -f
What did the agent process print? your terminal (it runs in the foreground) maqpna logs S -n NS -f
Kubernetes events (sandbox, pods, tokens) — maqpna events S -n NS, with --audit to merge the gateway's records
CPU, memory, tokens and cost per session maqpna session cost S maqpna top sessions -n NS --sort-by cost
Gateway and broker logs .maqpna/logs/ kubectl logs -n maqpna-system deploy/maqpna-gateway
One request across agent, gateway and upstream traceparent + maqpna audit tail --trace ID OpenTelemetry traces in your backend

Load the local admin token first for the admin-API commands: eval "$(maqpna dev env)".

The timeline#

The timeline is the ledger-ordered view of one session: tool calls, model calls, approvals and the result your agent reported.

maqpna dev timeline --last
TIME      KIND        SERVER/TOOL                   DECISION          DETAIL
17:31:58  approval    echo/delete_resource          require_approval  apr_31bc56b1ef96a592333824db approved
17:31:58  tool_call   echo/delete_resource          allow             approval:approved by reviewer@localhost (apr_31bc56b1ef96a592333824db)
17:31:58  tool_call   echo/delete_resource          deny              approval_invalid: approval: already used
17:31:58  model_call  llm/stub/chat.completions     allow             scope:models:stub; model:stub; usage:prompt=8,completion=44
17:31:58  model_call  llm/stub/models               allow             scope:models:stub; model:stub
17:31:58  model_call  llm/stub/messages             allow             scope:models:stub; unsupported_by_provider: model provider openai cannot serve this request: native Anthropic Messages requests need a route with provider anthropic or bedrock
17:31:58  result      maqpna-session/report_result  observe           session_result:Succeeded
dev-curl2: 4 allow, 1 deny, cost $0.0000 (maqpna dev timeline --last)

-o json prints every field. MAQPNA Desk's Local (dev) window shows the same timeline live (maqpna desk). In a cluster, maqpna session describe merges the AgentSession, its sandbox and pods with the gateway's view: cost, taint and pending approvals.

The audit ledger#

Each record is one decision. audit tail reads it through the gateway's admin API:

maqpna audit tail --session dev-curl2
2026-10-18T21:31:58.265Z #4 dev/dev-curl2 coder echo/delete_resource require_approval default-agent-policy/destructive-needs-human approval_pending:apr_31bc56b1ef96a592333824db
2026-10-18T21:31:58.341Z #5 dev/dev-curl2 coder echo/delete_resource allow default-agent-policy/destructive-needs-human approval:approved by reviewer@localhost (apr_31bc56b1ef96a592333824db) approver=reviewer@localhost
2026-10-18T21:31:58.358Z #6 dev/dev-curl2 coder echo/delete_resource deny default-agent-policy/destructive-needs-human approval_invalid: approval: already used
2026-10-18T21:31:58.382Z #7 dev/dev-curl2 coder llm/stub/chat.completions allow - scope:models:stub; model:stub; usage:prompt=8,completion=44
2026-10-18T21:31:58.403Z #8 dev/dev-curl2 coder llm/stub/models allow - scope:models:stub; model:stub
2026-10-18T21:31:58.424Z #9 dev/dev-curl2 coder llm/stub/messages allow - scope:models:stub; unsupported_by_provider: model provider openai cannot serve this request: native Anthropic Messages requests need a route with provider anthropic or bedrock
2026-10-18T21:31:58.445Z #10 dev/dev-curl2 coder maqpna-session/report_result observe session/result session_result:Succeeded

-o json shows the whole record, including ext, where the details of each check live. A DLP denial, for example (shortened: the time, latency, cost and hash-chain fields are left out):

{
  "seq": 14, "session": "dev-dlp2", "namespace": "dev", "agent": "coder", "user": "you@localhost",
  "server": "echo", "tool": "echo",
  "argsSha256": "3a6fa4e5f2a367f992b13fc53f6a94cb4c45a922acc588f3efbea4c4621430bf",
  "decision": "deny", "rule": "dlp/dev", "reason": "dlp:aws_access_key",
  "ext": {"dlp": "aws_access_key:1", "dlpAction": "deny", "dlpBlocked": "request:aws_access_key", "dlpProfile": "dev"}
}

The ledger stores a hash of the arguments, never the arguments or prompts. Filters: --session, -n, --agent, --user, --trace, --since 1h, --tail 50, and -f to follow. Prove nothing was changed with maqpna audit verify .maqpna/audit.jsonl (or --gateway URL in a cluster). Record fields are described in Audit.

Why was this call denied?#

Step 1: Read the error your code got#

error.data names the policy, the rule and the reason. Log all three; they are what the steps below need.

error -32001: denied by policy baseline-guardrails rule never-touch-system-namespaces: system namespaces are off-limits to agents
data: {"detail":"system namespaces are off-limits to agents","domain":"maqpna.com","policy":"baseline-guardrails","reason":"policy_denied","rule":"never-touch-system-namespaces"}

Step 2: Find the record#

maqpna audit tail --session dev-aa0511c1 -o json          # local
maqpna audit tail --gateway "$GW" --session fix-4821 -n team-a -o json   # cluster

The record's rule is <policy>/<rule> and its ext carries the detail of the check that decided.

Step 3: Ask why, per reason#

Reason Command What it tells you
policy_denied, default_action maqpna policy test --policy policies/ --ns NS --agent A --server S --tool T --args '{...}' --explain Each rule, whether it matched, and why not (tool "x" not in tools).
the same, against what the gateway has loaded maqpna policy test --gateway "$GW" --ns NS --agent A --server S --tool T The decision with the live policies and the session's taints.
no_applicable_policy maqpna policy list -n NS, maqpna agent describe A -n NS Whether any policy selects this agent in this namespace.
revoked maqpna revocations list --gateway "$GW" The revocation, who created it, why and until when.
budget_exceeded:<scope>/<window> maqpna budgets list --exceeded, maqpna costs summary --scope agent --window day Which budget is spent and by whom.
dlp:<detector> maqpna audit tail ... -o json (ext.dlp*) The detector, the profile and whether the request or the response matched.
tool_definition_changed, tool_unpinned maqpna mcp tools NS/SERVER --drift-only Which tool drifted from its pin.
a rule with sessionTaints maqpna taint get SESSION -n NS The session's taint labels and where they came from.
missing_scope:*, token_* maqpna token inspect "$TOKEN" The token's scopes, expiry, agent and user.
approval_denied, approval_expired maqpna approvals get ID Who decided, when, and the note.

Step 4: Fix it in the right place#

  • The agent did something it should not: change the code or the prompt, and let it handle the denial (see Handle decisions).
  • The policy is wrong: change it, add a case to your PolicyTest suite, and replay the ledger with maqpna replay before you ship it.
  • A revocation or a spent budget: those are decisions by people; talk to whoever made them.

Logs and events in a cluster#

maqpna logs fix-4821 -n team-a -f --since 10m --timestamps
maqpna logs ag/triage -n team-a                  # the agent's newest running session
maqpna events fix-4821 -n team-a                 # session, sandbox, pods, snapshots
maqpna events -n team-a --audit --since 30m --gateway "$GW"

maqpna logs finds the sandbox pods by the maqpna.com/session label; -c agent limits it to your container. Token mints and refreshes show as events TokenMinted and TokenRefreshed.

Traces with OpenTelemetry GenAI#

Pass W3C trace context from your agent and the gateway continues your trace: it creates spans that follow the OpenTelemetry GenAI conventions (chat {model}, tools/call {tool} with gen_ai.operation.name=execute_tool, invoke_agent {agent}), forwards traceparent to the model, MCP server or called agent, and records the trace ID in the audit record.

client = MaqpnaClient.from_env(traceparent=lambda: current_traceparent())   # or env TRACEPARENT
client.tools("github").call_tool("get_issue", {"number": 4821}, traceparent="00-4bf92f3577b34da6a3ce929d0e0e4736-00f067aa0ba902b7-01")
const client = MaqpnaClient.fromEnv(process.env, { traceparent: () => currentTraceparent() });
client, err := maqpna.NewFromEnv(ctx, maqpna.WithTraceSource(func(ctx context.Context) (string, string) {
    return traceparentFromSpan(ctx) // from your OpenTelemetry span
}))
curl -sS "$MAQPNA_TOOL_ECHO_URL" -H "Authorization: Bearer $MAQPNA_TOKEN" -H 'Content-Type: application/json' \
  -H 'traceparent: 00-4bf92f3577b34da6a3ce929d0e0e4736-00f067aa0ba902b7-01' \
  -d '{"jsonrpc":"2.0","id":1,"method":"tools/call","params":{"name":"get_time","arguments":{}}}'

Then find the decision for that trace:

maqpna audit tail --trace 4bf92f3577b34da6a3ce929d0e0e4736
2026-10-18T21:55:17.263Z #28 dev/dev-trace coder echo/get_time allow baseline-guardrails default_action trace=4bf92f3577b34da6a3ce929d0e0e4736

The gateway exports spans when tracing is enabled (gateway.tracing.enabled and endpoint in the chart). Prompts and arguments are not exported unless the platform team opts in to DLP-redacted content capture. See Trace agents with OpenTelemetry GenAI and OpenTelemetry.

Next steps#