# NanoClaw

Multi-tenant AI agent hosting platform. Single Node.js process managing multiple tenants, each with isolated Discord channels, agent containers, workspaces, and resource limits.

## Quick Context

One NanoClaw instance, one Discord bot, multiple tenants. Messages route through tenant-scoped queues to Claude Agent SDK containers (Docker). Each tenant gets isolated groups, sessions, IPC, and configurable resource limits.

## Architecture

```
Discord bot (platform-owned)
  -> message received in channel
  -> registered_groups lookup (channel -> tenant + group)
  -> tenant status check (active/suspended)
  -> GroupQueue (per-tenant concurrency limits)
  -> Docker container (per-tenant resource flags: --memory, --cpus)
  -> agent response -> Discord channel
```

## Key Files

| File | Purpose |
|------|---------|
| `src/index.ts` | Orchestrator: state, message loop, tenant-aware routing |
| `src/tenant-manager.ts` | Tenant lifecycle: load configs, secrets, assistant names |
| `src/db.ts` | SQLite: tenants table, tenant_id on all tables, CRUD |
| `src/group-queue.ts` | Per-tenant + global concurrency limits |
| `src/container-runner.ts` | Spawns containers with tenant resource limits and secrets |
| `src/group-folder.ts` | Composite `tenant/folder` path validation and resolution |
| `src/ipc.ts` | IPC watcher with nested tenant/folder scanning, cross-tenant auth |
| `src/channels/registry.ts` | Channel registry (self-registration at startup) |
| `src/channels/discord.ts` | Discord channel: single bot, all tenants |
| `src/router.ts` | Message formatting and outbound routing |
| `src/config.ts` | Platform-level config: trigger pattern, paths, intervals |
| `src/task-scheduler.ts` | Scheduled tasks with tenant context |
| `src/types.ts` | Core types: Tenant, RegisteredGroup, Channel, etc. |

## Data Model

```
Filesystem (tenant-scoped):
  groups/<tenant>/<folder>/          # Per-group workspace + CLAUDE.md memory
  data/sessions/<tenant>/<folder>/   # Claude SDK sessions
  data/ipc/<tenant>/<folder>/        # IPC: input/, messages/, tasks/
  store/messages.db                  # Single DB for all tenants

Database tables:
  tenants              # id, name, resource limits, API key, status
  registered_groups    # jid -> tenant_id, folder (composite: tenant/name)
  chats                # tenant_id for metadata
  messages             # tenant_id for content
  sessions             # tenant_id for Claude sessions
  scheduled_tasks      # tenant_id for task ownership
```

## Multi-Tenancy

- **Tenant isolation**: separate filesystem paths, per-tenant container resource limits, cross-tenant IPC blocked
- **Resource limits**: per-tenant max_concurrent containers, max_memory_mb, cpu_quota (enforced via Docker flags)
- **Secrets**: tenant can have own API key (anthropic_key in tenants table) or use platform pooled key
- **Assistant name**: per-tenant customizable, falls back to platform ASSISTANT_NAME
- **Migration**: existing single-tenant data auto-migrates to "default" tenant on first startup

## Skills

| Skill | When to Use |
|-------|-------------|
| `/setup` | First-time installation, authentication, service configuration |
| `/customize` | Adding channels, integrations, changing behavior |
| `/debug` | Container issues, logs, troubleshooting |
| `/update-nanoclaw` | Bring upstream NanoClaw updates into a customized install |

## Development

```bash
npm run dev          # Run with hot reload
npm run build        # Compile TypeScript
npm test             # Run test suite (357 tests)
./container/build.sh # Rebuild agent container
```

Service management (treehouse):
```bash
sudo systemctl start nanoclaw-testclient
sudo systemctl stop nanoclaw-testclient
sudo systemctl restart nanoclaw-testclient
sudo journalctl -u nanoclaw-testclient -f  # tail logs
```

## Migration

To migrate an existing single-tenant install to multi-tenant:
```bash
# 1. Stop the service
# 2. Run filesystem migration
./scripts/migrate-to-multitenant.sh /path/to/workdir default
# 3. Restart — DB migration happens automatically
```

## Container Build Cache

The container buildkit caches the build context aggressively. `--no-cache` alone does NOT invalidate COPY steps — the builder's volume retains stale files. To force a truly clean rebuild, prune the builder then re-run `./container/build.sh`.
