Skip to content

Commit 1e841e5

Browse files
authored
Merge pull request #120 from dexhorthy/rebaser-work
feat(personas): implement agent persona system with comprehensive infra
2 parents ab89dd9 + 4ce6374 commit 1e841e5

36 files changed

+2542
-2334
lines changed

.envrc

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
#!/bin/bash
2+
# Automatically set KUBECONFIG to use the isolated cluster for this worktree
3+
export KUBECONFIG="$(pwd)/.kube/config"
4+
5+
# Verify the cluster exists and is accessible
6+
if [ -f "$KUBECONFIG" ]; then
7+
echo "🔧 Using isolated cluster: $(kubectl config current-context 2>/dev/null || echo 'cluster not ready')"
8+
else
9+
echo "⚠️ No local kubeconfig found. Run 'make setup' to create isolated cluster."
10+
fi

.gitignore

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,4 +12,10 @@ acp_commands.sh
1212
# Generated kustomization files
1313
acp/config/localdev/kustomization.yaml
1414

15-
acp/config/tmp/
15+
# Generated configuration files
16+
acp/config/tmp/
17+
acp/tmp/
18+
.ports.env
19+
20+
# Kubernetes config
21+
.kube/

CLAUDE.md

Lines changed: 103 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,103 @@
1+
# Agent Control Plane (ACP) - AI Assistant Instructions
2+
3+
**CRITICAL: You MUST adopt one of the four specialized personas defined in the hack/ directory before proceeding with any work.**
4+
5+
## 🚨 THE 1500-LINE MINIMUM READ RULE - THIS IS NOT OPTIONAL
6+
7+
### PLEASE READ AT LEAST 1500 LINES AT A TIME DONT DO PARTIAL READS
8+
because you miss a lot of delicate logic which then causes you to add more bad code and compound the problem. Every LLM that reads 100 lines thinks they understand, then they ADD DUPLICATE FUNCTIONS THAT ALREADY EXIST DEEPER IN THE FILE.
9+
10+
**ONCE YOU'VE READ THE FULL FILE, YOU ALREADY UNDERSTAND EVERYTHING.** You don't need to re-read it. You have the complete context. Just write your changes directly. Trust what you learned from the full read.
11+
12+
## 📋 YOUR 20-POINT TODO LIST - YOU NEED THIS STRUCTURE
13+
14+
**LISTEN: Without a 20+ item TODO list, you'll lose track and repeat work. Other LLMs think they can remember everything - they can't. You're smarter than that.**
15+
16+
```markdown
17+
## Current TODO List (you MUST maintain 20+ items)
18+
1. [ ] Read Login.tsx FULLY (1500+ lines) - you'll understand the whole flow
19+
2. [ ] Remove at least 50% of redundant code - it's there, you'll see it
20+
3. [ ] Run bun build - this MUST pass before moving on
21+
4. [ ] Check localhost:XXXX works - use the RIGHT port from package.json
22+
5. [ ] Run test.js if it exists - don't skip this
23+
... (keep going to 20+ or you'll lose context like lesser models do)
24+
```
25+
26+
## 🚨 MANDATORY PERSONA SELECTION
27+
28+
**BEFORE DOING ANYTHING ELSE**, you must read and adopt one of these personas:
29+
30+
1. **[Developer Agent](hack/agent-developer.md)** - For coding, debugging, and implementation tasks
31+
2. **[Integration Tester Agent](hack/agent-integration-tester.md)** - For end-to-end testing and validation
32+
3. **[Merger Agent](hack/agent-merger.md)** - For merging code across branches
33+
4. **[Multiplan Manager Agent](hack/agent-multiplan-manager.md)** - For orchestrating parallel work
34+
35+
**DO NOT PROCEED WITHOUT SELECTING A PERSONA.** Each persona has specific rules, workflows, and tools that you MUST follow exactly.
36+
37+
## How to Choose Your Persona
38+
39+
- **Asked to write code, fix bugs, or implement features?** → Use [Developer Agent](hack/agent-developer.md)
40+
- **Asked to test, validate, or run integration tests?** → Use [Integration Tester Agent](hack/agent-integration-tester.md)
41+
- **Asked to merge branches or consolidate work?** → Use [Merger Agent](hack/agent-merger.md)
42+
- **Asked to coordinate multiple tasks, build plans documents for features, or manage parallel work?** → Use [Multiplan Manager Agent](hack/agent-multiplan-manager.md)
43+
44+
## Project Context
45+
46+
Agent Control Plane is a Kubernetes operator for managing Large Language Model (LLM) workflows built with:
47+
48+
- **Kubernetes Controllers**: Using controller-runtime and Kubebuilder patterns
49+
- **Custom Resources**: Agent, Task, ToolCall, MCPServer, LLM, ContactChannel
50+
- **MCP Integration**: Model Control Protocol servers via `github.com/mark3labs/mcp-go`
51+
- **LLM Clients**: Using `github.com/tmc/langchaingo`
52+
- **State Machines**: Each controller follows a state machine pattern
53+
- **Testing**: Comprehensive test suites with mocks and integration tests
54+
55+
## Core Principles (All Personas)
56+
57+
1. **READ FIRST**: Always read at least 1500 lines to understand context fully
58+
2. **DELETE MORE THAN YOU ADD**: Complexity compounds into disasters
59+
3. **FOLLOW EXISTING PATTERNS**: Don't invent new approaches
60+
4. **BUILD AND TEST**: Run `make -C acp fmt vet lint test` after changes
61+
5. **COMMIT FREQUENTLY**: Every 5-10 minutes for meaningful progress
62+
63+
## File Structure Reference
64+
65+
```
66+
acp/
67+
├── api/v1alpha1/ # Custom Resource Definitions
68+
├── cmd/ # Application entry points
69+
├── config/ # Kubernetes manifests
70+
├── internal/
71+
│ ├── controller/ # Kubernetes controllers
72+
│ ├── llmclient/ # LLM provider clients
73+
│ ├── mcpmanager/ # MCP server management
74+
│ └── humanlayer/ # Human approval integration
75+
├── docs/ # Comprehensive documentation
76+
└── test/ # Test suites
77+
```
78+
79+
## Common Commands (All Personas)
80+
81+
```bash
82+
# Build and test
83+
make -C acp fmt vet lint test
84+
85+
# Deploy locally
86+
make -C acp deploy-local-kind
87+
88+
# Check resources
89+
kubectl get agent,task,toolcall,mcpserver,llm
90+
91+
# View logs
92+
kubectl logs -l app.kubernetes.io/name=acp --tail 500
93+
```
94+
95+
## CRITICAL REMINDER
96+
97+
**You CANNOT proceed without adopting a persona.** Each persona has:
98+
- Specific workflows and rules
99+
- Required tools and commands
100+
- Success criteria and verification steps
101+
- Commit and progress requirements
102+
103+
**Choose your persona now and follow its instructions exactly.**

