- 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.
9.6 KiB
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:
- Create ChatSession —
POST /api/v1/chat-sessions - Lock drone — Socket.IO
requestSessionLock(drone, project, session) - Set workspace mode — Socket.IO
requestWorkspaceMode(drone, project, session, "agent") - Submit prompt — Socket.IO
submitPrompt(task.content)→ creates ChatTurn with canonical system prompt → routes work order to drone - Wait for completion — Socket.IO receives
workOrderComplete(turnId, success, message) - Release drone lock — Socket.IO
releaseSessionLock(drone, project, session) - 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
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
# 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
# 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 — Project overview and quick start
- Agent Toolbox —
@gadget/ai-toolboxtool implementations (used by drones, not gadget-tasks) - Socket Protocol — Socket.IO protocol between IDE, platform, and drone
- Drone Documentation — How drones execute work orders