Imported from team9ai/team9 (
AGENTS.md). Install upstream withnpx skills add team9ai/team9. Copyright stays with the author.
AGENTS.md
This file provides guidance to AI coding agents when working with code in this repository.
Project Overview
Team9 is a full-stack instant messaging and team collaboration platform built as a monorepo. The backend uses NestJS with PostgreSQL (Drizzle ORM), while the frontend is a Tauri-based cross-platform desktop app (React + TypeScript) with real-time WebSocket communication via Socket.io.
Common Commands
Development
pnpm dev # Run server (gateway + im-worker) and client concurrently
pnpm dev:client # Web frontend only (Vite dev server)
pnpm dev:desktop # Tauri desktop app (hot reload)
pnpm dev:server # Gateway service only
pnpm dev:im-worker # Background IM worker service only
pnpm dev:server:all # Both gateway and im-worker services
Database Operations
pnpm db:generate # Generate Drizzle schemas from TypeScript
pnpm db:migrate # Run pending migrations
pnpm db:push # Push schema changes to database (dev only)
pnpm db:studio # Open Drizzle Studio UI for database inspection
Building
pnpm build # Build both server and client
pnpm build:server # Build NestJS backend
pnpm build:client # Build web client
pnpm build:client:mac # Build macOS Tauri app
pnpm build:client:windows # Build Windows Tauri app
Production
pnpm start:prod # Start server in production mode
Architecture
Monorepo Structure
apps/
├── client/ # Tauri + React frontend
├── server/
│ ├── apps/
│ │ ├── gateway/ # Main API gateway (port 3000)
│ │ ├── im-worker/ # Background IM worker service (port 3001)
│ │ └── task-worker/ # Task execution worker service
│ └── libs/ # Shared libraries
│ ├── database/ # Drizzle schemas and DB module
│ ├── auth/ # Shared authentication
│ ├── ai-client/
│ ├── agent-framework/
│ ├── redis/
│ ├── rabbitmq/
│ └── shared/ # Common types, constants
└── debugger/ # Debug tool
Backend Architecture (NestJS)
Entry point: apps/server/apps/gateway/src/main.ts
The backend follows a modular NestJS architecture with two main applications:
- Gateway: Main API service with REST endpoints and WebSocket gateway
- IM Worker: Background service for async processing (message persistence, routing, offline delivery)
Key Modules:
- Auth Module (
apps/server/apps/gateway/src/auth): JWT-based authentication with Passport strategy, 7-day token expiry - IM Module (
apps/server/apps/gateway/src/im): Instant messaging functionality- Channels: direct, public, private types
- Messages: text, file, image, system types with threading support (parentId)
- Users: profile management, status tracking
- WebSocket: Socket.io gateway for real-time events
- Workspace Module (
apps/server/apps/gateway/src/workspace): Multi-tenant workspace management - Edition Module (
apps/server/apps/gateway/src/edition): Dynamic feature loading for Community vs Enterprise editions
Edition System:
The codebase supports Community and Enterprise editions via environment variable EDITION=community|enterprise. The Edition module conditionally loads features (e.g., TenantModule only in enterprise). Enterprise code lives in a separate git submodule at enterprise/.
Database Layer:
- Uses Drizzle ORM with PostgreSQL
- Schemas organized by domain in
apps/server/libs/database/schemas:- im/: users, channels, messages, channel_members, message_attachments, message_reactions, message_acks, mentions, user_channel_read_status
- tenant/: tenants, tenant_members, workspace_invitations
- All migrations managed via
pnpm db:migrate - Schema changes pushed via
pnpm db:push(dev) orpnpm db:generate+pnpm db:migrate(prod)
Frontend Architecture (Tauri + React)
Entry point: apps/client/src/main.tsx
State Management:
- Zustand for UI state: theme, user profile, loading states
- App store:
apps/client/src/stores/app.ts - Workspace store:
apps/client/src/stores/workspace.ts - Home store:
apps/client/src/stores/home.ts
- App store:
- TanStack React Query for server state: messages, channels, users (caching, invalidation)
- Local component state for UI-only interactions
Routing:
- TanStack Router with file-based routing in
apps/client/src/routes - Protected routes via
_authenticatedlayout - Automatic route generation from directory structure
HTTP Client:
- Custom HttpClient class at
apps/client/src/services/http.ts - Request/response interceptors for auth tokens and error handling
- Centralized API client at
apps/client/src/services/api.ts
WebSocket Service:
- Singleton pattern at
apps/client/src/services/websocket.ts - Auto-reconnection with exponential backoff
- Event queuing for offline operations
- Type-safe event emitters
- Channel join/leave lifecycle management
Real-Time Communication
WebSocket Events (Socket.io):
Message Operations:
new_message: Server broadcasts new messages to channel membersmark_as_read→read_status_updated: Read receipt trackingadd_reaction→reaction_added,reaction_removed: Message reactions
User Presence:
user_online,user_offline: Connection statususer_status_changed: Status updates (online/offline/away/busy)typing_start,typing_stop→user_typing: Typing indicators
Channel Management:
join_channel,leave_channel: Channel subscription lifecycle
Message Features:
- Threading via
parentIdfield - Mentions: @user, @channel, @everyone (parsed server-side)
- Attachments: file, image types
- Reactions: emoji-based reactions per message
- Read status: per-user, per-channel tracking via
user_channel_read_statustable
Key Development Patterns
Adding a New Database Table:
- Define schema in
apps/server/libs/database/schemasusing Drizzle syntax - Export from the appropriate index file (im/index.ts or tenant/index.ts)
- Run
pnpm db:generateto generate migration - Run
pnpm db:migrateto apply migration - Update database module to inject the new table
Adding a New API Endpoint:
- Create controller method in appropriate module (auth, im, workspace)
- Implement business logic in service layer
- Use Drizzle to query database via injected DatabaseService
- Add DTO classes for request/response validation
- Apply appropriate guards (JwtAuthGuard for protected routes)
Adding a New WebSocket Event:
- Define event handler in
apps/server/apps/gateway/src/im/websocket/websocket.gateway.ts - Emit response events via
this.server.to(channelId).emit(event, data) - Add client-side listener in
apps/client/src/services/websocket.ts - Update React Query cache or Zustand store based on event data
Adding a New Frontend Route:
- Create file in
apps/client/src/routesfollowing TanStack Router conventions - Use
_authenticatedlayout for protected routes - Define loader functions for data fetching
- Implement component with hooks for state/query management
External Dependencies
OpenClaw Hive
Team9 acts as a client of the OpenClaw Hive Control Plane API. The integration module lives at apps/server/apps/gateway/src/openclaw/:
- OpenclawService (
openclaw.service.ts): HTTP client that calls the Control Plane API to manage instances - OpenclawModule (
openclaw.module.ts): Global NestJS module exporting the service
Data flow:
- When a bot is created in Team9,
OpenclawServicecalls the Control Plane API (POST /api/instances) to provision an OpenClaw instance - Team9 passes
TEAM9_TOKENandTEAM9_BASE_URLas env vars so the OpenClaw instance can call back to Team9's IM APIs - The OpenClaw instance connects to Team9 via REST API and WebSocket (Socket.io) to send/receive messages
Environment variables (Team9 side):
OPENCLAW_API_URL: Control Plane base URL (e.g.,https://plane.claw.team9.ai)OPENCLAW_AUTH_TOKEN: Bearer token for authenticating with the Control Plane API
Control Plane API endpoints (called by Team9's OpenclawService):
| Method | Endpoint | Description |
|---|---|---|
| GET | /api/instances |
List all instances |
| GET | /api/instances/:id |
Get instance by ID |
| POST | /api/instances |
Create new instance ({id, subdomain?, env?}) |
| DELETE | /api/instances/:id |
Delete instance |
| POST | /api/instances/:id/start |
Start instance |
| POST | /api/instances/:id/stop |
Stop instance |
Authentication model:
- Team9 → Control Plane:
Authorization: Bearer <OPENCLAW_AUTH_TOKEN> - OpenClaw instance → Team9:
Authorization: Bearer <TEAM9_TOKEN>(JWT generated per bot)
Team9 REST API endpoints called by the OpenClaw plugin:
| Method | Endpoint | Description |
|---|---|---|
| GET | /api/v1/users/me |
Get bot's own user profile |
| GET | /api/v1/users/:userId |
Get user by ID |
| GET | /api/v1/im/channels |
List channels |
| GET | /api/v1/im/channels/:id |
Get channel by ID |
| POST | /api/v1/im/channels/dm/:targetUserId |
Get or create DM channel |
| GET | /api/v1/im/channels/:id/messages |
Get channel messages |
| POST | /api/v1/im/channels/:id/messages |
Send message (supports parentId for threads) |
| PATCH | /api/v1/im/messages/:id |
Update message |
| DELETE | /api/v1/im/messages/:id |
Delete message |
| POST | /api/v1/im/messages/:id/reactions |
Add reaction |
| DELETE | /api/v1/im/messages/:id/reactions/:emoji |
Remove reaction |
| POST | /api/v1/im/channels/:id/read |
Mark channel as read |
Important: When modifying Team9's IM APIs or WebSocket events, changes must stay compatible with the OpenClaw plugin.
TaskCast
Team9 acts as a client of a TaskCast server instance, using @taskcast/server-sdk to create tasks, transition statuses, and publish events. The frontend subscribes to task progress via SSE (Server-Sent Events) proxied through the Team9 gateway.
Integration points in Team9:
| Component | Path | Role |
|---|---|---|
TaskCastService |
apps/server/apps/gateway/src/tasks/taskcast.service.ts |
Creates TaskCast tasks, transitions status, publishes events |
TasksStreamController |
apps/server/apps/gateway/src/tasks/tasks-stream.controller.ts |
SSE proxy — authenticates user, verifies access, proxies upstream TaskCast SSE |
TaskCastClient |
apps/server/apps/task-worker/src/taskcast/taskcast.client.ts |
Creates TaskCast tasks from the worker service |
WebhookController |
apps/server/apps/task-worker/src/webhook/webhook.controller.ts |
Receives TaskCast timeout webhooks, updates execution/task status |
useExecutionStream |
apps/client/src/hooks/useExecutionStream.ts |
React hook — opens SSE to stream execution events |
Data flow:
- When a task execution starts,
TaskCastServicecreates a TaskCast task with deterministic IDagent_task_exec_${executionId} - During execution, events are published to TaskCast via
publishEvent() - The frontend opens an SSE connection through the gateway's proxy endpoint (
GET /api/v1/tasks/:taskId/executions/:execId/stream) - The gateway authenticates the user, verifies workspace membership, then proxies the SSE stream from TaskCast
- On task timeout, TaskCast calls the webhook endpoint (
POST /webhooks/taskcast/timeout), which updates the DB status
Environment variables (Team9 side):
TASKCAST_URL: TaskCast server base URL (default:http://localhost:3721)TASKCAST_WEBHOOK_SECRET: Shared secret for validating incoming TaskCast webhooks
Key concepts:
- Task lifecycle:
pending → running → completed|failed|timeout|cancelled(no backward transitions) - Deterministic IDs: Team9 uses
agent_task_exec_${executionId}pattern — no DB lookup needed for TaskCast task ID
aHand
Team9 integrates with aHand — a local execution gateway for cloud AI that lets cloud-side orchestrators run tools on local machines behind NAT/firewalls via WebSocket.
Architecture:
Cloud (WS server) ←── WebSocket (protobuf) ──→ Local daemon (WS client)
│ │
@ahand/sdk ahandd
(control plane) (job executor)
├─ shell / tools
├─ browser automation
└─ policy enforcement
- SDK (
@ahand/sdk): TypeScript cloud control plane SDK - Daemon (
ahandd): Rust binary enforcing local security policy before executing any job - Protocol: Protocol Buffers over WebSocket
Session modes enforced by the daemon:
| Mode | Behavior |
|---|---|
| Inactive | Default — rejects all jobs until activated |
| Strict | Every command requires manual approval |
| Trust | Auto-approve with inactivity timeout (default 60 min) |
| Auto-Accept | Auto-approve, no timeout |
Technology Stack
Frontend:
- React 19, TypeScript 5.8+, Tauri 2
- TanStack Router 1.141, TanStack React Query 5.90
- Zustand 5.0, Socket.io-client
- Radix UI, Tailwind CSS 4.1, Lucide icons
- Vite 7
Backend:
- NestJS 11, TypeScript 5.8+
- PostgreSQL + Drizzle ORM
- Socket.io, JWT + Passport
- Redis, RabbitMQ
- Anthropic AI SDK, Google Generative AI
Tooling:
- pnpm workspaces, ESLint, Prettier
- Jest (testing), SWC (compilation)
- Husky + lint-staged (pre-commit hooks)
Prerequisites
- Node.js >= 18.0.0
- pnpm >= 8.0.0
- Rust toolchain (for Tauri builds)
- PostgreSQL (local or remote)
- Redis (for caching/sessions)
- RabbitMQ (for message queuing)