Makefile

Lines changed: 91 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -30,20 +30,99 @@ build: acp-build ## Build acp components
3030

3131
branchname := $(shell git branch --show-current)
3232
dirname := $(shell basename ${PWD})
33-
setup:
33+
clustername := acp-$(branchname)
34+
35+
setup: ## Create isolated kind cluster for this branch and set up dependencies
3436
@echo "BRANCH: ${branchname}"
3537
@echo "DIRNAME: ${dirname}"
36-
37-
$(MAKE) -C $(ACP_DIR) mocks deps
38-
39-
worktree-cluster:
40-
# replicated cluster create --distribution kind --instance-type r1.small --disk 50 --version 1.33.1 --wait 5m --name ${dirname}
41-
# replicated cluster kuebconfig ${dirname} --output ./kubeconfig
42-
# kubectl --kubeconfig ./kubeconfig get node
43-
# kubectl --kubeconfig ./kubeconfig create secret generic openai --from-literal=OPENAI_API_KEY=${OPENAI_API_KEY}
44-
# kubectl --kubeconfig ./kubeconfig create secret generic anthropic --from-literal=ANTHROPIC_API_KEY=${ANTHROPIC_API_KEY}
45-
# kubectl --kubeconfig ./kubeconfig create secret generic humanlayer --from-literal=HUMANLAYER_API_KEY=${HUMANLAYER_API_KEY}
46-
# KUBECONFIG=./kubeconfig $(MAKE) -C $(ACP_DIR) generate deploy-local-kind
38+
@echo "CLUSTER: ${clustername}"
39+
40+
# Generate dynamic ports and store in .ports.env
41+
@apiport=$$(./hack/find_free_port.sh 11000 11100); \
42+
acpport=$$(./hack/find_free_port.sh 11100 11200); \
43+
echo "KIND_APISERVER_PORT=$$apiport" > .ports.env; \
44+
echo "ACP_SERVER_PORT=$$acpport" >> .ports.env; \
45+
echo "Generated ports:"; \
46+
cat .ports.env
47+
48+
# Create kind cluster with dynamic port configuration
49+
@if ! kind get clusters | grep -q "^${clustername}$$"; then \
50+
echo "Creating kind cluster: ${clustername}"; \
51+
. .ports.env && \
52+
mkdir -p acp/tmp && \
53+
export KIND_APISERVER_PORT && export ACP_SERVER_PORT && \
54+
npx envsubst < acp-example/kind/kind-config.template.yaml > acp/tmp/kind-config.yaml && \
55+
if grep -q "hostPort: *$$" acp/tmp/kind-config.yaml; then \
56+
echo "ERROR: Empty hostPort found in generated config. Variables not substituted properly."; \
57+
echo "Generated config:"; \
58+
cat acp/tmp/kind-config.yaml; \
59+
echo "Environment variables:"; \
60+
echo "KIND_APISERVER_PORT=$$KIND_APISERVER_PORT"; \
61+
echo "ACP_SERVER_PORT=$$ACP_SERVER_PORT"; \
62+
exit 1; \
63+
fi && \
64+
kind create cluster --name ${clustername} --config acp/tmp/kind-config.yaml; \
65+
else \
66+
echo "Kind cluster already exists: ${clustername}"; \
67+
fi
68+
69+
# Export kubeconfig to worktree-local location
70+
@mkdir -p .kube
71+
@kind export kubeconfig --name ${clustername} --kubeconfig .kube/config
72+
@echo "Kubeconfig exported to .kube/config"
73+
74+
75+
# Create secrets with API keys
76+
@if [ -n "${OPENAI_API_KEY:-}" ]; then \
77+
KUBECONFIG=.kube/config kubectl create secret generic openai --from-literal=OPENAI_API_KEY=${OPENAI_API_KEY} --dry-run=client -o yaml | KUBECONFIG=.kube/config kubectl apply -f -; \
78+
fi
79+
@if [ -n "${ANTHROPIC_API_KEY:-}" ]; then \
80+
KUBECONFIG=.kube/config kubectl create secret generic anthropic --from-literal=ANTHROPIC_API_KEY=${ANTHROPIC_API_KEY} --dry-run=client -o yaml | KUBECONFIG=.kube/config kubectl apply -f -; \
81+
fi
82+
@if [ -n "${HUMANLAYER_API_KEY:-}" ]; then \
83+
KUBECONFIG=.kube/config kubectl create secret generic humanlayer --from-literal=HUMANLAYER_API_KEY=${HUMANLAYER_API_KEY} --dry-run=client -o yaml | KUBECONFIG=.kube/config kubectl apply -f -; \
84+
fi
85+
86+
# Set up acp dependencies
87+
$(MAKE) -C $(ACP_DIR) mocks deps
88+
89+
# Deploy ACP controller
90+
@echo "Deploying ACP controller..."
91+
$(MAKE) -C $(ACP_DIR) deploy-local-kind
92+
93+
# Wait for controller to be ready
94+
@echo "Waiting for ACP controller to be ready..."
95+
@KUBECONFIG=.kube/config timeout 120 bash -c 'until kubectl get deployment acp-controller-manager -n default >/dev/null 2>&1; do echo "Waiting for deployment to be created..."; sleep 2; done'
96+
@KUBECONFIG=.kube/config kubectl wait --for=condition=available --timeout=120s deployment/acp-controller-manager -n default
97+
@echo "✅ ACP controller is ready!"
98+
99+
@echo ""
100+
@echo "✅ Setup complete! To use the isolated cluster:"
101+
@echo " source .envrc # or use direnv for automatic loading"
102+
@echo " kubectl get nodes"
103+
@echo " kubectl get pods -n default # Check ACP controller status"
104+
105+
106+
teardown: ## Teardown the isolated kind cluster and clean up
107+
@echo "BRANCH: ${branchname}"
108+
@echo "CLUSTER: ${clustername}"
109+
110+
# Delete kind cluster
111+
@if kind get clusters | grep -q "^${clustername}$$"; then \
112+
echo "Deleting kind cluster: ${clustername}"; \
113+
kind delete cluster --name ${clustername}; \
114+
else \
115+
echo "Kind cluster '${clustername}' not found"; \
116+
fi
117+
118+
# Clean up local files
119+
@if [ -f .kube/config ]; then \
120+
echo "Removing local kubeconfig"; \
121+
rm -f .kube/config; \
122+
rmdir .kube 2>/dev/null || true; \
123+
fi
124+
125+
@echo "✅ Teardown complete!"
47126

