# 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 2–6 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
