Skip to main content
Glama

jobd

jobd is a job broker with an MCP server that lets AI agents submit, route and babysit jobs on a few machines of your own. Like task-spooler or pueue, but across all your machines on Tailscale or a LAN, routing each job to a worker with the VRAM free, with no cloud, Kubernetes or Slurm.

CI PyPI Python License DOI Glama

# from any machine on your tailnet:
job submit --project myproj --gpu --vram-required 16 --wait -- python train.py
# → routed to whichever worker has ≥16 GB VRAM free, streamed back to your terminal

VRAM routing tracks one GPU per host: the worker reports free memory on GPU index 0 only.

Why it exists

  • VRAM-fit routing. Each job goes to a worker whose live free VRAM, RAM, CPUs, capability tags and arch/OS fit it, instead of you guessing which box is free.

  • Preemption with a checkpoint window. A higher-priority job can preempt a running one: SIGTERM, a grace window for the workload to save into its JOBD_CHECKPOINT_DIR, then SIGKILL. A preempted job is not re-run; to resume, submit a new job pointed at the checkpoint (docs/preemption.md).

  • Survives sessions. Jobs live in the broker, not your shell. Submit, close the laptop, check back tomorrow.

  • Agent-native. An MCP server lets an AI agent submit, monitor and babysit jobs as tool calls (benchmark).

  • Yours. One broker process on a machine you own. No accounts, no telemetry, no per-GPU-hour billing. Only the optional self-update scripts go online (runbook).

Related MCP server: jungle-grid-mcp-server

Why not just use…?

Tool

What it gives you

Why jobd instead

nohup / tmux / ssh

Runs a command on one box

No queue, VRAM-aware routing, preemption, or record of what ran where

task-spooler

A job queue on one machine

jobd queues across all your machines and routes by live VRAM/CPU fit

Pueue

A mature single-machine queue daemon

Pueue's README puts distributed execution out of scope; jobd is that layer, plus GPU awareness

HyperQueue

Multi-machine scheduling with HPC roots, single binary

Counts GPUs but doesn't track VRAM; no preemption/checkpoint contract or agent interface

Slurm

Datacenter-grade scheduling

Heavy to stand up and operate for 2–3 personal boxes

SkyPilot / dstack

Runs on clouds and your own machines

SkyPilot's "existing machines" mode installs Kubernetes (k3s) on your boxes; dstack wants Docker and passwordless sudo on every host

Modal

Serverless GPU compute

Runs on Modal's machines, not yours

Ray

Distributed compute; Ray Jobs runs any shell command on a Ray cluster

You first stand up and run a Ray cluster

Install

pip install jobd               # broker + CLI
pip install "jobd[mcp]"        # adds the MCP server
pip install "jobd[worker]"     # adds the worker daemon (jobd-worker)

Requires Python ≥ 3.11. scripts/install-worker.sh sets up a worker under ~/jobd-worker with its own venv and a generated config.

Quickstart (single host)

# 1. start the broker (binds 127.0.0.1:8765 by default)
JOBD_ALLOW_NO_AUTH=1 jobd          # no-auth is fine for a loopback-only broker

# 2. in another shell, install + start a worker pointed at it
pip install "jobd[worker]"
JOBD_URL=http://127.0.0.1:8765 JOBD_WORKER_HOST=local jobd-worker

# 3. submit a job and wait for it
job submit --project demo --wait -- echo hello
job list
job logs <id>

For a multi-host deployment (Docker broker, systemd workers, Tailscale binding, shared auth token), see docs/security.md and the templates in docker-compose.yml and scripts/. Adding a worker to a running fleet is one command:

job fleet add user@newbox      # ssh in, install pinned to the broker's version,
                               # wire systemd units + the self-update timer,
                               # verify it registers. `job fleet status` shows drift.

Day-2 operations (health, draining a worker, upgrades, token rotation, backups) are in docs/runbook.md.

How it works

The broker (FastAPI + SQLite) holds the queue, runs the matcher and resolves per-project priorities. Workers, one per host, poll it, so nothing connects in to a worker: each heartbeats its live capacity, claims jobs it can run, streams logs back and honors preemption. The argv you submit runs as-is (shell=False); job submit --stdin and the MCP jobd_submit tool are the exceptions and run a command string as bash -c <string>. Design notes: docs/ARCHITECTURE.md.

CLI