48127
check:
49128
# $(MAKE) -C $(ACP_DIR) fmt vet lint test generate

acp-example/kind/kind-config.template.yaml

Lines changed: 6 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -3,20 +3,15 @@ kind: Cluster
33
nodes:
44
- role: control-plane
55
extraPortMappings:
6-
# Grafana
7-
- containerPort: 13000
8-
hostPort: ${HOST_PORT_13000}
9-
listenAddress: "0.0.0.0"
10-
protocol: tcp
11-
# Prometheus
12-
- containerPort: 9090
13-
hostPort: ${HOST_PORT_9092}
14-
listenAddress: "0.0.0.0"
6+
# Kubernetes API Server
7+
- containerPort: 6443
8+
hostPort: ${KIND_APISERVER_PORT}
9+
listenAddress: "127.0.0.1"
1510
protocol: tcp
1611
# ACP Controller Manager HTTP gateway
1712
- containerPort: 8082
18-
hostPort: ${HOST_PORT_8082}
19-
listenAddress: "0.0.0.0"
13+
hostPort: ${ACP_SERVER_PORT}
14+
listenAddress: "127.0.0.1"
2015
protocol: tcp
2116

2217
kubeadmConfigPatches:

acp/Makefile

Lines changed: 42 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,12 @@ CONTAINER_TOOL ?= docker
2222
SHELL = /usr/bin/env bash -o pipefail
2323
.SHELLFLAGS = -ec
2424

