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
48 changes: 39 additions & 9 deletions .env.example
Original file line number Diff line number Diff line change
Expand Up @@ -13,9 +13,9 @@ ANTHROPIC_API_KEY=sk-ant-...
# Apps reference this as NEXT_PUBLIC_CONVEX_URL
CONVEX_URL=https://your-deployment.convex.cloud

# Agency gateway authentication token
# SquadHub gateway authentication token
# Auto-generated by scripts/start.sh, or set your own secure random string
AGENCY_TOKEN=your-secure-token-here
SQUADHUB_TOKEN=your-secure-token-here

# =============================================================================
# OPTIONAL
Expand All @@ -27,16 +27,46 @@ AGENCY_TOKEN=your-secure-token-here
# Environment: dev or prod
ENVIRONMENT=dev

# Agency gateway URL
# SquadHub gateway URL
# Development: http://localhost:18790 (Docker exposed on host)
# Production: http://agency:18789 (Docker internal network)
AGENCY_URL=http://localhost:18790
# Production: http://squadhub:18789 (Docker internal network)
SQUADHUB_URL=http://localhost:18790

# =============================================================================
# AUTHENTICATION
# =============================================================================

# Authentication provider: "nextauth" (local/self-hosted) or "cognito" (cloud)
# NextAuth uses auto-login with committed dev keys — zero config for local dev.
AUTH_PROVIDER=nextauth

# NextAuth settings (only when AUTH_PROVIDER=nextauth)
NEXTAUTH_SECRET=clawe-dev-secret-change-in-production
NEXTAUTH_URL=http://localhost:3000

# Google OAuth credentials (for NextAuth Google provider)
# Required for self-hosted deployments. Get from Google Cloud Console.
# GOOGLE_CLIENT_ID=your-client-id.apps.googleusercontent.com
# GOOGLE_CLIENT_SECRET=your-client-secret

# Auto-login email for local dev (skips Google OAuth, uses Credentials provider)
# Remove or leave empty to require Google sign-in
AUTO_LOGIN_EMAIL=dev@clawe.local

# AWS Cognito settings (only when AUTH_PROVIDER=cognito)
# COGNITO_USER_POOL_ID=us-east-1_xxxxxxxxx
# COGNITO_CLIENT_ID=xxxxxxxxxxxxxxxxxxxxxxxxxx
# COGNITO_DOMAIN=your-domain.auth.us-east-1.amazoncognito.com

# =============================================================================
# ADVANCED (usually don't need to change)
# =============================================================================

# Agency state directory (path to config dir, mounted from Docker)
# Development: ./.agency/config (relative to project root)
# Production: /agency-data/config (shared Docker volume)
AGENCY_STATE_DIR=./.agency/config
# SquadHub state directory (path to config dir, mounted from Docker)
# Development: ./.squadhub/config (relative to project root)
# Production: /squadhub-data/config (shared Docker volume)
SQUADHUB_STATE_DIR=./.squadhub/config

# Watcher system token (must also be set as Convex env var)
# Used by the watcher service for system-level auth (tenants.listActive)
WATCHER_TOKEN=clawe-watcher-dev-token
14 changes: 9 additions & 5 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -40,17 +40,21 @@ yarn-error.log*
# Misc
.DS_Store
*.pem
!packages/backend/convex/dev-jwks/*.pem

# Claude Code local settings (personal permissions)
.claude/settings.local.json

convex/_generated

# auth.config.ts is committed as the NextAuth version (default for local dev).
# scripts/convex-deploy.sh overwrites it for cloud deployments.

# Local data directory (Clawe config)
.data/

# Agency state directory (shared with Docker in dev)
.agency/*
!.agency/.gitkeep
.agency/logs/*
!.agency/logs/.gitkeep
# SquadHub state directory (shared with Docker in dev)
.squadhub/*
!.squadhub/.gitkeep
.squadhub/logs/*
!.squadhub/logs/.gitkeep
2 changes: 1 addition & 1 deletion .prettierignore
Original file line number Diff line number Diff line change
@@ -1,2 +1,2 @@
# Template files with shell variable substitution
docker/agency/templates/*.json
docker/squadhub/templates/*.json
File renamed without changes.
6 changes: 3 additions & 3 deletions CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -54,9 +54,9 @@ Core models: `agents`, `tasks`, `messages` (see `packages/backend/convex/schema.
## Environment Variables

- `NEXT_PUBLIC_CONVEX_URL`: Convex deployment URL (required)
- `ANTHROPIC_API_KEY`: Anthropic API key for AI operations (required, passed to agency)
- `AGENCY_TOKEN`: Authentication token for agency gateway (required)
- `AGENCY_URL`: Agency gateway URL (set in `.env.development` / `.env.production`)
- `ANTHROPIC_API_KEY`: Anthropic API key for AI operations (required, passed to squadhub)
- `SQUADHUB_TOKEN`: Authentication token for squadhub gateway (required)
- `SQUADHUB_URL`: SquadHub gateway URL (set in `.env.development` / `.env.production`)
- `NODE_ENV`: local (`development`) vs deployed (`production`) — controls dev tooling
- `ENVIRONMENT`: deployment target (`dev` / `prod`) — controls feature flags

Expand Down
24 changes: 12 additions & 12 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,7 @@ Edit `.env`:
```bash
# Required
ANTHROPIC_API_KEY=sk-ant-...
AGENCY_TOKEN=your-secure-token
SQUADHUB_TOKEN=your-secure-token
CONVEX_URL=https://your-deployment.convex.cloud

# Optional
Expand All @@ -71,15 +71,15 @@ npx convex deploy
This script will:

- Create `.env` from `.env.example` if missing
- Auto-generate a secure `AGENCY_TOKEN`
- Auto-generate a secure `SQUADHUB_TOKEN`
- Validate all required environment variables
- Build necessary packages
- Start the Docker containers

**Development:**

```bash
# Start agency gateway only (use local web dev server)
# Start squadhub gateway only (use local web dev server)
pnpm dev:docker

# In another terminal, start web + Convex
Expand All @@ -88,7 +88,7 @@ pnpm dev

The production stack starts:

- **agency**: Gateway running all agents
- **squadhub**: Gateway running all agents
- **watcher**: Notification delivery + cron setup
- **clawe**: Web dashboard at http://localhost:3000

Expand Down Expand Up @@ -120,7 +120,7 @@ Schedule recurring tasks that automatically create inbox items:
┌─────────────────────────────────────────────────────────────┐
│ DOCKER COMPOSE │
├─────────────────┬─────────────────────┬─────────────────────┤
agency │ watcher │ clawe │
squadhub │ watcher │ clawe │
│ │ │ │
│ Agent Gateway │ • Register agents │ Web Dashboard │
│ with 4 agents │ • Setup crons │ • Squad status │
Expand Down Expand Up @@ -151,10 +151,10 @@ clawe/
├── packages/
│ ├── backend/ # Convex schema & functions
│ ├── cli/ # `clawe` CLI for agents
│ ├── shared/ # Shared agency client
│ ├── shared/ # Shared squadhub client
│ └── ui/ # UI components
└── docker/
└── agency/
└── squadhub/
├── Dockerfile
├── entrypoint.sh
├── scripts/ # init-agents.sh
Expand Down Expand Up @@ -221,8 +221,8 @@ Each agent has an isolated workspace with:

### Adding New Agents

1. Create workspace template in `docker/agency/templates/workspaces/{name}/`
2. Add agent to `docker/agency/templates/config.template.json`
1. Create workspace template in `docker/squadhub/templates/workspaces/{name}/`
2. Add agent to `docker/squadhub/templates/config.template.json`
3. Add agent to watcher's `AGENTS` array in `apps/watcher/src/index.ts`
4. Rebuild: `docker compose build && docker compose up -d`

Expand Down Expand Up @@ -252,13 +252,13 @@ pnpm install
# Terminal 1: Start Convex dev server
pnpm convex:dev

# Terminal 2: Start agency gateway in Docker
# Terminal 2: Start squadhub gateway in Docker
pnpm dev:docker

# Terminal 3: Start web dashboard
pnpm dev:web

# Or run everything together (Convex + web, but not agency)
# Or run everything together (Convex + web, but not squadhub)
pnpm dev
```

Expand All @@ -284,6 +284,6 @@ pnpm convex:deploy
| Variable | Required | Description |
| ------------------- | -------- | --------------------------------- |
| `ANTHROPIC_API_KEY` | Yes | Anthropic API key for Claude |
| `AGENCY_TOKEN` | Yes | Auth token for agency gateway |
| `SQUADHUB_TOKEN` | Yes | Auth token for squadhub gateway |
| `CONVEX_URL` | Yes | Convex deployment URL |
| `OPENAI_API_KEY` | No | OpenAI key (for image generation) |
3 changes: 1 addition & 2 deletions apps/watcher/.env.example
Original file line number Diff line number Diff line change
Expand Up @@ -4,5 +4,4 @@

# Required variables (from root .env):
# - CONVEX_URL
# - AGENCY_URL
# - AGENCY_TOKEN
# - WATCHER_TOKEN
2 changes: 1 addition & 1 deletion apps/watcher/Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ WORKDIR /app
# Watcher
COPY apps/watcher/dist/ ./dist/

# Shared package (agency client)
# Shared package (squadhub client)
COPY packages/shared/package.json ./node_modules/@clawe/shared/package.json
COPY packages/shared/dist/ ./node_modules/@clawe/shared/dist/

Expand Down
25 changes: 10 additions & 15 deletions apps/watcher/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,22 +4,17 @@ Coordination watcher for Clawe multi-agent system.

## What It Does

1. **On startup:** Registers all agents in Convex (upsert)
2. **On startup:** Ensures heartbeat crons are configured for all agents
3. **Continuously:** Polls Convex for undelivered notifications and delivers them
1. **Continuously:** Polls Convex for undelivered notifications and delivers them
2. **Continuously:** Checks for due routines and triggers them

This enables:

- Automatic agent heartbeat scheduling (no manual cron setup needed)
- Near-instant notification delivery without waiting for heartbeats
Tenant connection info (squadhub URL/token) comes from Convex via `tenants.listActive`.

## Environment Variables

| Variable | Required | Description |
| -------------- | -------- | --------------------------- |
| `CONVEX_URL` | Yes | Convex deployment URL |
| `AGENCY_URL` | Yes | Agency gateway URL |
| `AGENCY_TOKEN` | Yes | Agency authentication token |
| Variable | Required | Description |
| --------------- | -------- | ---------------------------------------------- |
| `CONVEX_URL` | Yes | Convex deployment URL |
| `WATCHER_TOKEN` | Yes | System-level token for querying active tenants |

## Running

Expand Down Expand Up @@ -53,7 +48,7 @@ Schedules are staggered to avoid rate limits.
│ │
│ ┌─────────────┐ │
│ │ On Startup │──> Check/create heartbeat crons │
│ └─────────────┘ via agency cron API │
│ └─────────────┘ via squadhub cron API │
│ │
│ ┌─────────────┐ ┌─────────────────────────┐ │
│ │ Poll Loop │───────>│ convex.query( │ │
Expand All @@ -62,13 +57,13 @@ Schedules are staggered to avoid rate limits.
│ │ └─────────────────────────┘ │
│ │ │
│ │ ┌─────────────────────────┐ │
│ └──────────────>│ agency.sessionsSend() │ │
│ └──────────────>│ squadhub.sessionsSend() │ │
│ └─────────────────────────┘ │
└─────────────────────────────────────────────────────────┘
│ │
▼ ▼
┌───────────┐ ┌───────────────┐
│ CONVEX │ │ AGENCY
│ CONVEX │ │ SQUADHUB
│ (data) │ │ (delivery) │
└───────────┘ └───────────────┘
```
21 changes: 8 additions & 13 deletions apps/watcher/src/config.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,8 +15,7 @@ describe("config", () => {
describe("validateEnv", () => {
it("exits when CONVEX_URL is missing", async () => {
delete process.env.CONVEX_URL;
process.env.AGENCY_URL = "http://localhost:18789";
process.env.AGENCY_TOKEN = "test-token";
process.env.WATCHER_TOKEN = "test-token";

const mockExit = vi
.spyOn(process, "exit")
Expand All @@ -35,10 +34,9 @@ describe("config", () => {
mockError.mockRestore();
});

it("exits when AGENCY_URL is missing", async () => {
it("exits when WATCHER_TOKEN is missing", async () => {
process.env.CONVEX_URL = "https://test.convex.cloud";
delete process.env.AGENCY_URL;
process.env.AGENCY_TOKEN = "test-token";
delete process.env.WATCHER_TOKEN;

const mockExit = vi
.spyOn(process, "exit")
Expand All @@ -49,7 +47,7 @@ describe("config", () => {
validateEnv();

expect(mockError).toHaveBeenCalledWith(
expect.stringContaining("AGENCY_URL"),
expect.stringContaining("WATCHER_TOKEN"),
);
expect(mockExit).toHaveBeenCalledWith(1);

Expand All @@ -59,8 +57,7 @@ describe("config", () => {

it("does not exit when all required vars are set", async () => {
process.env.CONVEX_URL = "https://test.convex.cloud";
process.env.AGENCY_URL = "http://localhost:18789";
process.env.AGENCY_TOKEN = "test-token";
process.env.WATCHER_TOKEN = "test-token";

const mockExit = vi
.spyOn(process, "exit")
Expand All @@ -76,16 +73,14 @@ describe("config", () => {
});

describe("config object", () => {
it("has correct default values", async () => {
it("has correct values from env", async () => {
process.env.CONVEX_URL = "https://test.convex.cloud";
process.env.AGENCY_URL = "http://custom:8080";
process.env.AGENCY_TOKEN = "my-token";
process.env.WATCHER_TOKEN = "my-watcher-token";

const { config } = await import("./config.js");

expect(config.convexUrl).toBe("https://test.convex.cloud");
expect(config.agencyUrl).toBe("http://custom:8080");
expect(config.agencyToken).toBe("my-token");
expect(config.watcherToken).toBe("my-watcher-token");
});
});

Expand Down
5 changes: 2 additions & 3 deletions apps/watcher/src/config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ export const POLL_INTERVAL_MS = 2000; // Check every 2 seconds

// Environment validation
export function validateEnv(): void {
const required = ["CONVEX_URL", "AGENCY_URL", "AGENCY_TOKEN"];
const required = ["CONVEX_URL", "WATCHER_TOKEN"];
const missing = required.filter((key) => !process.env[key]);

if (missing.length > 0) {
Expand All @@ -17,7 +17,6 @@ export function validateEnv(): void {

export const config = {
convexUrl: process.env.CONVEX_URL || "",
agencyUrl: process.env.AGENCY_URL || "http://localhost:18789",
agencyToken: process.env.AGENCY_TOKEN || "",
watcherToken: process.env.WATCHER_TOKEN || "",
pollIntervalMs: POLL_INTERVAL_MS,
};
Loading