MAQPNADocs

Use a framework template

Start from the LangGraph, OpenAI Agents SDK or Claude Agent SDK template, run it against the scripted model, then validate and deploy it to a cluster.

flowchart LR
    A["pick a template<br/>examples-agents/"] --> B["pip install SDK +<br/>requirements.txt"]
    B --> C["maqpna dev up --stub-llm"]
    C --> D["maqpna dev run --<br/>python agent.py"]
    D --> E["maqpna dev timeline --last"]
    E --> F["docker build +<br/>maqpna validate"]
    F --> G["kubectl apply<br/>Agent, ToolPolicy, AgentSession"]

Goal#

Start a new agent from one of the three ready-made templates in examples-agents/, see it make governed tool and model calls against the scripted stub model on your laptop, and then take the same code to a cluster. Each template uses the matching MAQPNA framework adapter, so a denied call reaches the model as a tool error and a call held for approval uses the framework's own pause.

Template Adapter How MAQPNA decisions reach the framework
examples-agents/langgraph/ maqpna.integrations.langgraph A governed ToolNode. Denied: a tool error message. Held for approval: interrupt({"type": "maqpna_approval", ...}). The run is wrapped in invoke_and_report.
examples-agents/openai-agents/ maqpna.integrations.openai_agents Governed FunctionTools on an OpenAIChatCompletionsModel. Denied: the tool output denied by policy: ... rule=... reason=.... Held: approval pending: id=....
examples-agents/claude-agent-sdk/ maqpna.integrations.claude_agent_sdk In-process MCP servers plus PreToolUse and PostToolUse permission hooks. A denied call is answered deny by the hook on any retry; a held call ask.
LangGraph template under a local MAQPNA.cast

Prerequisites#

  • A checkout of the MAQPNA repository (the templates and the SDK live there).
  • Python 3.12 is what the templates' requirements.txt pins were tested with; 3.10 or later is the SDK minimum.
  • The maqpna CLI and free ports 8080, 8081, 8090 and 8000 (see Your first governed agent).
  • For a cluster deployment: Docker or another image builder, a registry, and an installation with the chart's test MCP server enabled (examples.mcpEcho.enabled=true). See Production install with Helm.

Steps#

1. Install the SDK and the template's dependencies#

From the root of the checkout, in a virtual environment (pick one template):

python3 -m venv .venv && . .venv/bin/activate
pip install ./sdk/python -r examples-agents/langgraph/requirements.txt
# or: -r examples-agents/openai-agents/requirements.txt
# or: -r examples-agents/claude-agent-sdk/requirements.txt

2. Start a local MAQPNA with the scripted model#

maqpna dev up --stub-llm

--stub-llm starts a scripted OpenAI-compatible model as the gateway's model route stub. maqpna dev run then exports OPENAI_BASE_URL (the gateway's /llm/stub/v1), OPENAI_API_KEY (your session token) and MAQPNA_MODEL_NAME=stub, so the templates' model clients go through the gateway too. The default script asks for echo, then for delete_resource in kube-system, then answers in text. No API key and no paid model are involved.

3. Run the template#

maqpna dev run -- python examples-agents/langgraph/agent.py "check the cluster"
maqpna dev: session dev-5f69d80f (agent coder, namespace dev, on behalf of you@localhost)
[langgraph] session=dev-5f69d80f task='check the cluster'
[langgraph] tools: delete_resource, echo, get_time
[langgraph] tool echo -> hello from stub-llm
[langgraph] tool delete_resource -> denied by policy: denied by policy baseline-guardrails rule never-touch-system-namespaces: system namespaces are off-limits to agents rule=never-touch-system-namespaces reason=policy_denied
Done: echoed a greeting; the delete in kube-system was refused by policy.
maqpna dev: dev-5f69d80f: 4 allow, 1 deny, cost $0.0000 (maqpna dev timeline --last)

The other two templates, against the same local MAQPNA:

maqpna dev run -- python examples-agents/openai-agents/agent.py "check the cluster"
[openai-agents] tools: delete_resource, echo, get_time
[openai-agents] tool output -> hello from stub-llm
[openai-agents] tool output -> denied by policy: denied by policy baseline-guardrails rule never-touch-system-namespaces: system namespaces are off-limits to agents rule=never-touch-system-namespaces reason=policy_denied
[openai-agents] reported Succeeded (audit seq 32)
Done: echoed a greeting; the delete in kube-system was refused by policy.
maqpna dev run -- python examples-agents/claude-agent-sdk/agent.py "check the cluster"
[claude-agent-sdk] session=dev-e9031788 mode=offline task='check the cluster'
[claude-agent-sdk] offline mode (no Anthropic model); tools: mcp__echo__delete_resource, mcp__echo__echo, mcp__echo__get_time
[claude-agent-sdk] tool mcp__echo__echo -> hello from stub-llm
[claude-agent-sdk] tool mcp__echo__delete_resource -> denied by policy: denied by policy baseline-guardrails rule never-touch-system-namespaces: system namespaces are off-limits to agents rule=never-touch-system-namespaces reason=policy_denied
[claude-agent-sdk] PreToolUse on a retry of mcp__echo__delete_resource: deny (denied by policy: denied by policy baseline-guardrails rule never-touch-system-n)
[claude-agent-sdk] reported Succeeded
Done: echoed a greeting; the delete in kube-system was refused by policy.

