MCP server for sequential task execution via FIFO queue
Project description
Agent Task Queue
Local task queuing for AI agents. Prevents multiple agents from running expensive operations concurrently and thrashing your machine.
The Problem
When multiple AI agents work on the same machine, they independently trigger expensive operations. Running these concurrently causes:
- 5-minute builds stretching to 30+ minutes
- Memory thrashing and disk I/O saturation
- Machine unresponsiveness
- Agents unable to coordinate with each other
How It Works
Default: Global queue - All run_task calls share one queue.
# Agent A runs:
run_task("./gradlew test", working_directory="/project")
# Agent B runs (waits for A to finish, then executes):
run_task("./gradlew build", working_directory="/project")
Custom queues - Use queue_name to isolate workloads:
# These run in separate queues (can run in parallel):
run_task("./gradlew build", queue_name="android", ...)
run_task("npm run build", queue_name="web", ...)
Both agents block until their respective builds complete. The server handles sequencing automatically.
Demo: Two Agents, One Build Queue
Terminal A - First agent requests an Android build:
> Build the Android app
⏺ agent-task-queue - run_task (MCP)
command: "./gradlew assembleDebug"
working_directory: "/path/to/android-project"
⎿ "SUCCESS exit=0 192.6s output=/tmp/agent-task-queue/output/task_1.log"
⏺ Build completed successfully in 192.6s.
Terminal B - Second agent requests the same build (started 2 seconds after A):
> Build the Android app
⏺ agent-task-queue - run_task (MCP)
command: "./gradlew assembleDebug"
working_directory: "/path/to/android-project"
⎿ "SUCCESS exit=0 32.6s output=/tmp/agent-task-queue/output/task_2.log"
⏺ Build completed successfully in 32.6s.
What happened behind the scenes:
| Time | Agent A | Agent B |
|---|---|---|
| 0:00 | Started build | |
| 0:02 | Building... | Entered queue, waiting |
| 3:12 | Completed (192.6s) | Started build |
| 3:45 | Completed (32.6s) |
Why this matters:
Without the queue, both builds would run simultaneously—fighting for CPU, memory, and disk I/O. Each build might take 5+ minutes, and your machine would be unresponsive.
With the queue:
- Agent B automatically waited for Agent A to finish
- Agent B's build was 6x faster (32s vs 193s) because Gradle reused cached artifacts
- Total time: 3:45 instead of 10+ minutes of thrashing
- Your machine stayed responsive throughout
Key Features
- FIFO Queuing: Strict first-in-first-out ordering
- No Queue Timeouts: MCP keeps connection alive while waiting in queue. The
timeout_secondsparameter only applies to execution time—tasks can wait in queue indefinitely without timing out. (see Why MCP?) - Environment Variables: Pass
env_vars="ANDROID_SERIAL=emulator-5560" - Multiple Queues: Isolate different workloads with
queue_name - Zombie Protection: Detects dead processes, kills orphans, clears stale locks
- Auto-Kill: Tasks running > 120 minutes are terminated
Installation
uvx agent-task-queue@latest
That's it. uvx runs the package directly from PyPI—no clone, no install, no virtual environment.
Agent Configuration
Agent Task Queue works with any AI coding tool that supports MCP. Add this config to your MCP client:
{
"mcpServers": {
"agent-task-queue": {
"command": "uvx",
"args": ["agent-task-queue@latest"]
}
}
}
MCP Client Configuration
Amp
Install via CLI:
amp mcp add agent-task-queue -- uvx agent-task-queue@latest
Or add to .amp/settings.json (workspace) or global settings. See Amp Manual for details.
Claude Desktop
Config file locations:
- macOS:
~/Library/Application Support/Claude/claude_desktop_config.json - Linux:
~/.config/Claude/claude_desktop_config.json
Use the standard config above.
Cline
Open the MCP Servers panel > Configure > "Configure MCP Servers" to edit cline_mcp_settings.json. Use the standard config above.
See Cline MCP docs for details.
Copilot / VS Code
Requires VS Code 1.102+ with GitHub Copilot Chat extension.
Config file locations:
- Workspace:
.vscode/mcp.json - Global: Via Command Palette > "MCP: Open User Configuration"
{
"servers": {
"agent-task-queue": {
"type": "stdio",
"command": "uvx",
"args": ["agent-task-queue@latest"]
}
}
}
See VS Code MCP docs for details.
Cursor
Go to Cursor Settings > MCP > + Add new global MCP server. Use the standard config above.
Config file locations:
- Global:
~/.cursor/mcp.json - Project:
.cursor/mcp.json
See Cursor MCP docs for details.
Firebender
Add to firebender.json in project root, or use Plugin Settings > MCP section. Use the standard config above.
See Firebender MCP docs for details.
Windsurf
Config file location: ~/.codeium/windsurf/mcp_config.json
Or use Windsurf Settings > Cascade > Manage MCPs. Use the standard config above.
See Windsurf MCP docs for details.
Usage
The run_task tool is automatically used by agents for heavy operations:
Build Tools: gradle, gradlew, bazel, make, cmake, mvn, cargo build, go build, npm/yarn/pnpm build
Container Operations: docker build, docker-compose, podman, kubectl, helm
Test Suites: pytest, jest, mocha, rspec
Tool Parameters
| Parameter | Required | Description |
|---|---|---|
command |
Yes | Shell command to execute |
working_directory |
Yes | Absolute path to run from |
queue_name |
No | Queue identifier (default: "global") |
timeout_seconds |
No | Max execution time before kill (default: 1200). Queue wait time doesn't count. |
env_vars |
No | Environment variables: "KEY=val,KEY2=val2" |
Example
run_task(
command="./gradlew connectedAndroidTest",
working_directory="/project",
queue_name="android",
env_vars="ANDROID_SERIAL=emulator-5560"
)
Note for Claude Code Users
Claude Code defaults to its built-in Bash tool for shell commands. To ensure it uses run_task instead, add instructions to your CLAUDE.md file.
Add to ~/.claude/CLAUDE.md (global) or .claude/CLAUDE.md (project-specific):
## Build Queue
For expensive build commands, ALWAYS use the `run_task` MCP tool instead of Bash.
**Commands that MUST use run_task:**
- gradle, gradlew, ./gradlew (any Gradle command)
- bazel, bazelisk
- docker build, docker-compose
- npm run build, yarn build, pnpm build
- pytest, jest, mocha
**How to use:**
- command: The full shell command
- working_directory: Absolute path to the project root
- env_vars: Environment variables like "ANDROID_SERIAL=emulator-5560"
NEVER run these commands directly via Bash. Always use the run_task MCP tool to prevent resource contention.
[!NOTE] Other agents like Amp may automatically use MCP tools without additional configuration.
Configuration
The server supports the following command-line options:
| Option | Default | Description |
|---|---|---|
--data-dir |
/tmp/agent-task-queue |
Directory for database and logs |
--max-log-size |
5 |
Max metrics log size in MB before rotation |
--max-output-files |
50 |
Number of task output files to retain |
--tail-lines |
50 |
Lines of output to include on failure |
--lock-timeout |
120 |
Minutes before stale locks are cleared |
Pass options via the args property in your MCP config:
{
"mcpServers": {
"agent-task-queue": {
"command": "uvx",
"args": [
"agent-task-queue@latest",
"--max-output-files=100",
"--lock-timeout=60"
]
}
}
}
Run uvx agent-task-queue@latest --help to see all options.
Architecture
flowchart TD
A[AI Agent<br/>Claude, Cursor, Windsurf, etc.] -->|MCP Protocol| B[task_queue.py<br/>FastMCP Server]
B -->|Query/Update| C[(SQLite Queue<br/>/tmp/agent-task-queue/queue.db)]
B -->|Execute| D[Subprocess<br/>gradle, docker, etc.]
D -.->|stdout/stderr| B
B -.->|blocks until complete| A
Data Directory
All data is stored in /tmp/agent-task-queue/ by default:
queue.db- SQLite database for queue stateagent-task-queue-logs.json- JSON metrics log (NDJSON format)
To use a different location, pass --data-dir=/path/to/data or set the TASK_QUEUE_DATA_DIR environment variable.
Database Schema
The queue state is stored in SQLite at /tmp/agent-task-queue/queue.db:
| Column | Type | Description |
|---|---|---|
id |
INTEGER | Auto-incrementing primary key |
queue_name |
TEXT | Queue identifier (e.g., "global", "android") |
status |
TEXT | Task state: "waiting" or "running" |
pid |
INTEGER | MCP server process ID (for liveness check) |
child_pid |
INTEGER | Subprocess ID (for orphan cleanup) |
created_at |
TIMESTAMP | When task was queued |
updated_at |
TIMESTAMP | Last status change |
Zombie Protection
If an agent crashes while a task is running:
- The next task detects the dead parent process (via PID check)
- It kills any orphaned child process (the actual build)
- It clears the stale lock
- Execution continues normally
Metrics Logging
All queue events are logged to agent-task-queue-logs.json in NDJSON format (one JSON object per line):
{"event":"task_queued","timestamp":"2025-12-12T16:01:34","task_id":8,"queue_name":"global","pid":23819}
{"event":"task_started","timestamp":"2025-12-12T16:01:34","task_id":8,"queue_name":"global","wait_time_seconds":0.0}
{"event":"task_completed","timestamp":"2025-12-12T16:02:05","task_id":8,"queue_name":"global","command":"./gradlew build","exit_code":0,"duration_seconds":31.2,"stdout_lines":45,"stderr_lines":2}
Events logged:
task_queued- Task entered the queuetask_started- Task acquired lock and began executiontask_completed- Task finished (includes exit code and duration)task_timeout- Task killed after timeouttask_error- Task failed with exceptionzombie_cleared- Stale lock was cleaned up
The log file rotates when it exceeds 5MB (keeps one backup as .json.1).
Task Output Logs
To reduce token usage, full command output is written to files instead of returned directly:
/tmp/agent-task-queue/output/
├── task_1.log
├── task_2.log
└── ...
On success, the tool returns a single line:
SUCCESS exit=0 31.2s output=/tmp/agent-task-queue/output/task_8.log
On failure, the last 50 lines of output are included:
FAILED exit=1 12.5s output=/tmp/agent-task-queue/output/task_9.log
[error output here]
Automatic cleanup: Old files are deleted when count exceeds 50 (configurable via MAX_OUTPUT_FILES).
Manual cleanup: Use the clear_task_logs tool to delete all output files.
Troubleshooting
"Database is locked" errors
The SQLite database uses WAL mode for concurrency. If you see lock errors:
ps aux | grep task_queue # Check for zombie processes
rm -rf /tmp/agent-task-queue/ # Delete and restart
Tasks stuck in queue
sqlite3 /tmp/agent-task-queue/queue.db "SELECT * FROM queue;" # Check status
sqlite3 /tmp/agent-task-queue/queue.db "DELETE FROM queue;" # Clear all
View metrics
cat /tmp/agent-task-queue/agent-task-queue-logs.json | jq . # Pretty print logs
tail -f /tmp/agent-task-queue/agent-task-queue-logs.json # Follow live
Server not connecting
- Ensure
uvxis in your PATH (install uv if needed) - Test manually:
uvx agent-task-queue@latest
Development
For contributors:
git clone https://github.com/block/agent-task-queue.git
cd agent-task-queue
uv sync # Install dependencies
uv run pytest -v # Run tests
uv run python task_queue.py # Run server locally
Platform Support
- macOS
- Linux
Why MCP Instead of a CLI Tool?
The first attempt at solving this problem was a file-based queue CLI that wrapped commands:
queue-cli ./gradlew build
The fatal flaw: AI tools have built-in shell timeouts (30s-120s). If a job waited in queue longer than the timeout, the agent gave up—even though the job would eventually run.
flowchart LR
subgraph cli [CLI Approach]
A1[Agent] --> B1[Shell]
B1 --> C1[CLI]
C1 --> D1[Queue]
B1 -.-> |"⏱️ TIMEOUT!"| A1
end
subgraph mcp [MCP Approach]
A2[Agent] --> |MCP Protocol| B2[Server]
B2 --> C2[Queue]
B2 -.-> |"✓ blocks until complete"| A2
end
Why MCP solves this:
- The MCP server keeps the connection alive indefinitely
- The agent's tool call blocks until the task completes
- No timeout configuration needed—it "just works"
- The server manages the queue; the agent just waits
| Aspect | CLI Wrapper | Agent Task Queue |
|---|---|---|
| Timeout handling | External workarounds | Solved by design |
| Queue storage | Filesystem | SQLite (WAL mode) |
| Integration | Wrap every command | Automatic tool selection |
| Agent compatibility | Varies by tool | Universal |
License
Apache 2.0
Project details
Release history Release notifications | RSS feed
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file agent_task_queue-0.1.2.tar.gz.
File metadata
- Download URL: agent_task_queue-0.1.2.tar.gz
- Upload date:
- Size: 25.1 kB
- Tags: Source
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.7
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
bcb884938fae84a8af794a79b942743b08d02c5736313cb9215fecc297f73001
|
|
| MD5 |
8e69fab46ceb87e6b62fff330de1e930
|
|
| BLAKE2b-256 |
2edc74c14cc732ac7c07bbffaef87f40e71b3bc9d7a0fff0163b8528fa0ac051
|
Provenance
The following attestation bundles were made for agent_task_queue-0.1.2.tar.gz:
Publisher:
release.yml on block/agent-task-queue
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
agent_task_queue-0.1.2.tar.gz -
Subject digest:
bcb884938fae84a8af794a79b942743b08d02c5736313cb9215fecc297f73001 - Sigstore transparency entry: 766084866
- Sigstore integration time:
-
Permalink:
block/agent-task-queue@fa40bc71f70d78fdbc0842dc64874cce2dd83d47 -
Branch / Tag:
refs/tags/v0.1.2 - Owner: https://github.com/block
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
release.yml@fa40bc71f70d78fdbc0842dc64874cce2dd83d47 -
Trigger Event:
release
-
Statement type:
File details
Details for the file agent_task_queue-0.1.2-py3-none-any.whl.
File metadata
- Download URL: agent_task_queue-0.1.2-py3-none-any.whl
- Upload date:
- Size: 17.4 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.7
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
cd97d421612ae5a2809c0e8d4d961e1242afb8be7587ca638a537314e3efccde
|
|
| MD5 |
d5b04bf515c40c4b411b9fc8b1b19821
|
|
| BLAKE2b-256 |
6f500944dfeb19afdd42acd7de9646639d2842568500a1b4535e9d2fc8696de4
|
Provenance
The following attestation bundles were made for agent_task_queue-0.1.2-py3-none-any.whl:
Publisher:
release.yml on block/agent-task-queue
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
agent_task_queue-0.1.2-py3-none-any.whl -
Subject digest:
cd97d421612ae5a2809c0e8d4d961e1242afb8be7587ca638a537314e3efccde - Sigstore transparency entry: 766084868
- Sigstore integration time:
-
Permalink:
block/agent-task-queue@fa40bc71f70d78fdbc0842dc64874cce2dd83d47 -
Branch / Tag:
refs/tags/v0.1.2 - Owner: https://github.com/block
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
release.yml@fa40bc71f70d78fdbc0842dc64874cce2dd83d47 -
Trigger Event:
release
-
Statement type: