Skip to main content

Project Structure

This guide explains what each file in an AgentVisor™ project does and how they work together.

Directory Layout

A typical agent project has this structure:

my-agent/
├── agent.py # Your agent code (required)
├── langgraph.json # Graph configuration (required)
├── requirements.txt # Python dependencies (required)
├── policies/
│ ├── domain.yml # Access control policies (required)
│ └── test.yml # Policy test cases (optional, recommended)
├── mav-agent-config.yaml # Agent-level configuration (optional)
├── .dockerignore # Files to exclude from guest image (optional)
├── .env # Environment variables (optional, never commit)
└── .env.example # Documentation of required variables (optional)

Required Files

agent.py

Your LangGraph agent implementation. Must export a compiled graph:

from langgraph.graph import StateGraph, END
from agentvisor.langgraph import AgentVisorCheckpointer

class AgentState(TypedDict):
messages: Annotated[list, add_messages]

def my_node(state: AgentState) -> dict:
# Your logic here
return {"messages": [...]}

builder = StateGraph(AgentState)
builder.add_node("my_node", my_node)
builder.set_entry_point("my_node")
builder.add_edge("my_node", END)

# This variable name must match langgraph.json
graph = builder.compile(checkpointer=AgentVisorCheckpointer())

Key points:

  • Use AgentVisorCheckpointer() for state persistence across runs
  • The compiled graph variable name must match what's declared in langgraph.json
  • All HTTP requests from your code go through the policy proxy automatically

langgraph.json

For LangGraph projects. Declares available graphs and their entry points:

{
"dependencies": ["."],
"graphs": {
"echo": "./agent.py:graph"
}
}
FieldDescription
dependenciesPython path(s) to add (usually ["."] for current directory)
graphsMap of graph names to file:variable paths

Multiple graphs example:

{
"dependencies": ["."],
"graphs": {
"chat": "./chat_agent.py:graph",
"search": "./search_agent.py:graph",
"summarize": "./summarize_agent.py:graph"
}
}

When you call GET /agents, you'll see all defined graphs.

requirements.txt

Standard Python dependencies file:

langgraph>=0.2.0
langchain-core>=0.3.0
requests>=2.31.0
langchain-ollama>=0.2.0

Notes:

  • Don't include a bare agentvisor — it's pre-installed in the guest image. If you need an optional extra (e.g. agentvisor[tracing]), add that instead; see requirements.txt and the SDK for the full guidance
  • Dependencies are installed when the agent container starts
  • For faster iteration, pre-build images with agentvisor build

policies/domain.yml

MPE PolicyDomain defining access control rules:

apiVersion: iamlite.manetu.io/v1beta1
kind: PolicyDomain
metadata:
name: my-agent
spec:
policies:
# Policy definitions (Rego rules)
- mrn: "mrn:iam:policy:allow"
name: allow
rego: |
package authz
default allow = false
allow = true

resource-groups:
# Groups of resources sharing a policy
- mrn: "mrn:iam:resource-group:allowed"
name: allowed
policy: "mrn:iam:policy:allow"

resources:
# Map MRN patterns to groups
- name: my-api
selector: ["mrn:agentvisor:http:api\\.example\\.com.*"]
group: "mrn:iam:resource-group:allowed"

Policy location:

  • Default: ./policies/domain.yml (relative to agent directory)
  • Override with --policy flag: agentvisor serve . --policy ./custom.yml
  • Multiple files: comma-separated in a single flag: --policy ./base.yml,./custom.yml (the flag is scalar — repeating it makes the last occurrence win, not merge)

See Policy Configuration for detailed policy syntax.

Optional Files

mav-agent-config.yaml

Agent-level configuration for customization beyond what langgraph.json offers. The file uses a namespaced structure where each top-level key represents a configuration domain.

Currently supported domains:

  • a2a: — A2A transport configuration (agent card, skills metadata)
  • framework: — Agent framework provider configuration
  • mcp: — MCP tool provider configuration
  • mounts: — Host filesystem mounts into the sandbox

Framework Configuration

The framework: section allows running non-LangGraph agents or customizing how agents are launched:

# mav-agent-config.yaml
framework:
# Provider name (required whenever the framework section is present)
provider: "langgraph"

# Command to execute (optional — overrides the provider's default command)
# First element is executable, remaining are arguments
# Uses array format - no shell expansion for security
command: ["python3", "main.py", "--config", "config.yaml"]

# Additional environment variables (optional)
env:
MY_CUSTOM_VAR: "value"
LOG_LEVEL: "debug"

Default behavior: When framework: is absent and langgraph.json exists, AgentVisor defaults to python3 -m agentvisor.langgraph.runner. Existing LangGraph agents continue to work without changes.

Environment variable precedence (later steps override earlier):

  1. Base environment — allowlist from the host environment when env curation is enabled, full host environment otherwise
  2. environment_passthrough — host env vars explicitly forwarded to the guest at startup (see Environment Configuration)
  3. framework.env from mav-agent-config.yaml
  4. .env file vars and credential tokens
  5. AgentVisor-injected variables (HTTP_PROXY, AGENTVISOR_*, SSL_CERT_FILE, etc.) — always wins

To install packages or tools into the sandbox image itself (beyond env vars), see Customizing the Sandbox Environment.

Examples by runtime type:

RuntimeCommand Example
Python (custom)["python3", "agent.py"]
Go binary["./myagent", "--config", "/app/config.yaml"]
Node.js["node", "dist/agent.js"]
Shell script["bash", "run.sh"]
Interactive (exec)["bash"] or ["claude", "--dangerously-skip-permissions"]

Debug mode: When --debug is enabled with a custom runtime, debug mode is ignored with a warning logged (debug providers are Python-specific).

A2A Configuration

Example for enriching the A2A Agent Card when using A2A Transport:

# mav-agent-config.yaml
a2a:
agent_card:
name: "My Research Assistant"
description: "An AI-powered research assistant"
provider:
organization: "Acme Corp"
url: "https://acme.com"

skills:
research_agent: # Must match graph name in langgraph.json
tags: ["research", "academic", "citations"]
examples:
- "Find papers about quantum computing"
- "Summarize recent AI safety research"

Key points:

  • All configuration is namespaced under a2a:, framework:, or other domain keys
  • Skill keys must match graph names in langgraph.json
  • All fields are optional — use only what you need
  • Unknown top-level keys produce warnings but don't cause errors

See Enriching A2A Agent Cards for the full A2A configuration reference.

policies/test.yml

Test cases for validating your policies before running agents. Use with mpe test decisions:

- name: "Allow httpbin.org requests"
porc:
principal:
sub: anonymous
mroles: ["mrn:agentvisor:role:anonymous"]
operation: "agentvisor:http:request"
resource: "mrn:agentvisor:http:httpbin.org/get"
context:
http:
method: GET
host: httpbin.org
expected: GRANT

- name: "Deny example.com requests"
porc:
principal:
sub: anonymous
mroles: ["mrn:agentvisor:role:anonymous"]
operation: "agentvisor:http:request"
resource: "mrn:agentvisor:http:example.com/"
context:
http:
method: GET
host: example.com
expected: DENY

- name: "Allow thread creation"
porc:
principal:
sub: anonymous
mroles: ["mrn:agentvisor:role:anonymous"]
operation: "agentvisor:thread:create"
resource:
id: "mrn:agentvisor:thread:test-123"
group: "mrn:agentvisor:resourcegroup:threads"
expected: GRANT

Run tests with:

mpe test decisions -d ./policies/domain.yml -i ./policies/test.yml

Why test policies separately?

  • Faster feedback loop than running full agent
  • Easier to debug - see exactly which phase denied
  • Catch policy errors before they cause confusing agent failures
  • Document expected behavior for your team

.env

Environment variables for your agent:

# API keys
OPENAI_API_KEY=sk-...
OLLAMA_BASE_URL=http://host.docker.internal:11434

# Configuration
LOG_LEVEL=debug
MAX_RETRIES=3

Important:

  • Auto-discovered if present in agent directory
  • Override with --env-files flag (supports comma-separated paths)
  • Never commit to git - add to .gitignore
  • Variables available via os.environ in your agent code

.env.example

Document required variables for your team (safe to commit):

# .env.example - Copy to .env and fill in values

# Required: LLM API key
OPENAI_API_KEY=your-key-here

# Optional: Custom Ollama endpoint
OLLAMA_BASE_URL=http://localhost:11434

.dockerignore

Exclude files from the guest image that aren't needed at runtime — such as documentation, policy files, and test fixtures:

# Host-side policy files (loaded by host runtime, not needed in guest)
policies/

# Documentation
README.md

This keeps the guest image lean and avoids packaging unnecessary files into the sandbox. Most of the Docker .dockerignore syntax is supported, including * wildcards and ** recursive globs — with one exception: negation patterns (lines starting with !) are not supported. A !pattern line is ignored (with a warning logged), not honored as a re-inclusion rule.

Common files already excluded by default include .git/, __pycache__/, .venv/, .env, and agentvisor.yaml. See Container Layout — Ignore Patterns for the full reference.

What Gets Built Into Images

When you run agentvisor build, here's what happens:

FilesBuilt IntoNotes
agent.py, *.pyGuest imageYour code
langgraph.jsonGuest imageGraph configuration
requirements.txtGuest imageDependencies installed
policies/domain.ymlHost runtimeLoaded at startup
.envExcludedNever packaged (security)
.dockerignoreExcludedControls what gets packaged

The separation matters:

  • Guest image: Your agent code runs here, in an isolated sandbox
  • Host runtime: Manages policy enforcement, Temporal workflows, HTTP API

Runtime vs Build-Time

ConcernWhen ResolvedHow to Change
Python codeBuild timeRebuild image
DependenciesBuild timeRebuild image
Policy rulesStartup timeRestart with new --policy
Environment varsStartup timeRestart with new .env

For development, use agentvisor serve which doesn't require rebuilding.

File Discovery

AgentVisor auto-discovers files in this order:

  1. langgraph.json - Must exist in the agent directory (not required when using agentvisor exec or a custom framework.command)
  2. Policy file - Checks the --policy flag first; falls back to ./policies/domain.yml only if --policy wasn't given
  3. .env file - Checks the --env-files flag first; falls back to .env in the agent directory only if --env-files wasn't given
  4. mav-agent-config.yaml - Checks ./mav-agent-config.yaml, then ./mav-agent-config.yml
  5. Interpreter - Auto-detects Python version or uses --interpreter flag

Next Steps