Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
108 changes: 84 additions & 24 deletions AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@

### Repository Layout
- Monorepo managed by Bun workspaces: `packages/gateway`, `packages/orchestrator`, `packages/worker`, `packages/core`.
- Top-level: `Makefile`, `bin/` (CLI/setup), `docker-compose*.yml`, `charts/peerbot` (Helm), `workspaces/`, `.env*`.
- Top-level: `Makefile`, `bin/` (CLI/setup), `sidecar.yaml`, `charts/peerbot` (Helm), `workspaces/`, `.env*`.
- TypeScript sources under `packages/*/src`. Tests in `packages/*/src/__tests__` and `packages/core/tests`.
- **ALWAYS prefer `bun` commands over `npm`**
- When fixing unused parameter errors, remove the parameter entirely if possible rather than prefixing with underscore
Expand All @@ -23,7 +23,7 @@
### Architecture

#### Platform
We currently only support Slack as messaging app.
We currently use WhatsApp as the messaging platform (Slack support also available but not configured).
There is also a public endpoint in gateway to trigger running the agent.

#### Orchestration
Expand Down Expand Up @@ -64,7 +64,7 @@ TypeScript packages must be compiled from `src/` → `dist/`. If you modify any
- The "is running" thread status indicator (with rotating messages) provides user feedback during processing; visible "Still processing" heartbeat messages are not sent to avoid clutter.
- Anytime you make changes in the code, you MUST:

1. Have the bot running via `make dev` running in the background for development. This uses Docker Compose with hot reload enabled when NODE_ENV=development.
1. Have the bot running via sidecar (`/process-management`) for development.
2. Test the bot using the test script:
```bash
./scripts/test-bot.sh "@me test prompt"
Expand All @@ -73,7 +73,7 @@ The script automatically handles sending the message, waiting for response, and
```bash
./scripts/test-bot.sh "@me first message" "follow up question" "another question"
```
3. Check logs using `docker compose logs` or `make logs` to verify the bot works properly.
3. Check logs using `get_logs("gateway")` via `/process-management` to verify the bot works properly.

- If you create ephemeral files, you MUST delete them when you're done with them.
- Use Docker to build and run the Slack bot in development mode, K8S for production.
Expand All @@ -87,33 +87,88 @@ File attachments are fully supported in all message contexts (DM, app mentions,

## Development Mode

- **Docker Compose**: Run `make dev` to start all services with hot reload enabled (uses docker-compose.dev.yml)
- **Logs**: View logs with `make logs` or `docker compose -f docker-compose.dev.yml logs -f [service]`
- **Hot Reload**: Source code changes are automatically detected when NODE_ENV=development
- **Gateway**: Source files are mounted as volumes and Bun runs with `--watch` flag
- Changes to `packages/gateway/src/`, `packages/core/src/`, or `packages/github/src/` trigger immediate restart
- Built dependencies (`packages/core/dist/`, `packages/github/dist/`) are also mounted
- Just save the file and watch logs for "Restarting..." message
- **Worker**: Worker image is rebuilt automatically in Docker mode (no rebuild needed for code changes)
- If hot reload isn't working, verify you're using `make dev` not `docker compose up`

### Automatic Build on `make dev`
- `make dev` now automatically runs `make build-packages` before starting services, so you don't have to remember.
- However, if you're testing changes without restarting:
- 1. **Option A(Recommended)**: Use `make watch-packages` in a separate terminal for auto-rebuild
- 2. **Option B**: Manually run `make build-packages` after each change
- 3. **Option C**: Restart with `make dev` to rebuild everything
### Prerequisites
- Redis: `brew install redis`
- Bun: installed
- Docker Desktop: running

### First-time Setup
```bash
./scripts/setup-dev.sh
```

### Starting Development
**Automatic!** Just open this project in Claude Code - sidecar auto-starts:
1. Redis server (port 6379)
2. Package watcher (rebuilds on changes)
3. Gateway with hot reload (port 8080)

A tmux session opens showing all process output.

### Managing Processes
Use `/process-management` if needed:
- `list_processes` - See status
- `get_logs("gateway")` - View logs
- `restart_process("gateway")` - Restart

### Hot Reload
- **Gateway**: Runs with `bun --watch`, auto-restarts on source changes
- **Packages**: The `packages` process watches and rebuilds TypeScript packages
- **Worker**: Run `make clean-workers` after worker code changes

### Testing
```bash
./scripts/test-bot.sh "@me test prompt"
```

## Deployment Instructions

When making changes to the Slack bot:
1. **Development**: Use `make dev` to start the server if it's not running. Use docker compose (for docker mode) or kubectl (for kubernetes mode) to pull the logs.
1. **Development**: Open project in Claude Code (auto-starts). View logs with `get_logs("gateway")`
2. **Kubernetes deployment**: Use `make deploy` for production deployment

## Environment Configuration

The `.env` file is the single source of truth for all secrets and configuration.

### Local Development
- Sidecar automatically reloads gateway when `.env` changes (via `envFile: .env`)
- No manual action needed

### Kubernetes Deployment
When `.env` changes, sync secrets to K8s using Sealed Secrets:

```bash
# Seal and apply secrets from .env
./scripts/seal-env.sh --apply