job submit -p PROJ [options] -- CMD...       # queue a job
job list [--state STATE] [--project P]       # queue + recent jobs
job status ID | A<id> [--watch]              # one job, or an array's aggregate
job logs ID [-n BYTES]                       # tail captured output
job wait ID                                  # block until terminal
job cancel ID  /  job preempt ID             # stop a job
job adopt --pid N -p PROJ [--gpu]            # track a process started outside jobd (Linux)
job workers                                  # fleet snapshot + health
job projects list | set NAME PRI | nudge NAME DELTA
job audit [--project P] [--since 24h]        # event history

Job arrays and sweeps, --explain dry runs, job adopt, and a pueue/task-spooler command map: docs/cli.md.

MCP / agent integration

jobd-mcp exposes the queue as nine tools: jobd_submit, jobd_status, jobd_logs, jobd_list, jobd_cancel, jobd_preempt, jobd_events, jobd_workers, jobd_worker_delete. docs/agent-cookbook.md walks through babysitting, preemption, sweeps, and asking why a job won't schedule.

For Claude Code:

claude mcp add jobd \
  --env JOBD_URL=http://127.0.0.1:8765 \
  --env JOBD_API_TOKEN=<your-token> \
  -- jobd-mcp

Any other MCP client:

{
  "mcpServers": {
    "jobd": {
      "command": "jobd-mcp",
      "env": {
        "JOBD_URL": "http://127.0.0.1:8765",
        "JOBD_API_TOKEN": "<your-token>"
      }
    }
  }
}

JOBD_API_TOKEN must match the broker's token, or every call returns 401. examples/claude-code-hooks/ has optional hooks that nudge (or hard-block) an agent toward submitting heavy commands through jobd.

What the benchmark shows

Claude with the server was compared with Claude using jobd's own job CLI, on 38 questions about a live broker, 3 runs each. With Claude Haiku 5.5 the server got all 38 right in every run and the CLI 29: a pre-registered win (Holm-adjusted McNemar p = 0.008). With Sonnet 5.5 it was 36 against 30 and with Opus 5.5 38 against 37, neither separated. On all three models the server's answers were cheaper and faster; with Sonnet, 0.20 times the cost and 0.17 times the time.

The chart is the Opus 5.5 run (2026-10-18). There the server's median answer cost 0.20 times as much (95% CI 0.15 to 0.28) and took 0.16 times as long (0.13 to 0.20), and it was cheaper and faster on all 38 questions. These were secondary endpoints, measured on an unreleased commit that adds a finished_at window to jobd_list. On an earlier, easier set both setups got 123 of 123.

We wrote the questions and ran the benchmark ourselves; an outside re-run is welcome. Method, controls and limitations: benchmarks/agent_qa; the Haiku and Sonnet runs: 2026-10-20 re-run.

Supported platforms

Component

Linux

macOS

Windows

Broker (jobd)

tested

expected

expected (WSL recommended)

CLI (job) / MCP (jobd-mcp)

tested

expected

expected

Worker (jobd-worker)

tested

degraded

untested

CI runs on Ubuntu with no GPU runner (VRAM paths are tested against mocks), and without a systemd user instance the worker still runs jobs but drops its memory caps, process reaping and preemption guarantees.

Configuration

Security

Run the broker on loopback or a Tailscale tailnet, never a public interface. Three stacked controls:

  1. Bind: JOBD_HOST is 127.0.0.1 by default; set it to a Tailscale address (100.64.0.0/10), never 0.0.0.0. The broker does not enforce this.

  2. Source IP: requests from outside loopback and 100.64.0.0/10 get 403 (JOBD_DISABLE_TAILNET_ACL=1 turns this off).

  3. Token: every host sets JOBD_API_TOKEN; JOBD_ALLOW_NO_AUTH=1 is for a loopback-only broker, and on any other bind exposes unauthenticated code execution to your tailnet.

/livez, /readyz and /metrics are exempt from both the source-IP check and the token; /metrics shows every worker's hostname and version, so don't port-forward the broker. Threat model and token rotation: docs/security.md.

License

MIT; see LICENSE.

Available Tools

9 tools
jobd_cancelB

Cancel a job (queued → cancelled; running → SIGTERM via worker signal poll, ~2s).

Inputs: job_id (required), reason.

jobd_eventsA

The broker's event stream — the surface that explains WHY, not just what. /jobs says a job is queued; only this says it has been skipped 400 times because no worker advertises cuda-32gb, or that its dependency was cancelled, or that a watchdog killed it. Filter by since (2h/3d/1w), event type, job_id, project, or source (broker|worker). Use when a job is not doing what you expect and jobd_status alone does not explain it.

Inputs: event, limit, since, job_id, +2 more.

jobd_listA

