Skip to content

Operations

Two binaries ship: agents-server and agents-migrator. Both bind the same Config struct from environment variables via sdk/config.MustBind.

VariableDescription
DATABASE_URLPgBouncer connection string, port 6432, transaction-pool mode
IDENTITY_GRPC_ADDRgRPC address of the identity service, e.g. identity.paper-board.svc.cluster.local:50051
ANTHROPIC_API_KEYAnthropic API key; omit to fall back to the deterministic dev mock
VariableDescription
MIGRATION_DB_URLDirect Postgres connection string, port 5432 (advisory lock requires session pool)

Do not swap these two URLs. PgBouncer breaks advisory locks; direct Postgres loses prepared statements at server scale.

VariableDefaultDescription
SERVER_PORT8080HTTP listen port
READ_HEADER_TIMEOUT5sGo http.Server.ReadHeaderTimeout
SHUTDOWN_TIMEOUT30sGraceful shutdown deadline
REQUEST_TIMEOUT30sTimeout for non-SSE routes
SSE_TIMEOUT300sTimeout for streaming routes only
BODY_LIMIT_BYTES1048576Max request body size (1 MiB)
DB_MAX_CONNS20PgBouncer pool max connections
DB_MIN_CONNS2PgBouncer pool min connections
DB_MAX_CONN_LIFETIME30mPgBouncer connection max lifetime
COMPUTE_GRPC_ADDRgRPC address of the compute service; empty disables tool dispatch
LOG_LEVELinfodebug | info | warn | error
OTEL_EXPORTER_OTLP_ENDPOINTOTLP endpoint for traces; empty disables
ENVdevdev | staging | prod

ANTHROPIC_API_KEY is injected from a Kubernetes Secret (anthropicKeySecret in Helm values). Non-secret tunables are surfaced via a ConfigMap (server.config in Helm values).

Terminal window
# Apply all pending migrations (Helm pre-install/pre-upgrade hook runs this automatically)
agents-migrator up
# Dry-run: print SQL without executing
agents-migrator up --dry-run
# Roll back N migrations
agents-migrator down 1
# Force schema version (use after a partial failure)
agents-migrator force 2
# Print current version
agents-migrator version
# Drop schema (dev only — requires MIGRATOR_ENV=dev)
MIGRATOR_ENV=dev agents-migrator drop

The migrator holds Postgres advisory lock id 3 (agents) during up and down so parallel runs from two Helm jobs cannot race. Lock id 3 is cluster-wide; do not reuse it in another service.

// from paper-board/agents/cmd/migrator/main.go:23-29
mc := migrator.Config{
DBURL: cfg.MigrationDatabaseURL,
Schema: "agents",
EmbedFS: migrations.SchemaFS,
EmbedRoot: "schema",
}
if err := migrator.Run(context.Background(), mc, os.Args[1:]); err != nil {

Chart: oci://ghcr.io/paper-board/helm/agents (current version 0.2.3).

# from paper-board/agents/helm/agents/values.yaml:2-18
migrator:
enabled: true
image:
repository: ghcr.io/paper-board/agents-migrator
tag: v0.2.3
pullPolicy: IfNotPresent
resources:
requests: { cpu: 100m, memory: 128Mi }
limits: { cpu: 200m, memory: 256Mi }
migrationDbUrlSecret:
name: agents-migration-db-url
key: url

The migrator Job runs as a Helm pre-install,pre-upgrade hook with backoffLimit: 0. A failed migration blocks the release.

# from paper-board/agents/helm/agents/values.yaml:20-54
server:
enabled: true
replicas: 1
image:
repository: ghcr.io/paper-board/agents-server
tag: v0.2.3
pullPolicy: IfNotPresent
service:
type: ClusterIP
port: 8080
databaseUrlSecret:
name: agents-app-db-url
key: url
anthropicKeySecret:
name: anthropic-api-key
key: key
config:
serverPort: 8080
sseTimeout: "300s"
logLevel: "info"
# from paper-board/agents/helm/agents/values.yaml:56-74
networkPolicy:
enabled: true
egress:
pgbouncer:
enabled: true
port: 6432
identity:
enabled: true
grpcPort: 50051

Egress is allowlisted to PgBouncer (6432) and identity gRPC (50051) only. Ingress from gateway is added in Phase 7.

flowchart TB
subgraph cluster["Kubernetes cluster"]
subgraph ns["paper-board namespace"]
migrator["agents-migrator\nJob (pre-upgrade hook)"]
server["agents-server\nDeployment"]
cm["server-configmap\nConfigMap"]
svc["agents\nService :8080"]
end
subgraph data["data plane"]
pgb[("PgBouncer\n:6432")]
pg[("Postgres\nagents schema\n:5432")]
end
subgraph identity_ns["identity namespace"]
idgrpc["identity-server\ngRPC :50051"]
end
end
ext{{"Anthropic API"}}
migrator -->|"MIGRATION_DB_URL"| pg
server -->|"DATABASE_URL"| pgb
pgb --> pg
server -->|"IDENTITY_GRPC_ADDR"| idgrpc
server -->|"ANTHROPIC_API_KEY"| ext
cm --> server
svc --> server
classDef controlPlane fill:#10b981,stroke:#047857,color:#fff
classDef dataPlane fill:#3b82f6,stroke:#1d4ed8,color:#fff
classDef sandbox fill:#f97316,stroke:#c2410c,color:#fff
classDef external fill:#ef4444,stroke:#b91c1c,color:#fff
classDef persistence fill:#6b7280,stroke:#374151,color:#fff
class migrator,idgrpc controlPlane
class server,svc,cm dataPlane
class pgb,pg persistence
class ext external

CI reuses the shared paper-board/.github/.github/workflows/go-ci.yml reusable workflow. The local wrapper is .github/workflows/ci.yml.

On a semver tag (v*), the release workflow builds agents-server and agents-migrator images, pushes them to ghcr.io/paper-board/agents-{server,migrator}:<tag>, then packages and pushes the Helm chart as an OCI artifact to ghcr.io/paper-board/helm/agents.

The chart version in helm/agents/Chart.yaml must match the release tag. The release workflow enforces this with a version-match step before helm push.