Skip to main content
Run fracta with a local Kubernetes cluster for development and testing. kind is the recommended default — it’s reproducible, cluster-agnostic, and matches what CI uses. Docker Desktop, minikube, and k3d also work; the Makefile’s image-loading helpers handle each. Agents spawn as K8s Jobs, the gateway proxies MCP tools, and state lives in Postgres.

Prerequisites

  • A local Kubernetes cluster. kind is the recommended default (kind create cluster --name fracta). Docker Desktop Kubernetes, minikube, and k3d are also supported for image loading.
  • kubectl, make, op (1Password CLI) on PATH
  • psql for event queries (optional)
Verify:
kubectl cluster-info
kubectl get nodes

Architecture

Host (your machine)
  └─ fracta serve         (reads ./fracta.yaml from your project root)
       └─ thin client (HTTP) ──▶ fracta-controlplane Service :9090 in-cluster
            (transport: kubectl port-forward / LoadBalancer / Ingress, depending on cluster)

K8s Cluster (fracta namespace)
  ├─ fracta-controlplane Deployment  ← lifecycle authority, workers, K8s agent spawner
  ├─ fracta-gateway      Deployment  ← HTTP MCP endpoint for agent pods
  ├─ postgres            StatefulSet ← shared state (agents, events, missions)
  ├─ falkordb            StatefulSet ← knowledge graph
  ├─ fracta-agent-*      Jobs        ← ephemeral batch agent pods (Claude/Codex/OpenCode)
  └─ fracta-stream-*     Pods        ← persistent stream agent pods (Codex/OpenCode)
Agent pods connect to the gateway via HTTP MCP. The gateway proxies fracta’s own agent/graph/strategy tools, plus any MCP backend services you add to deployment/k8s/manifests/.

Quick Start

1. Initialize fracta in your project

From the root of any git repository:
fracta init --scaffold k8s
This drops fracta.yaml and deployment/k8s/manifests/ (namespace, RBAC, postgres, falkordb, controlplane, gateway, auth-helpers ConfigMap stub, agent job template).

2. Apply the manifests

kubectl apply -f deployment/k8s/manifests/

3. Verify pods are running

kubectl get pods -n fracta
All pods should show 1/1 Running:
  • postgres-0
  • falkordb-0
  • fracta-controlplane-*
  • fracta-gateway-*

4. Reach the control plane Service from your host

The control plane is the fracta-controlplane Service on port 9090 inside the cluster. For local dev clusters:
kubectl port-forward -n fracta svc/fracta-controlplane 9090:9090
Runs in the foreground. Opens localhost:9090 → fracta-controlplane Service. Port-forward is the canonical path on kind: kind’s LoadBalancer Services stay <pending> because there’s no cloud provisioner. For Docker Desktop, a LoadBalancer Service may publish directly without port-forward; for non-dev clusters, expose via an Ingress. In all cases, update control_plane_api.url in fracta.yaml to match. The rest of the flow is identical.

5. Connect via MCP (golden path)

The scaffolded fracta.yaml is the host-side thin-client config. Configure your AI CLI to run fracta serve from your project root:
// .mcp.json
{ "mcpServers": { "fracta": { "command": "fracta", "args": ["serve"] } } }
Once the host can reach the control plane Service:
# In Claude Code, reconnect MCP:
/mcp
Or run directly:
fracta serve

Configuration

Two config files

FilePurposeUsed by
fracta.yaml (project root)Host-side thin-client config pointing at control plane APIfracta serve on your machine
deployment/k8s/manifests/fracta-controlplane.yamlIn-cluster controlplane config (ConfigMap)Controlplane pod
deployment/k8s/manifests/fracta-gateway.yamlIn-cluster gateway config (ConfigMap)Gateway pod

Key differences

