Build on MAQPNA
What a governed agent is, the developer workflow from first line of code to production, and which path to take for your language or framework.
This guide is for developers who write AI agents that run under MAQPNA. It takes you from an empty directory to an agent that runs in a cluster, step by step: set up a local MAQPNA, write the agent, call tools and models through the MAQPNA gateway, handle the gateway's decisions in code, test the agent and its policies, deploy it and find out why a call was denied.
You do not need a Kubernetes cluster for most of it. maqpna dev up runs the gateway, the identity broker and a test MCP server on your laptop, and maqpna dev run gives your agent the same environment a sandbox gets in a cluster. Code that works locally runs unchanged in a cluster.
Who this is for#
| You are | Start with |
|---|---|
| Writing a new agent in Python, TypeScript or Go | Set up your environment, then Call tools over MCP |
| Porting an agent built on LangGraph, the OpenAI Agents SDK, the Claude Agent SDK, CrewAI or the Vercel AI SDK | Framework adapters and Use a framework template |
| Running an agent you cannot change much (any language, any HTTP client) | Connect to the gateway and the curl tabs on each page |
| A platform engineer reviewing what agents will do | Policies and decisions and Test agents and policies |
What a governed agent is#
An agent is an AI program that uses tools. MAQPNA runs each run of it, a session, in its own sandbox with default-deny networking. The only way out of the sandbox is the gateway. The gateway checks every tool call, model call and agent-to-agent (A2A) call before it reaches anything real, and records the decision in the audit ledger.
flowchart LR
subgraph SB["Sandbox (one session)"]
A["Your agent<br/>SDK or HTTP client"]
end
IB["Identity broker"] -->|"session token<br/>/var/run/maqpna/token"| A
OP["Operator"] -->|"creates sandbox,<br/>env, token Secret"| SB
A -->|"Bearer session token"| GW["MAQPNA gateway"]
GW -->|"/mcp/server"| T["MCP servers<br/>(tools)"]
GW -->|"/llm/route"| M["Models"]
GW -->|"/a2a/ns/agent"| P["Other agents"]
GW --> L[("Audit ledger")]
H["Approver<br/>Desk, console, CLI"] -->|"approve / deny"| GW
| Component | What it does for your agent |
|---|---|
| Operator | Turns your Agent and AgentSession resources into a sandbox, a session token and the environment variables the SDK reads. |
| Identity broker | Signs a short-lived session token: agent, namespace, session, the user the agent acts for, and scopes such as tools:github. |
| Gateway | Verifies the token, then runs the kill switch, data loss prevention (DLP), scope, tool pins, policy, budgets and approvals on each call. It holds the real credentials for tools and models; your agent never sees them. |
| Audit ledger | An append-only, hash-chained record of every decision, approval and session result. |
Your code needs to know three things: the gateway URL, the session token and the name of each MCP server. The operator (or maqpna dev run) puts all three in the environment, and the SDKs read them with one call.
The developer workflow#
flowchart LR
W["1 Write<br/>agent + manifests"] --> R["2 Run locally<br/>maqpna dev up / dev run"]
R --> T["3 Test<br/>policy test, validate, unit tests"]
T --> D["4 Deploy<br/>image + maqpna apply"]
D --> O["5 Observe<br/>timeline, audit, traces"]
O -->|"denied? too costly?<br/>change code or policy"| W
- Write the agent code and its manifests: an
Agent, aToolPolicyand the MCP servers it uses. See Anatomy of an agent project. - Run locally with
maqpna dev upandmaqpna dev run -- <your command>. Every tool and model call goes through a real gateway. See Set up your environment. - Test the policy offline with
maqpna policy test, the manifests withmaqpna validate, and the agent code against the local gateway. See Test agents and policies. - Deploy the image and manifests to a cluster with
maqpna apply, kubectl or GitOps. See Package and deploy. - Observe what each session did: the timeline, the audit ledger, logs and OpenTelemetry traces. See Debug and observe.
Choose your path#
All paths reach the same gateway endpoints and get the same decisions. Pick the one that matches your code.
| Path | Use it when | Tool calls | Model calls | Read |
|---|---|---|---|---|
Python SDK (kosa8-maqpna, import maqpna) |
Python 3.10 or later | client.tools("echo").call("echo", text="hi") |
official openai package, base URL from client.model_endpoint() |
Python SDK |
TypeScript SDK (@kosa8/maqpna) |
Node.js 20.3 or later, ESM | await client.tools("echo").call("echo", {text: "hi"}) |
official openai package, client.modelEndpoint() |
TypeScript SDK |
| Go SDK | Go 1.24 or later, standard library only | client.CallTool(ctx, "echo", "echo", args) |
any OpenAI-compatible client, client.ModelBaseURL(route) |
Go SDK |
| Framework adapter | LangGraph, LangChain, OpenAI Agents SDK, Claude Agent SDK, CrewAI, Vercel AI SDK | governed tools for the framework | the framework's OpenAI or Anthropic client | Framework adapters |
| Plain HTTP | Any language; an agent you cannot add a dependency to | MCP JSON-RPC over HTTP with Authorization: Bearer |
OpenAI-compatible HTTP | Connect to the gateway |
Existing agent under maqpna dev run |
You want to see what an unmodified agent does before you change it | point its MCP client at MAQPNA_TOOL_<NAME>_URL |
point its OpenAI client at OPENAI_BASE_URL (with --stub-llm) |
Run your own agent |
A governed agent in a few lines#
This is the whole contract between your code and MAQPNA: build a client from the environment, call a tool on a named MCP server, catch the decision, report the result.
from maqpna import MaqpnaClient, PolicyDenied, Result
client = MaqpnaClient.from_env()
echo = client.tools("echo")
try:
print(echo.call("echo", text="hello").text)
except PolicyDenied as e:
print("denied:", e.rule, e.reason)
client.report_result(Result.SUCCEEDED, "said hello")
import { MaqpnaClient, PolicyDenied } from "@kosa8/maqpna";
const client = MaqpnaClient.fromEnv();
try {
console.log((await client.tools("echo").call("echo", { text: "hello" })).text);
} catch (e) {
if (e instanceof PolicyDenied) console.log("denied:", e.rule, e.reason);
else throw e;
}
await client.reportResult("Succeeded", "said hello");
client, err := maqpna.NewFromEnv(ctx)
if err != nil {
return err
}
res, err := client.CallTool(ctx, "echo", "echo", map[string]any{"text": "hello"})
var denied *maqpna.PolicyDenied
if errors.As(err, &denied) {
fmt.Println("denied:", denied.Rule, denied.Reason)
} else if err == nil {
fmt.Println(res.Text())
}
_, err = client.ReportResult(ctx, maqpna.Succeeded, "said hello")
Run it with maqpna dev run -- python agent.py (or node agent.mjs, go run .) on a laptop. In a cluster the same code runs unchanged in a sandbox.
Local and cluster: what stays the same#
Local (maqpna dev) |
Cluster | |
|---|---|---|
| Gateway | http://127.0.0.1:8080, file ledger in .maqpna/ |
The installation's gateway, Postgres state and ledger sinks |
| Session token | MAQPNA_TOKEN, 1 hour by default |
File at /var/run/maqpna/token, refreshed by the operator |
| MCP servers | echo plus --upstream NAME=URL |
MCPServer resources and Agent.spec.tools |
| Policy | Built-in dev policy, or --policy FILE (bundle JSON) |
ToolPolicy resources |
| Isolation | None: your process runs on your machine | A sandbox at the agent's trust tier, default-deny egress |
| Your code | The same | The same |
What the gateway can decide#
Every governed call ends in one decision. Your code should expect all of them:
| Decision | What your code sees | Page |
|---|---|---|
| Allowed | The tool result or model completion | Call tools over MCP |
| Denied by a rule, default deny, DLP, a tool pin or taint | JSON-RPC -32001, PolicyDenied with reason |
Handle decisions |
| Held for approval | The call waits, or -32002 with an approval ID in async mode |
Handle decisions |
| Budget exceeded | -32001 with budget_exceeded:<scope>/<window>, or HTTP 402 on the model route |
Call models |
| Revoked (kill switch) | -32001 with revoked |
Handle decisions |
| Token bad or expired, scope missing | -32003, Unauthorized |
Connect to the gateway |
The pages in this guide#
- Set up your environment: install the CLI, start a local MAQPNA, contexts, MAQPNA Desk.
- Anatomy of an agent project: files, manifests and how they relate.
- Connect to the gateway: identity, session token, environment variables.
- Call tools over MCP: list and call tools, read results, tool pins.
- Call models: the
/llmroute, OpenAI and Anthropic clients, budgets and DLP. - Handle decisions in code: denials, approvals, budgets, revocations, retries.
- Call another agent: A2A with delegation.
- Test agents and policies: offline tests and a CI pipeline.
- Package and deploy: image, manifests, promotion, GitOps.
- Debug and observe: logs, timeline, audit ledger, traces.
- Cookbook: short recipes for common tasks.
Next steps#
- Set up your environment
- Your first governed agent in 5 minutes, if you have not run one yet
- What MAQPNA is
- Agents and sessions