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"
}
}
| Field | Description |
|---|---|
dependencies | Python path(s) to add (usually ["."] for current directory) |
graphs | Map 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
--policyflag: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 configurationmcp:— MCP tool provider configurationmounts:— 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):
- Base environment — allowlist from the host environment when env curation is enabled, full host environment otherwise
environment_passthrough— host env vars explicitly forwarded to the guest at startup (see Environment Configuration)framework.envfrom mav-agent-config.yaml.envfile vars and credential tokens- 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:
| Runtime | Command 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-filesflag (supports comma-separated paths) - Never commit to git - add to
.gitignore - Variables available via
os.environin 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:
| Files | Built Into | Notes |
|---|---|---|
agent.py, *.py | Guest image | Your code |
langgraph.json | Guest image | Graph configuration |
requirements.txt | Guest image | Dependencies installed |
policies/domain.yml | Host runtime | Loaded at startup |
.env | Excluded | Never packaged (security) |
.dockerignore | Excluded | Controls 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
| Concern | When Resolved | How to Change |
|---|---|---|
| Python code | Build time | Rebuild image |
| Dependencies | Build time | Rebuild image |
| Policy rules | Startup time | Restart with new --policy |
| Environment vars | Startup time | Restart with new .env |
For development, use agentvisor serve which doesn't require rebuilding.
File Discovery
AgentVisor auto-discovers files in this order:
- langgraph.json - Must exist in the agent directory (not required when using
agentvisor execor a customframework.command) - Policy file - Checks the
--policyflag first; falls back to./policies/domain.ymlonly if--policywasn't given - .env file - Checks the
--env-filesflag first; falls back to.envin the agent directory only if--env-fileswasn't given - mav-agent-config.yaml - Checks
./mav-agent-config.yaml, then./mav-agent-config.yml - Interpreter - Auto-detects Python version or uses
--interpreterflag
Next Steps
- Deploying Your Agent: Dev to production journey
- Environment Configuration: Deep dive on
.envfiles - Policy Configuration: Advanced policy patterns