SettingHost-side (fracta.yaml)In-cluster (controlplane / gateway ConfigMaps)
Control plane APIhttp://localhost:9090Pod-local service access
State/queueNot configured in thin clientPostgres in cluster
FalkorDBNot configured in thin clientfalkordb.fracta.svc:6379
Runtime backendkubernetes (with extra_volumes)kubernetes

MCP backend transports

Each MCP backend in the gateway config needs an explicit transport. For example, if you’ve added an Elasticsearch MCP container as a Service in the cluster:
mcp_servers:
  servers:
    my-elastic:
      remote:
        url: http://my-elastic-mcp.fracta.svc:3000/mcp
        transport: streamable-http
    my-other-backend:
      remote:
        url: http://my-other.fracta.svc:3000/sse
        transport: sse
Supported transports: streamable-http (default if omitted), sse.

Secrets

You’ll need at minimum a postgres secret. Create it manually:
kubectl create secret generic postgres-secrets -n fracta \
  --from-literal=password="$(openssl rand -base64 24)"
For agent auth credentials, populate the fracta-auth-helpers ConfigMap from deployment/auth-helpers/ — see the auth helpers section of the K8s configuration docs. For MCP backend services you add (Elasticsearch, internal services, etc.), create their secrets the same way and reference them via secretKeyRef in the corresponding Deployment manifest.

How Agent Pods Work

When you spawn an agent (via fracta_spawn or fracta spawn), fracta prepares a per-agent workspace and the K8s runtime runs it:
  1. The in-cluster control plane worker resolves the selected runtime (claude, codex, or opencode) and writes runtime-specific workspace files into the configured staging directory.
  2. The K8s backend packages those files into a ConfigMap and creates either a batch Job (fracta-agent-<task>) or, for stream mode, a persistent Pod (fracta-stream-<task>).
  3. A workspace-init init container copies the ConfigMap files into the runtime workdir, normally /workspace/agents/<task>.
  4. An optional auth Secret provides the host-seeded bearer token for the agent runtime.
  5. The main container runs entrypoint.sh, starts the strategy sidecar, and execs the selected runtime command.
  6. The runtime reads its workspace config and connects to the fracta gateway via the agent-scoped HTTP MCP endpoint.
  7. The agent discovers fracta, elastic, and vendor tools through the gateway. On completion, fracta records output and events.
The runtime-specific files currently injected into K8s agent workspaces are:
RuntimeWorkspace files
Claude.mcp.json, .claude/settings.json, .fracta/user-settings.json, CLAUDE.md
Codex.codex/config.toml, AGENTS.md
OpenCodeopencode.json, AGENTS.md

Runtime MCP Config Formats

Claude uses .mcp.json:
{
  "mcpServers": {
    "fracta": {
      "type": "http",
      "url": "http://fracta-gateway.fracta.svc:8080/agents/<task>/mcp"
    }
  }
}
The "type": "http" field is required — Claude CLI uses it to select HTTP MCP transport. Codex uses .codex/config.toml:
[mcp_servers.fracta]
url = "http://fracta-gateway.fracta.svc:8080/agents/<task>/mcp"
bearer_token_env_var = "FRACTA_GATEWAY_TOKEN"
OpenCode uses opencode.json:
{
  "mcp": {
    "fracta": {
      "type": "remote",
      "url": "http://fracta-gateway.fracta.svc:8080/agents/<task>/mcp",
      "headers": {
        "Authorization": "Bearer {env:FRACTA_GATEWAY_TOKEN}"
      }
    }
  },
  "permission": {
    "task": "deny"
  }
}
The exact permission payload differs by runtime. See docs/runtime-configuration.md for the full multi-runtime configuration details.

Images