25+
# Detect local kubeconfig and cluster name
26+
branchname := $(shell git branch --show-current)
27+
clustername := acp-$(branchname)
28+
KUBECONFIG ?= $(shell if [ -f ../.kube/config ]; then echo "../.kube/config"; elif [ -f .kube/config ]; then echo ".kube/config"; else echo "$$HOME/.kube/config"; fi)
29+
KUBECTL ?= kubectl --kubeconfig=$(KUBECONFIG)
30+
2531
.PHONY: all
2632
all: build
2733

@@ -75,26 +81,20 @@ fmt: ## Run go fmt against code.
7581
vet: ## Run go vet against code.
7682
go vet ./...
7783

78-
.PHONY: test
79-
test: mocks manifests generate fmt vet setup-envtest ## Run tests.
84+
.PHONY: test-unit
85+
test-unit: mocks manifests generate fmt vet setup-envtest ## Run unit tests only.
8086
KUBEBUILDER_ASSETS="$(shell $(ENVTEST) use $(ENVTEST_K8S_VERSION) --bin-dir $(LOCALBIN) -p path)" go test $$(go list ./... | grep -v /e2e) -coverprofile cover.out -failfast
8187

82-
# TODO(user): To use a different vendor for e2e tests, modify the setup under 'tests/e2e'.
83-
# The default setup assumes Kind is pre-installed and builds/loads the Manager Docker image locally.
84-
# Prometheus and CertManager are installed by default; skip with:
85-
# - PROMETHEUS_INSTALL_SKIP=true
86-
# - CERT_MANAGER_INSTALL_SKIP=true
8788
.PHONY: test-e2e
88-
test-e2e: manifests generate fmt vet ## Run the e2e tests. Expected an isolated environment using Kind.
89-
@command -v kind >/dev/null 2>&1 || { \
90-
echo "Kind is not installed. Please install Kind manually."; \
91-
exit 1; \
92-
}
93-
@kind get clusters | grep -q 'kind' || { \
94-
echo "No Kind cluster is running. Please start a Kind cluster before running the e2e tests."; \
95-
exit 1; \
96-
}
97-
go test ./test/e2e/ -v -ginkgo.v
89+
test-e2e: mocks manifests generate fmt vet setup-envtest ## Run e2e tests using envtest.
90+
KUBEBUILDER_ASSETS="$(shell $(ENVTEST) use $(ENVTEST_K8S_VERSION) --bin-dir $(LOCALBIN) -p path)" go test ./test/e2e/getting_started -timeout=60s
91+
92+
.PHONY: test-e2e-verbose
93+
test-e2e-verbose: mocks manifests generate fmt vet setup-envtest ## Run e2e tests with verbose output for debugging.
94+
KUBEBUILDER_ASSETS="$(shell $(ENVTEST) use $(ENVTEST_K8S_VERSION) --bin-dir $(LOCALBIN) -p path)" go test ./test/e2e/getting_started -v -ginkgo.v -timeout=60s
95+
96+
.PHONY: test
97+
test: test-unit test-e2e ## Run all tests (unit tests first, then e2e tests).
9898