List jobs on the broker with per-state counts. Defaults to the active set (queued/assigned/running); filter by state (e.g. ['failed']), project, or a finished_at window (finished_after/finished_before) to find past runs. Each row is a compact summary: job_id, project, state, host, exit_code, queued_at, started_at, finished_at, duration_s — call jobd_status for a job's full record. Use to answer 'what is running / queued right now?', 'what failed overnight?', or to locate a job id you've lost.

Inputs: limit, state, project, finished_after, +1 more.

jobd_logsA

Tail the captured stdout/stderr of a job (workers stream output to the broker's per-job log as it runs). Returns log_tail (last tail_bytes, default 8 KiB, max 1 MiB) plus size_bytes/returned_bytes/truncated — works for running AND finished jobs. Use to check progress mid-run, diagnose a failure's traceback, or grab a job's final output.

Inputs: job_id (required), tail_bytes.

jobd_preemptA

Preempt a running/assigned preemptible job (worker SIGTERMs with grace; final state 'preempted'). Refused if not preemptible or not running.

Inputs: job_id (required).

jobd_statusA

The full JobInfo record for one job_id: state, exit_code, timings, host, and the scheduling internals — depends_on + cascade policy (depends_on_any_exit), pending cancel/preempt signal, resolved profile, requires (gpu/tags/idempotent), host pin, fast_path, timeouts, termination_reason. Use for a quick state check AND for debugging why a job routed/failed/stalled. Pass wait=true to block until terminal or wait_timeout_s.

Inputs: job_id (required), wait, wait_timeout_s.

jobd_submitA

Submit a job to the jobd broker. Default async; pass wait=true to block up to wait_timeout_s (server clamps to 270).

Inputs: cwd (required), command (required), project (required), gpu, +6 more.

jobd_worker_deleteA

Remove a worker from the broker registry. The broker refuses (409) if the worker is still online — caller stops the worker process or waits for the heartbeat sweeper first.

Inputs: host (required).

jobd_workersA

Fleet snapshot: every registered worker with state (online/stale/offline), live capacity ad (free_vram_gb, unregistered_vram_gb, free_ram_gb, idle_cpus), capability tags (cuda tiers, arch/os), slot usage (running/max_concurrent), and last_heartbeat — plus an overall health rollup (healthy|degraded|empty). Use before submitting GPU work to see what's free, or to diagnose why a job isn't being dispatched.

Inputs: no named parameters.

Tool Schema Changelog

Recent tool additions, removals, and schema changes observed during successful MCP inspections.

Loading tool schema changelog…

    TDQS

    A4/5.0

    Scored across 9 tools

    Disambiguation5/5

    Each tool targets a distinct resource+action: list (summary rows), status (full record), events (causal 'why'), logs (output tail), with descriptions explicitly cross-referencing each other to prevent confusion. Cancel vs preempt and workers vs worker_delete are cleanly bounded. The list/status/events/logs cluster all touch job data but their scopes and use cases are clearly differentiated.

    Naming Consistency4/5

    All names use a consistent jobd_ prefix with snake_case (jobd_worker_delete fits cleanly). The set mixes verb-first (list, cancel, preempt, submit) with noun-first (events, logs, status, workers), which is a minor deviation but reads predictably and clearly.

    Tool Count5/5

    Nine tools is well-scoped for a job broker: submit/query/cancel/preempt for jobs, plus events, logs, and worker fleet management. Each tool earns its place with no filler.

    Completeness4/5

    Core job lifecycle (submit, list, status, cancel, preempt) and diagnostic surfaces (events, logs) plus fleet visibility are well covered. Minor gaps exist—no explicit job retry/resubmit, worker registration, or queue pause/resume—but agents can work around these.

    Maintenance

    ActivityActive
    ResponsivenessLimited data

    Related MCP Connectors

    Related MCP Servers

    • A
      license
      B
      quality
      D
      maintenance
      agent-mq is a message queue that enables AI coding agents to communicate with each other across sessions and machines. Agents can send messages, delegate tasks, and coordinate work — all through MCP tools. Supports Claude Code, Cursor, Codex, OpenClaw, and any MCP-compatible tool. UUID-based authentication with per-user data isolation. Self-hostable with Docker.
      7
      2
      MIT
    • A
      license
      A
      quality
      D
      maintenance
      Jungle Grid MCP Server lets AI agents submit, estimate, monitor, and retrieve logs for GPU workloads through Jungle Grid. It enables agentic execution for inference, training, fine-tuning, and batch jobs without manually choosing GPU providers or infrastructure.
      8
      22 npm
      4
      MIT
    • F
      license
      Not graded
      quality
      A
      maintenance
      An MCP server for monitoring and managing multi-cluster Slurm GPU jobs, enabling AI agents to execute commands, check allocations, and explore logs across HPC clusters.
      1
      -