The scaffolded manifests reference ghcr.io/darkquasar/fracta:latest (the published fracta image) with imagePullPolicy: IfNotPresent. For local clusters that can pull from public registries, no extra setup is needed. For air-gapped clusters or fracta-development workflows where you’ve built a local image, load the image into your cluster runtime and edit the image: and imagePullPolicy: fields in deployment/k8s/manifests/fracta-controlplane.yaml and fracta-gateway.yaml accordingly:
# Examples — pick the one matching your cluster runtime.
kind load docker-image ghcr.io/darkquasar/fracta:dev --name <cluster-name>
minikube image load ghcr.io/darkquasar/fracta:dev --profile <profile>
k3d image import  ghcr.io/darkquasar/fracta:dev --cluster <cluster-name>
Then update image: and set imagePullPolicy: Never on the relevant Deployments. After image changes, restart the Deployments:
kubectl rollout restart deployment/fracta-controlplane -n fracta
kubectl rollout restart deployment/fracta-gateway -n fracta

Observability

Events in Postgres

kubectl exec -n  fracta postgres-0 -- psql -U  fracta -d  fracta -c \
  "SELECT component, action, outcome, task, detail FROM agent_events ORDER BY timestamp DESC LIMIT 20;"

Kubernetes Events

kubectl get events -n  fracta --sort-by='.lastTimestamp' | grep fracta

Gateway logs

kubectl logs deployment/fracta-gateway -n  fracta --tail=20

Agent pod logs (while pod exists)

# Batch agents
kubectl logs job/fracta-agent-<task> -n fracta

# Stream agents
kubectl logs pod/fracta-stream-<task> -n fracta

Sidecar logs (inside agent pod)

kubectl exec <pod> -n  fracta -- cat /var/log/fracta-strategy.log

Troubleshooting

SymptomCauseFix
ErrImagePull on locally-built podsImage not loaded into the local cluster runtimeLoad the image via your cluster’s loader (kind load docker-image, minikube image load, k3d image import) and set imagePullPolicy: Never on the relevant manifests
ErrImagePull on a public-image podCluster cannot reach the registryCheck local cluster network and registry access; for air-gapped clusters, mirror the image
no MCP transport configuredMCP server entry is missing both remote.url and local.commandAdd a remote entry to the gateway ConfigMap
MCP tools not discovered in agentRuntime workspace config missing gateway endpoint or auth fieldsCheck generated .mcp.json, .codex/config.toml, or opencode.json
OpenCode MCP tool call is auto-rejectedConcrete OpenCode MCP permission key missing, for example fracta_listEnsure opencode.json expands fracta tool permissions
unexpected status code: 404 on MCP backendWrong SSE/HTTP endpoint pathCheck transport + URL path (/sse vs /mcp)
timeout waiting for endpointSSE endpoint returns EOFBackend may need /sse path explicitly
Agent output parse errorSidecar stdout contaminationCheck entrypoint.sh redirects to log file
config_skew eventHost and worker config differExpected if configs diverge; informational
Agents table empty after completionReaper cleans terminal queued agentsBy design; check missions table instead
Authorization header is missingCLAUDE_CODE_SIMPLE in credential profile env blocks apiKeyHelper loadingRemove CLAUDE_CODE_SIMPLE from credential profile env (enforced by forbid_env assertion)
Authentication failed: API Key is validMissing AWS_REGION in credential profile envAdd AWS_REGION: "ap-southeast-2" to auth.credentials.profiles.bedrock.env (enforced by require_env assertion)
model identifier is invalidStale/wrong Bedrock model IDUse global.anthropic.claude-sonnet-4-6 (not us.anthropic.* or old dated IDs)
configmaps is forbidden (in-cluster mode)Controlplane pod using default service accountSet serviceAccountName: fracta-agent on controlplane deployment
bedrock-auth-helper: executable file not found (in-cluster mode)host_fallback credential source (scope: host_edge) runs bedrock-auth-helper which doesn’t exist in-clusterIn-cluster config should not include host_fallback source; the credential planner annotates it as unavailable. Pods self-auth via corporate proxy source

Teardown

kubectl delete -f deployment/k8s/manifests/
kubectl delete namespace fracta
Deletes the fracta namespace and persistent volumes.