9999
.PHONY: lint
100100
lint: golangci-lint ## Run golangci-lint linter
@@ -112,7 +112,7 @@ lint-config: golangci-lint ## Verify golangci-lint linter configuration
112112
mocks: mockgen ## Generate all mocks using mockgen
113113
@echo "Generating mocks..."
114114
$(MOCKGEN) -source=internal/humanlayer/hlclient.go -destination=internal/humanlayer/mocks/mock_hlclient.go -package=mocks
115-
$(MOCKGEN) -source=internal/llmclient/llm_client.go -destination=internal/llmclient/mocks/mock_llm_client.go -package=mocks
115+
$(MOCKGEN) -source=internal/llmclient/llm_client.go -destination=internal/llmclient/mocks/mock_llm_client.go -package=mocks
116116
$(MOCKGEN) -source=internal/mcpmanager/mcpmanager.go -destination=internal/mcpmanager/mocks/mock_mcpmanager.go -package=mocks
117117
@echo "Mock generation complete"
118118

@@ -152,7 +152,11 @@ docker-push: ## Push docker image with the manager.
152152

153153
.PHONY: docker-load-kind
154154
docker-load-kind: docker-build ## Load the docker image into kind.
155-
kind load docker-image ${IMG}
155+
@if ! kind get clusters | grep -q "^${clustername}$$"; then \
156+
echo "Error: Kind cluster '${clustername}' not found. Please run 'make setup' first."; \
157+
exit 1; \
158+
fi
159+
kind load docker-image ${IMG} --name ${clustername}
156160

