gadget/docs/gadget-tasks.md
Rob Colbert 4d8f403d78 docs: add gadget-tasks documentation, update README and agent-toolbox
- README.md: Added gadget-tasks and @gadget/ai-toolbox to projects table,
  added Scheduled Tasks architecture section with diagram, updated
  monorepo structure, added gadget-tasks to dev server instructions,
  added doc link

- docs/agent-toolbox.md: Updated to reflect @gadget/ai-toolbox extraction
  from @gadget/ai. Changed IAiEnvironment → GadgetToolboxEnvironment,
  AiTool → GadgetTool, updated import paths, config examples, and added
  Tool Categories section. Clarified gadget-tasks is NOT a consumer.

- docs/gadget-tasks.md: New documentation covering architecture, per-task
  execution flow, configuration, startup/shutdown sequences, concurrency,
  work order tracking, heartbeat, error recovery, and getting started.
2026-05-17 01:30:35 -04:00

221 lines
9.6 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# gadget-tasks Documentation
## Overview
gadget-tasks is the Gadget Code **scheduled task worker** — a headless IDE client that automates the browser IDE flow on a cron schedule. It drives the gadget-code platform via REST API and Socket.IO, using the exact same protocol the browser IDE uses, but without any UI.
**Key design principle:** gadget-tasks contains **zero duplicated code**. No Mongoose models, no AI API calls, no prompt templates, no workspace management. All of that flows through the gadget-code platform, which delegates to drones for execution.
## Architecture
```
┌──────────────────┐ ┌──────────────────┐ ┌──────────────────┐
│ gadget-tasks │────▶│ gadget-code │────▶│ gadget-drone │
│ (Headless IDE) │◀────│ (Platform) │◀────│ (AWL Worker) │
└──────────────────┘ └──────────────────┘ └──────────────────┘
│ │ │
│ REST API │ MongoDB │ Files
│ Socket.IO │ Redis │ Git
│ JWT Auth │ Socket.IO relay │ AI API
```
gadget-tasks acts as a **programmatic IDE user**:
- Authenticates with the platform (same as logging into the browser IDE)
- Selects a drone (same as clicking a drone in the UI)
- Creates chat sessions, locks drones, submits prompts (same as typing in the chat)
- Waits for work order completion (same as watching the streaming response)
- Updates task records when done
## Per-Task Execution Flow
When a CronJob fires for a scheduled task:
1. **Create ChatSession**`POST /api/v1/chat-sessions`
2. **Lock drone** — Socket.IO `requestSessionLock(drone, project, session)`
3. **Set workspace mode** — Socket.IO `requestWorkspaceMode(drone, project, session, "agent")`
4. **Submit prompt** — Socket.IO `submitPrompt(task.content)` → creates ChatTurn with canonical system prompt → routes work order to drone
5. **Wait for completion** — Socket.IO receives `workOrderComplete(turnId, success, message)`
6. **Release drone lock** — Socket.IO `releaseSessionLock(drone, project, session)`
7. **Update task.lastRun**`PATCH /api/v1/projects/:id/tasks/:taskId/lastRun`
Steps 26 use the **same Socket.IO protocol** the browser IDE uses. gadget-code builds the system prompt from its canonical templates, routes the work order to the drone, and persists all results as ChatTurn records — identical to interactive use.
## Source Structure
```
gadget-tasks/
├── gadget-tasks.yaml # YAML configuration
├── package.json
├── tsconfig.json
└── src/
├── gadget-tasks.ts # Main entry — startup, shutdown, drone selection
├── config/
│ └── env.ts # Config loader — platform.baseUrl, redis, concurrency
├── lib/
│ ├── process.ts # GadgetProcess base class
│ └── service.ts # GadgetService base class
└── services/
├── platform.ts # REST API + Socket.IO headless IDE client
├── scheduler.ts # CronJob management, concurrency control
└── lock.ts # Redis singleton lock (prevents duplicate instances)
```
### Key Services
| Service | File | Purpose |
|---------|------|---------|
| **PlatformService** | `src/services/platform.ts` | REST API client (auth, projects, sessions, drones) + Socket.IO client (session lock, workspace mode, prompt submission, work order tracking) |
| **SchedulerService** | `src/services/scheduler.ts` | Creates CronJobs from task crontab expressions, enforces concurrency limit, delegates to PlatformService |
| **TaskLockService** | `src/services/lock.ts` | Redis-based singleton lock — prevents multiple gadget-tasks instances from running simultaneously |
## Configuration
### gadget-tasks.yaml
```yaml
timezone: America/New_York
platform:
baseUrl: https://code-dev.g4dge7.com:5174
redis:
host: localhost
port: 6379
# password: optional
# keyPrefix: defaults to "gadget:"
concurrency: 1
logging:
console:
enabled: true
file:
enabled: true
path: ~/logs/gadget-tasks
# name: defaults to "gadget-tasks"
# maxWritesPerFile: defaults to 10000
# maxFiles: defaults to 10
```
| Field | Required | Default | Description |
|-------|----------|---------|-------------|
| `timezone` | No | `America/New_York` | Timezone for CronJob scheduling |
| `platform.baseUrl` | **Yes** | — | URL of the gadget-code platform |
| `redis.host` | No | `localhost` | Redis host for singleton lock |
| `redis.port` | No | `6379` | Redis port |
| `redis.password` | No | — | Redis password |
| `redis.keyPrefix` | No | `gadget:` | Redis key prefix |
| `concurrency` | No | `1` | Max concurrent tasks (sequential by default) |
| `logging.console.enabled` | No | `false` | Enable console logging |
| `logging.file.enabled` | No | `false` | Enable file logging |
| `logging.file.path` | No | `./logs` | Log file directory |
### Credentials
Email and password are **not stored in the config file**. They are provided at startup:
- **CLI args:** `--user=admin@example.com --password=secret`
- **Interactive prompt:** If no CLI args, you'll be prompted (same pattern as gadget-drone)
This avoids storing credentials in config files that may be checked into version control.
## Startup Sequence
```
1. hookProcessSignals() — SIGINT handler
2. Acquire Redis singleton lock — exits if another instance is running
3. Get user credentials — CLI args or interactive prompt
4. Authenticate with platform — POST /api/v1/auth/sign-in → JWT
5. Select a drone — auto-select if only one, otherwise interactive list
6. Connect Socket.IO — JWT auth, websocket transport
7. Start scheduler — empty initially
8. Fetch projects — GET /api/v1/projects
9. Schedule enabled tasks — create CronJob for each task's crontab
10. Start heartbeat — 19s interval, prevents drone timeout
```
## Shutdown Sequence
```
1. Stop all CronJobs — SchedulerService.stop()
2. Disconnect Socket.IO — reject pending work orders
3. Release Redis lock — allow another instance to start
```
## Getting Started
### Prerequisites
- gadget-code platform running (backend + frontend)
- At least one gadget-drone registered and online
- Redis running on localhost:6379 (or as configured)
- A user account on the platform with projects that have tasks defined
### Running
```bash
# From the monorepo root
cd gadget-tasks
# Development mode (TypeScript, auto-restart)
pnpm dev -- --user=admin@example.com --password=secret
# Or with interactive credential prompt
pnpm dev
# Production (build first)
pnpm build
pnpm start -- --user=admin@example.com --password=secret
```
### Global CLI
```bash
# Link globally from the monorepo root
pnpm link:global
# Now available as a system command
gadget-tasks --user=admin@example.com --password=secret
```
## Task Execution Details
### Concurrency
By default, `concurrency: 1` means tasks execute **sequentially**. If a task fires while the drone is busy with another task, the new task waits for the drone to become available. This is the correct model — drones process one work order at a time.
If you increase `concurrency`, you need multiple drones available. Each task requires a drone lock, and a drone can only be locked to one session at a time.
### Work Order Tracking
When `submitPrompt` is called, the callback provides a `turnId`. gadget-tasks stores a Promise resolver in a `pendingWorkOrders` map keyed by `turnId`. When the server emits `workOrderComplete(turnId, success, message)`, the corresponding Promise is resolved, unblocking the task execution.
### Session Heartbeat
gadget-tasks sends a `sessionHeartbeat` every 19 seconds (same interval as the browser IDE). This prevents the drone's 120-second heartbeat timeout from firing while a task is active.
### Error Recovery
If gadget-tasks crashes while a task is being processed:
- The drone continues processing the work order independently
- The session and ChatTurn records are preserved in the database
- On restart, gadget-tasks creates **new** sessions for tasks that fire
- Old sessions are visible in the browser IDE via their ChatTurn records
No special crash recovery is needed in gadget-tasks (unlike gadget-drone, which has crash recovery for incomplete work orders).
## What gadget-tasks Does NOT Do
| ❌ Does NOT | ✅ Instead |
|------------|----------|
| Connect to MongoDB | Uses REST API to read/write data |
| Define Mongoose models | Uses `@gadget/api` TypeScript interfaces |
| Call AI APIs | Submits prompts through the platform → drone |
| Build system prompts | gadget-code builds prompts from canonical templates |
| Execute tools | The drone executes tools via `@gadget/ai-toolbox` |
| Manage workspaces | The drone manages workspace directories |
| Store credentials in config | Provides them at startup via CLI or prompt |
## Related Documentation
- [README](../README.md) — Project overview and quick start
- [Agent Toolbox](./agent-toolbox.md) — `@gadget/ai-toolbox` tool implementations (used by drones, not gadget-tasks)
- [Socket Protocol](./socket-protocol.md) — Socket.IO protocol between IDE, platform, and drone
- [Drone Documentation](../gadget-drone/docs/gadget-drone.md) — How drones execute work orders