# Or output to file for review
./scripts/seal-env.sh -o sealed-secrets.yaml
kubectl apply -f sealed-secrets.yaml
```

**Prerequisites for Sealed Secrets:**
```bash
# Install controller (once per cluster)
helm repo add sealed-secrets https://bitnami-labs.github.io/sealed-secrets
helm install sealed-secrets sealed-secrets/sealed-secrets -n kube-system

# Install CLI
brew install kubeseal
```

The gateway deployment has a checksum annotation that triggers automatic pod restart when secrets change via `helm upgrade`.

## Development Configuration

- Rate limiting is disabled in local development
- Worker image is built automatically when running `make dev`
- Worker image built with `make build-worker` or `make setup`

### Docker Compose (Alternative)
For quick demos without sidecar, docker-compose.yml is available:
```bash
docker compose up
```
## Persistent Storage

Worker deployments use persistent volumes for session continuity across scale-to-zero:
Expand Down Expand Up @@ -208,6 +263,10 @@ export GOOGLE_CLIENT_SECRET=your_google_client_secret

Use the `test-bot.sh` script for easy bot testing. No manual curl commands needed.

**Platform self-testing behavior:**
- **Slack**: Cannot trigger its own event handlers (Slack filters bot-to-self messages). The test script uses `/api/messaging/send` endpoint which posts via bot token, then gateway receives as normal Slack events.
- **WhatsApp**: Supports self-chat mode! Set `WHATSAPP_SELF_CHAT=true` and send to the bot's own phone number. The gateway detects self-messages and queues them directly to workers, bypassing event handler filters.

### Basic Test
```bash
./scripts/test-bot.sh "@me hello"
Expand Down Expand Up @@ -241,6 +300,7 @@ curl -X POST http://localhost:8080/api/messaging/send \
```

### Check Logs
```bash
docker compose -f docker-compose.dev.yml logs gateway --tail 50
Use `/process-management`:
```
get_logs("gateway", tail=50)
```
37 changes: 17 additions & 20 deletions Dockerfile.gateway
Original file line number Diff line number Diff line change
@@ -1,50 +1,47 @@
FROM node:20-alpine

# Install bun and docker-cli for build and runtime
# Install bun for fast dependency installation, docker-cli for runtime
RUN apk add --no-cache curl bash docker-cli && \
curl -fsSL https://bun.sh/install | bash
ENV PATH="/root/.bun/bin:${PATH}"
curl -fsSL https://bun.sh/install | BUN_INSTALL=/usr/local bash && \
chmod +x /usr/local/bin/bun
ENV PATH="/usr/local/bin:${PATH}"

WORKDIR /app

# Only set up a minimal workspace for gateway + core + github to avoid cache busts
# Set up workspace for gateway + core + github
COPY tsconfig.json ./
RUN echo '{ "name": "@peerbot/gateway-build", "private": true, "workspaces": [ "packages/core", "packages/github", "packages/gateway" ] }' > package.json
COPY packages/core/package.json ./packages/core/
COPY packages/github/package.json ./packages/github/
COPY packages/gateway/package.json ./packages/gateway/