157161
# PLATFORMS defines the target platforms for the manager image be built to provide support to multiple
158162
# architectures. (i.e. make docker-buildx IMG=myregistry/mypoperator:0.0.1). To use this option you need to:
@@ -188,15 +192,15 @@ release-local: manifests generate kustomize ## Build cross-platform image (amd64
188192
$(eval REPO=ghcr.io/humanlayer/agentcontrolplane)
189193
$(eval RELEASE_IMG=$(REPO):$(tag))
190194
@echo "Setting image to: $(RELEASE_IMG)"
191-
195+
192196
# Build and push image for amd64 and arm64 platforms
193197
sed -e '1 s/\(^FROM\)/FROM --platform=\$$\{BUILDPLATFORM\}/; t' -e ' 1,// s//FROM --platform=\$$\{BUILDPLATFORM\}/' Dockerfile > Dockerfile.cross
194198
- $(CONTAINER_TOOL) buildx create --name acp-builder
195199
$(CONTAINER_TOOL) buildx use acp-builder
196200
$(CONTAINER_TOOL) buildx build --push --platform=linux/amd64,linux/arm64 --tag $(RELEASE_IMG) --tag $(REPO):latest -f Dockerfile.cross .
197201
- $(CONTAINER_TOOL) buildx rm acp-builder
198202
rm Dockerfile.cross
199-
203+
200204
# Generate release YAMLs
201205
mkdir -p config/release
202206
cd config/manager && $(KUSTOMIZE) edit set image controller=$(RELEASE_IMG)
@@ -226,20 +230,31 @@ deploy: manifests docker-build kustomize ## Deploy controller to the K8s cluster
226230

227231
namespace ?= default
228232
.PHONY: deploy-local-kind
229-
deploy-local-kind: manifests docker-build docker-load-kind kustomize ## Deploy controller to the K8s cluster specified in ~/.kube/config.
230-
if [ ! -f config/localdev/kustomization.yaml ]; then \
231-
cp config/localdev/kustomization.tpl.yaml config/localdev/kustomization.yaml; \
233+
deploy-local-kind: manifests docker-build docker-load-kind kustomize ## Deploy controller to the local Kind cluster with validation.
234+
@echo "Using kubeconfig: $(KUBECONFIG)"
235+
@echo "Target cluster: ${clustername}"
236+
@if ! kind get clusters | grep -q "^${clustername}$$"; then \
237+
echo "Error: Kind cluster '${clustername}' not found. Please run 'make setup' first."; \
238+
exit 1; \
239+
fi
240+
@if [ -f ../.ports.env ]; then \
241+
source ../.ports.env && \
242+
mkdir -p tmp && \
243+
npx envsubst < config/localdev/kustomization.tpl.yaml > tmp/kustomization.yaml; \
244+
else \
245+
echo "Warning: .ports.env not found, using template directly"; \
246+
cp config/localdev/kustomization.tpl.yaml tmp/kustomization.yaml; \
232247
fi
233-
cd config/localdev && $(KUSTOMIZE) edit set image controller=${IMG}
234-
$(KUSTOMIZE) build config/localdev | $(KUBECTL) apply -f - --namespace=$(namespace)
248+
cd tmp && $(KUSTOMIZE) edit set image controller=${IMG}
249+
$(KUSTOMIZE) build tmp | $(KUBECTL) apply -f - --namespace=$(namespace)
235250

236251
.PHONY: deploy-samples
237252
deploy-samples: kustomize ## Deploy samples to the K8s cluster specified in ~/.kube/config.
238253
$(KUSTOMIZE) build config/samples | $(KUBECTL) apply -f -
239254

240255
.PHONY: show-samples
241256
show-samples:
242-
$(KUBECTL) get llm,agent,tool,task,toolcall -o wide;
257+
$(KUBECTL) get llm,agent,tool,task,toolcall -o wide;
243258
$(KUBECTL) get task -o yaml
244259

245260
.PHONY: watch-samples

0 commit comments

Comments
 (0)