4. Read the timeline#

maqpna dev timeline --last
TIME      KIND        SERVER/TOOL                   DECISION  DETAIL
00:12:32  model_call  llm/stub/chat.completions     allow     scope:models:stub; model:stub; usage:prompt=79,completion=44
00:12:32  tool_call   echo/echo                     allow     default_action
00:12:32  model_call  llm/stub/chat.completions     allow     scope:models:stub; model:stub; usage:prompt=100,completion=50
00:12:32  tool_call   echo/delete_resource          deny      system namespaces are off-limits to agents
00:12:32  model_call  llm/stub/chat.completions     allow     scope:models:stub; model:stub; usage:prompt=163,completion=26
00:12:32  result      maqpna-session/report_result  observe   session_result:Succeeded
dev-5f69d80f: 4 allow, 1 deny, cost $0.0000 (maqpna dev timeline --last)

Model calls are governed calls too: the gateway checks the models:stub scope, meters the tokens and writes an audit record. It does not store the prompt. The last row is the result the template reported at the end of the run.

5. Try an approval#

The templates' environment variable MAQPNA_WAIT_FOR_APPROVALS=true makes a held call wait for the human decision instead of going on without it. Write a stub script that asks for a delete outside a system namespace, which the dev policy holds for approval:

# approve.yaml
model: stub
steps:
  - tool_calls:
      - name: delete_resource
        arguments: {id: "db-1", namespace: "dev"}
  - content: "Deleted db-1 after approval."
maqpna dev down && maqpna dev up --stub-llm --stub-llm-script approve.yaml
MAQPNA_WAIT_FOR_APPROVALS=true maqpna dev run -- python examples-agents/langgraph/agent.py "clean up db-1"

Approve it in a second terminal (eval "$(maqpna dev env)", then maqpna approvals list and maqpna approvals approve ID --approver reviewer@localhost), in MAQPNA Desk or in the console. The template then runs the call and finishes:

[langgraph] session=dev-78b93218 task='clean up db-1'
[langgraph] tools: delete_resource, echo, get_time
[langgraph] PENDING echo.delete_resource approval=apr_8b9196af65cd62eccbe381d5
[langgraph] tool delete_resource -> deleted resource db-1 in namespace dev (simulated)
Deleted db-1 after approval.

6. Validate and test the cluster manifests#

Each template has manifests/ with an Agent, a ToolPolicy and an AgentSession. Check them offline:

maqpna validate -f examples-agents/langgraph/manifests/
SEVERITY  FILE                                           OBJECT                         FIELD      CODE                  MESSAGE
INFO      examples-agents/langgraph/manifests/agent.yaml:2  Agent/maqpna-agents/langgraph  spec.tier  unresolved-reference  TrustTier "tier-0" is not among the validated files (it must exist in the cluster)

3 file(s), 3 object(s): 0 error(s), 0 warning(s)

The INFO line is expected: the trust tier tier-0 (gVisor) is created by the chart, not by the template. Then test the template's policy without a cluster:

maqpna policy test --policy examples-agents/langgraph/manifests/toolpolicy.yaml --ns maqpna-agents \
  --agent langgraph --server echo --tool delete_resource --args '{"namespace":"staging"}' --expect require_approval
{
  "decision": {
    "action": "require_approval",
    "policy": "langgraph-guard",
    "rule": "deletes-need-a-human",
    "reason": "destructive operation"
  },
  ...
}

7. Build and deploy#

docker build -f examples-agents/langgraph/Dockerfile -t registry.example.eu/agents/langgraph-agent:0.1.0 .
docker push registry.example.eu/agents/langgraph-agent:0.1.0

Set spec.image in manifests/agent.yaml to your image (use a digest when your sovereignty policy requires one) and spec.model.endpoint to your OpenAI-compatible model server. Only the gateway talks to that endpoint: the sandbox gets MAQPNA_MODEL_ENDPOINT, the gateway's model route, and uses its session token as the API key. Then apply:

maqpna apply -f examples-agents/langgraph/manifests/
kubectl -n maqpna-agents get agentsession langgraph-demo -o jsonpath='{.status.outcome}'

The images run as UID 65532 and write only to /tmp, so they work under the sandbox's read-only root filesystem.

Verify#

  • The local run printed 4 allow, 1 deny and Done: ....
  • maqpna dev timeline --last ends with session_result:Succeeded.
  • maqpna validate -f .../manifests/ reports 0 error(s).

Troubleshooting#

Symptom Cause Fix
no MAQPNA_TOOL_<NAME>_URL in the environment The agent ran outside maqpna dev run or a sandbox. Run it under maqpna dev run --.
The model call fails with a connection error maqpna dev up ran without --stub-llm, so OPENAI_BASE_URL is not set. Restart with maqpna dev down && maqpna dev up --stub-llm.
Claude Agent SDK template runs live and asks for a key Anthropic credentials are set in your shell and the model is not the stub. Set MAQPNA_CLAUDE_MODE=offline.
The run stops at a held call without waiting MAQPNA_WAIT_FOR_APPROVALS is not true. Set it, or approve and run again: a retry of the same call resumes that approval.
maqpna validate reports registry-not-allowed or digest-required with --sovereignty Your image is not in an allowed registry or has no digest. Push to an allowed registry and pin the digest. See Sovereignty.

Next steps#