# Install dependencies for gateway + core + github
# Allow some packages to fail (post-install scripts) but keep others
# Install dependencies with Bun (faster than npm)
RUN --mount=type=cache,target=/root/.bun/install/cache bun install || true

# Copy source code
COPY packages/core/ ./packages/core/
COPY packages/github/ ./packages/github/
COPY packages/gateway/ ./packages/gateway/

# Build core first
# Build core and github packages (gateway has type errors, run from source)
WORKDIR /app/packages/core
RUN bun run build

# Build github module
WORKDIR /app/packages/github
RUN bun run build

# Build gateway - Skip build due to pre-existing type errors, run from source
# Gateway build has type errors that are pre-existing
WORKDIR /app/packages/gateway
# RUN bun run build
# Install tsx for running TypeScript with Node.js
WORKDIR /app
RUN npm install -g tsx

# Runtime
# Runtime configuration
ENV NODE_ENV=production
EXPOSE 3000

# Runtime user setup is handled by node:20-alpine

# Set working directory back to /app for runtime to resolve modules correctly
WORKDIR /app
# Fix permissions for non-root K8s execution (user 1001)
RUN chmod -R a+rX /app

# Use Bun to run from source (better WebSocket compatibility)
# In development, docker-compose overrides this with --watch flag
CMD ["bun", "packages/gateway/src/index.ts"]
# Run with Node.js + tsx for TypeScript support
# This gives us Node.js compatibility while running .ts files directly
ENTRYPOINT []
CMD ["tsx", "packages/gateway/src/index.ts"]
91 changes: 29 additions & 62 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -5,19 +5,18 @@
# Default target
help:
@echo "Available commands:"
@echo " peerbot setup - Interactive setup for Slack bot development"
@echo " peerbot build-packages - Build all TypeScript packages (core, gateway, worker)"
@echo " peerbot check-build - Check if packages need rebuilding"
@echo " peerbot watch-packages - Watch packages and rebuild on changes (for development)"
@echo " peerbot dev - Start development with Docker Compose (hot reload)"
@echo " peerbot prod - Start production with Docker Compose"
@echo " peerbot down - Stop all services including dynamic workers"
@echo " peerbot logs - View Docker Compose service logs"
@echo " peerbot deploy - Deploy to K8s using values-local.yaml"
@echo " peerbot deploy --target=production - Deploy to K8s using values-production.yaml"
@echo " peerbot deploy --target=path/to/values.yaml - Deploy to K8s using custom values file"
@echo " peerbot test - Run test bot"
@echo " peerbot clean - Stop all services and clean up all resources"
@echo " make setup - Setup development environment (run once)"
@echo " make build-packages - Build all TypeScript packages"
@echo " make build-worker - Build worker Docker image"
@echo " make watch-packages - Watch packages and rebuild on changes"
@echo " make deploy - Deploy to K8s using values-local.yaml"
@echo " make deploy TARGET=production - Deploy to K8s using values-production.yaml"
@echo " make test - Run test bot"
@echo " make clean - Stop all services and clean up"
@echo " make clean-workers - Remove worker containers only"
@echo ""
@echo "Development:"
@echo " Use /process-management to start/stop sidecar processes (redis, packages, gateway)"

# Build all TypeScript packages in dependency order
build-packages:
Expand All @@ -40,41 +39,14 @@ check-build:
watch-packages:
@./scripts/watch-packages.sh

# Start local development with Docker Compose in foreground
dev:
@if [ ! -f .env ]; then \
echo "❌ .env file not found!"; \
echo ""; \
echo "Please run setup first:"; \
echo " make setup"; \
echo ""; \
exit 1; \
fi
@echo "ℹ️ Loading environment overrides from .env"
@echo ""
@echo "📦 Step 1/2: Building TypeScript packages..."
@$(MAKE) build-packages
@echo ""
@echo "🚀 Step 2/2: Starting local development mode with Docker Compose..."
@echo " This will:"
@echo " - Build all services including worker image"
@echo " - Start services with hot reload"
@echo " - Mount source code for live changes"
@echo ""
@echo "🔨 Building all services..."
@DETACH_FLAG=""; [ "$(DETACH)" = "1" ] && DETACH_FLAG="-d"; \
if [ -n "$$DETACH_FLAG" ]; then echo "🧩 Running in detached mode"; fi; \
COMPOSE_DOCKER_CLI_BUILD=1 DOCKER_BUILDKIT=1 docker compose -f docker-compose.dev.yml up $$DETACH_FLAG --build gateway redis
# Setup development environment (run once)
setup:
@./scripts/setup-dev.sh

