This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
AgentField is a Kubernetes-style control plane for AI agents. It provides production infrastructure for deploying, orchestrating, and observing multi-agent systems with cryptographic identity and audit trails.
Architecture: Three-tier monorepo
- Control Plane (Go): Orchestration server providing REST/gRPC APIs, workflow execution, observability, and cryptographic identity
- SDKs (Python & Go): Libraries for building agents that communicate with the control plane
- Web UI (React/TypeScript): Embedded admin interface for monitoring workflows and managing agents
- Go 1.23+
- Python 3.8+
- Node.js 20+
- PostgreSQL 15+ (for PostgreSQL storage mode)
# Install all dependencies
make install
# Or install components individually:
./scripts/install.sh
# Build everything
make buildLocal mode (uses SQLite + BoltDB, no external dependencies):
cd control-plane
go run ./cmd/af dev
# Or: go run ./cmd/agentfield-serverPostgreSQL storage mode:
# Run migrations first
cd control-plane
export AGENTFIELD_DATABASE_URL="postgres://agentfield:agentfield@localhost:5432/agentfield?sslmode=disable"
goose -dir ./migrations postgres "$AGENTFIELD_DATABASE_URL" up
# Start server
AGENTFIELD_STORAGE_MODE=postgres \
AGENTFIELD_DATABASE_URL="postgres://agentfield:agentfield@localhost:5432/agentfield?sslmode=disable" \
go run ./cmd/agentfield-serverDocker Compose (includes PostgreSQL):
cd deployments/docker
docker compose upControl plane runs at http://localhost:8080
Web UI accessible at http://localhost:8080/ui/
make build # Build all components
make control-plane # Build control plane only
make sdk-go # Build Go SDK
make sdk-python # Build Python SDKmake test # Run all tests
# Component-specific tests:
cd control-plane && go test ./...
cd sdk/go && go test ./...
cd sdk/python && pytest
# Python tests with coverage:
cd sdk/python && pytest --cov=agentfield --cov-report=term-missing
# Web UI linting:
cd control-plane/web/client && npm run lintmake lint # Lint all code
make fmt # Format all code
make tidy # Tidy Go modules
# Component-specific:
cd control-plane && golangci-lint run
cd sdk/python && ruff check
cd sdk/python && ruff format .cd control-plane
export AGENTFIELD_DATABASE_URL="postgres://agentfield:agentfield@localhost:5432/agentfield?sslmode=disable"
# Check migration status
goose -dir ./migrations postgres "$AGENTFIELD_DATABASE_URL" status
# Apply all pending migrations
goose -dir ./migrations postgres "$AGENTFIELD_DATABASE_URL" up
# Create new migration
goose -dir ./migrations create <migration_name> sqlcd control-plane/web/client
npm install
npm run dev # Runs on http://localhost:5173
# In parallel, run the control plane server to handle API calls
cd control-plane
go run ./cmd/agentfield-serverThe UI dev server proxies API requests to the control plane. In production, the UI is embedded via Go's embed package.
Entry Points:
cmd/agentfield/- Unified CLI with server + dev/init commandscmd/agentfield-server/- Standalone server binary
Core Packages (internal/):
cli/- CLI command definitions and routingserver/- HTTP server setup (Gin framework), middleware, routinghandlers/- HTTP request handlers for REST/gRPC endpointsservices/- Business logic layer (workflow execution, agent registry, DID/VC generation)storage/- Data persistence layer with multiple backends (local SQLite/BoltDB, PostgreSQL, cloud)events/- Event bus for workflow notifications and SSE streamingcore/- Domain models and interfacesapplication/- Application service orchestrationinfrastructure/- Infrastructure utilities (database connection pooling, etc.)mcp/- Model Context Protocol integrationlogger/- Structured logging (zerolog)config/- Configuration management (Viper)templates/- Code generation templates foraf initutils/- Shared utilitiesencryption/- Cryptographic primitives for DID/VCpackages/- Shared internal packagesembedded/- Embedded assets (web UI dist)
Configuration:
- Environment variables take precedence over
config/agentfield.yaml - See
control-plane/.env.examplefor all options - Storage modes:
AGENTFIELD_STORAGE_MODE=local(SQLite/BoltDB) orAGENTFIELD_STORAGE_MODE=postgres(PostgreSQL)
Database Schema:
migrations/- SQL migrations managed by Goose- Always run migrations before starting the server in PostgreSQL mode
Python SDK (sdk/python/agentfield/):
- Built on FastAPI/Uvicorn for agent HTTP servers
- Key modules:
Agent,agent_field_handler,client,execution_context,memory,ai - Agents register "reasoners" (decorated functions) that become REST endpoints
- Test with:
pytest(seepyproject.tomlfor test markers: unit, functional, integration) - Install locally:
pip install -e .[dev]
Go SDK (sdk/go/):
- Modules:
agent/(agent builder),client/(HTTP client),types/(shared types),ai/(LLM helpers) - Agents register "skills" (functions) similar to Python SDK
- Test with:
go test ./...
- React + TypeScript + Vite
- Tailwind CSS + Radix UI components
- Build:
npm run build→ outputs todist/→ embedded in Go binary - Dev mode:
npm run dev(separate Vite server)
# Generate agent scaffold (run from repo root or any directory)
af init my-agent
cd my-agent
# Edit agent code (auto-generated template)
# Run agent locally (connects to control plane at AGENTFIELD_SERVER env var or --server flag)
af runimport agentfieldagent "github.com/Agent-Field/agentfield/sdk/go/agent"
agent, err := agentfieldagent.New(agentfieldagent.Config{
NodeID: "my-agent",
Version: "1.0.0", // required — New returns an error without it
AgentFieldURL: "http://localhost:8080",
})
if err != nil {
log.Fatal(err)
}
agent.RegisterSkill("greet", func(ctx context.Context, input map[string]any) (any, error) {
return map[string]any{"message": "hello"}, nil
})
agent.Run(context.Background())- Define handler in
control-plane/internal/handlers/<domain>/ - Add route in
control-plane/internal/server/routes.go - Add business logic in
control-plane/internal/services/<domain>/ - Add storage methods in
control-plane/internal/storage/<domain>/ - If adding new DB tables, create migration:
goose -dir ./migrations create <name> sql
- Local mode: SQLite (relational) + BoltDB (key-value). No external dependencies. Good for dev/testing.
- PostgreSQL mode: Full PostgreSQL backend. Requires running migrations. Production-ready.
Storage interface is unified—services call storage layer methods, storage layer switches backends based on config.
Control Plane:
- Unit tests:
go test ./...(mock storage/services) - Integration tests: Spin up test database, run migrations, test full stack
Python SDK:
- Markers:
@pytest.mark.unit,@pytest.mark.functional,@pytest.mark.integration,@pytest.mark.mcp - Default:
pytestruns all except MCP tests (use-m mcpto include) - Coverage tracked for core modules (see
pyproject.toml)
Go SDK:
- Standard
go test ./... - Table-driven tests preferred
- Control plane: Return structured JSON errors with HTTP status codes
- SDKs: Raise/return typed exceptions/errors with context
- Log errors before returning (use zerolog in Go, standard logging in Python)
- Environment variables (highest priority)
- Config file (
config/agentfield.yamlorAGENTFIELD_CONFIG_FILEpath) - Defaults in code
- Every installable node has this manifest at its repo root. It's parsed by
packages.ParsePackageMetadata(control-plane/internal/packages/installer.go) — the single loader used byaf install/af runand the package/agent services. Full authoring guide: docs/installing-agent-nodes.md; a real example is Agent-Field/SWE-AF'sagentfield-package.yaml. config_versiondeclares the manifest schema version (e.g.v1) — distinct fromversion:, which is the node's own release. Absent meansv0(legacy, pre-versioning).CurrentConfigVersionininstaller.gois the highest version the binary reads; a manifest declaring a higher version is refused with an upgrade hint rather than mis-parsed. New manifests should setconfig_version: v1.- When to bump
config_version: only for breaking format changes (a field renamed/removed, or its shape/meaning changed). Adding a new optional field is additive —yaml.Unmarshalignores unknown keys and new readers fall back to defaults, so it does not need a bump. If you do change the format in a breaking way: bumpCurrentConfigVersion, add acasein theParsePackageMetadataversion switch, and record the change in the version table indocs/installing-agent-nodes.md. - Golden-fixture tests are the spec:
control-plane/internal/packages/config_version_fixtures_test.goholds one canonical manifest + structure assertion per version, with a coverage guard that fails CI if a version has no fixture. Maintenance rule (also documented in that file): grow the current version's fixture for additive fields; add a net-new fixture for a net-new version and never rewrite old fixtures into the new shape — they guard backwards compatibility.
- Agents call each other via control plane:
await agent.call("other-agent.function", input={...}) - Control plane routes requests, tracks workflow DAG, injects metrics
- Never direct agent-to-agent HTTP—always through control plane
The control plane defines four scopes: global, session, actor, and workflow. The HTTP handler does not alias agent or run to those scopes. Python's MemoryInterface rejects any name outside _VALID_SCOPES = ("global", "session", "actor", "workflow"). The Go SDK remaps unknown names (including agent and run) to global before they hit the wire; its constant for the actor scope is agent.ScopeUser ("user"), which apiScope translates to actor (sdk/go/agent/control_plane_memory_backend.go). Do not use agent or run as scope names.
global: Shared by every agent and session; fixed scope idglobalsession: One conversation, keyed byX-Session-IDactor: One actor across all its sessions, keyed byX-Actor-IDworkflow: One workflow run, keyed byX-Workflow-ID
session, actor and workflow are sibling dimensions, not a nested chain. A read with no explicit scope resolves workflow -> session -> actor -> global and returns the first hit. Scope controls lookup and isolation, not lifetime: nothing is purged when a session or run ends — values persist until explicitly deleted.
Agents access via SDK methods: agent.memory.get(key, default=None, scope=..., scope_id=...) / agent.memory.set(key, data, scope=..., scope_id=...). See sdk/python/agentfield/memory.py for the authoritative description.
- Opt-in per agent: Set
app.vc_generator.set_enabled(True)in Python or equivalent in Go - Control plane generates W3C Verifiable Credentials for each execution
- Export audit trails:
GET /api/v1/did/workflow/{workflow_id}/vc-chain - Verify offline:
af verify audit.json
Control Plane (Go):
- Use
github.com/Agent-Field/agentfield/control-planeas module path - Internal packages:
github.com/Agent-Field/agentfield/control-plane/internal/<package>
SDKs:
- Python:
agentfield(PyPI package) - Go:
github.com/Agent-Field/agentfield/sdk/go(import path)
Releases are automated via .github/workflows/release.yml and .goreleaser.yml:
- Tag a commit:
git tag v0.1.0 && git push origin v0.1.0 - GitHub Actions builds binaries for multiple platforms
control-plane/build-single-binary.shcreates unified binary (embeds web UI)
- Control plane not starting: Check
AGENTFIELD_DATABASE_URLis set correctly (PostgreSQL mode) or ensure SQLite file path is writable (local mode) - Migrations failing: Ensure PostgreSQL is running and connection string is correct. Check migration status with
goose status - Agent can't connect: Verify
AGENTFIELD_SERVERenv var points to control plane (default:http://localhost:8080) - UI not loading: In dev, ensure both Vite dev server (
npm run dev) and control plane server are running. In prod, ensuremake buildwas run to embed UI in binary - Agent execution stuck: Check workflow DAG in UI (
/ui/workflows) for errors. Check agent logs for exceptions. - Database connection pool exhausted: Increase
AGENTFIELD_STORAGE_POSTGRES_MAX_CONNECTIONSin config
See control-plane/.env.example for comprehensive list. Key vars:
AGENTFIELD_PORT- HTTP server port (default: 8080)AGENTFIELD_STORAGE_MODE-localorpostgresAGENTFIELD_DATABASE_URL- PostgreSQL connection stringAGENTFIELD_UI_ENABLED- Enable/disable web UIAGENTFIELD_UI_MODE-embedded(production) ordevelopment(Vite proxy)AGENTFIELD_CONFIG_FILE- Path to config YAMLGIN_MODE-debugorreleaseAGENTFIELD_LOG_LEVEL-debug,info,warn,error(default:info;af --verboseoverrides it)AGENTFIELD_LOG_REDACT_PAYLOADS-true(default) keeps execution payloads and agent response bodies out of logs
Go:
- Use
gofmtfor formatting (enforced bymake fmt) - Follow Effective Go conventions
- Use zerolog for structured logging:
logger.Logger.Info().Msg("message")
Python:
- Use Ruff for linting and formatting (
make fmtrunsruff format) - Type hints required for public APIs
- Async/await for I/O operations
- Follow PEP 8
TypeScript/React:
- Use ESLint config in
control-plane/web/client/.eslintrc.json - Functional components with hooks
- Tailwind for styling (no CSS-in-JS)