# Build the worker image on demand
# Build the worker image
build-worker:
@echo "📦 Building worker image..."
@COMPOSE_DOCKER_CLI_BUILD=1 DOCKER_BUILDKIT=1 docker compose -f docker-compose.dev.yml build worker

# Convenience alias for detached dev
.PHONY: dev-d dev-detached
dev-d dev-detached:
@$(MAKE) dev DETACH=1
@docker build -t peerbot-worker:latest -f Dockerfile.worker --build-arg NODE_ENV=development .

# Catch-all target to prevent errors when passing arguments
%:
Expand Down Expand Up @@ -179,22 +151,19 @@ logs:
echo ""; \
echo "View logs with:"; \
echo " kubectl logs -f <pod-name> -n peerbot"; \
elif docker compose -f docker-compose.dev.yml ps --services 2>/dev/null | grep -q .; then \
docker compose -f docker-compose.dev.yml logs -f; \
else \
docker compose logs -f; \
echo "For development, use /process-management to view logs:"; \
echo " get_logs(\"gateway\")"; \
echo " get_logs(\"redis\")"; \
fi

# Stop all services without removing volumes
# Stop worker containers
down:
@echo "🛑 Stopping all peerbot services and workers..."
@# Stop and remove all containers with the peerbot project label (includes dynamic workers)
@docker ps -q --filter "label=com.docker.compose.project=peerbot" | xargs -r docker stop 2>/dev/null || true
@docker ps -aq --filter "label=com.docker.compose.project=peerbot" | xargs -r docker rm 2>/dev/null || true
@# Use docker compose to clean up networks and remaining resources
@docker compose -f docker-compose.dev.yml down --remove-orphans 2>/dev/null || true
@docker compose down --remove-orphans 2>/dev/null || true
@echo "✅ All peerbot services stopped"
@echo "🛑 Stopping peerbot worker containers..."
@docker ps -q --filter "label=app.kubernetes.io/component=worker" | xargs -r docker stop 2>/dev/null || true
@docker ps -aq --filter "label=app.kubernetes.io/component=worker" | xargs -r docker rm 2>/dev/null || true
@echo "✅ Worker containers stopped"
@echo "Note: For sidecar processes (redis, gateway), use /process-management"

# Clean up everything including volumes
clean:
Expand All @@ -211,13 +180,11 @@ clean:
kubectl delete namespace peerbot --wait=false 2>/dev/null || true; \
echo "✅ Kubernetes resources cleaned up"; \
else \
echo "🐳 Cleaning Docker Compose resources..."; \
docker ps -q --filter "label=com.docker.compose.project=peerbot" | xargs -r docker stop 2>/dev/null || true; \
docker ps -aq --filter "label=com.docker.compose.project=peerbot" | xargs -r docker rm 2>/dev/null || true; \
echo "🐳 Cleaning Docker worker containers..."; \
docker ps -q --filter "label=app.kubernetes.io/component=worker" | xargs -r docker stop 2>/dev/null || true; \
docker ps -aq --filter "label=app.kubernetes.io/component=worker" | xargs -r docker rm 2>/dev/null || true; \
docker compose -f docker-compose.dev.yml down -v --remove-orphans 2>/dev/null || true; \
docker compose down -v --remove-orphans 2>/dev/null || true; \
docker volume ls -q --filter "name=peerbot-workspace-" | xargs -r docker volume rm 2>/dev/null || true; \
docker network rm peerbot-internal 2>/dev/null || true; \
echo "✅ Docker containers and volumes cleaned up"; \
fi

Expand Down
Loading
Loading