Repository files navigation

Queued Agents

A full-stack job queue dashboard for running LLM inference through Ollama with real-time GPU monitoring. Submit prompts, track job status, and observe GPU utilization, memory, temperature, and token throughput from a single UI.

Architecture

┌────────────┐ ┌──────────┐ ┌──────────┐ ┌────────┐
│ Frontend │────▶│ Backend │────▶│ SQLite │◀────│ Worker │
│ React/Nginx │ │ FastAPI │ │ (WAL) │ │ Python │
└────────────┘ └──────────┘ └──────────┘ └───┬────┘
▲ │
│ ▼
┌─────┴──────┐ ┌────────┐
│ GPU Monitor │ │ Ollama │
│ pynvml │ │ LLMs │
└─────────────┘ └────────┘

Five Docker services:

ServiceRolePort
frontendReact 19 + Vite + Tailwind, served via Nginx3001
backendFastAPI REST API8001
workerPolls for pending jobs, calls Ollama, writes results-
gpu-monitorPolls NVIDIA GPU metrics via pynvml, writes to DB-
ollamaLLM inference server11435

All Python services share a shared/ package containing the SQLAlchemy models, database engine, and config.

Prerequisites

Quick Start

# 1. Clone and configure
git clone git@github.com:chrisfauerbach/queuedagents.git
cd queuedagents
cp .env.example .env
# 2. Launch everything
docker compose up --build -d
# 3. Pull a model into Ollama
docker compose exec ollama ollama pull gemma3:12b
# 4. Open the dashboard
open http://localhost:3001

Configuration

Environment variables (set in .env):

VariableDefaultDescription
DATABASE_URLsqlite+aiosqlite:///./data/queue.dbSQLAlchemy async database URL
OLLAMA_HOSThttp://ollama:11434Ollama API base URL
POLL_INTERVAL1.0Worker job polling interval (seconds)
GPU_POLL_INTERVAL2.0GPU metrics polling interval (seconds)

API

All endpoints are prefixed with /api.

Jobs

MethodPathDescription
POST/api/jobsSubmit a new job
GET/api/jobsList jobs (query: status, limit, offset)
GET/api/jobs/:idGet a single job
GET/api/statsAggregate job status counts
GET/api/token-usage?hours=24Cumulative token usage per model (1-168 hour window)

GPU Metrics

MethodPathDescription
GET/api/gpu/metrics?minutes=10GPU time-series data (1-60 min window)

Comparisons

MethodPathDescription
POST/api/comparisonsCreate a comparison (runs same prompt across N models)
GET/api/comparisonsList all comparisons with their jobs
GET/api/comparisons/:idGet a single comparison with jobs

Models

MethodPathDescription
GET/api/modelsList available Ollama models
GET/api/models/catalogCurated model catalog with installed status
POST/api/models/pullPull/download a model (streams NDJSON progress)
POST/api/models/showGet detailed model info (license, family, quantization)
DELETE/api/modelsDelete a local model

Prompts

MethodPathDescription
POST/api/promptsSave a reusable prompt
GET/api/promptsList all saved prompts
GET/api/prompts/:idGet a single prompt
PUT/api/prompts/:idUpdate a prompt
DELETE/api/prompts/:idDelete a prompt

Leaderboard

MethodPathDescription
GET/api/leaderboardModel performance leaderboard with win rates

Health

MethodPathDescription
GET/api/healthReturns {"status": "ok"}

Job Lifecycle

  1. Submit a job via the dashboard or API with a model name, prompt, and optional parameters (system prompt, temperature, max tokens).
  2. The worker picks up the oldest pending job, marks it as processing, and sends it to Ollama.
  3. On completion, the worker records the result along with input tokens, output tokens, and generation time from the Ollama response.
  4. The dashboard polls for updates and displays status, results, and token throughput.

Model Management

The Models page (/models) provides a full model management interface:

  • Installed models table — Shows name, size, family, parameters, and quantization for all local models. Two-click delete confirmation.
  • Curated catalog — Browse ~15 model families across 6 categories (General Purpose, Code, Reasoning, Chat/Instruct, Small/Fast, Multilingual). Click a variant chip to pull it.
  • Real-time download progress — Pull streams NDJSON from Ollama with animated progress bars.
  • Custom pull — Text input for pulling any model by name (e.g. llama3.1:8b).

Model Comparison

The Compare feature (/compare) lets you run the same prompt against multiple models side-by-side. A comparison creates one job per selected model, all sharing the same prompt and parameters. Results are displayed in a side-by-side grid with per-model status, output, token counts, and generation speed. The detail page auto-refreshes until all jobs complete.

Prompt Library

The Prompts page (/prompts) lets you save reusable prompts with parameters (system prompt, temperature, max tokens). Saved prompts can be edited, deleted, run directly as a comparison against selected models, or sent to the Compare page pre-filled.

Model Leaderboard

The Leaderboard page (/leaderboard) ranks models by performance metrics including average tokens per second, generation time, total token usage, and comparison win rate.

Token Usage Tracking

The dashboard includes a cumulative token usage chart that tracks input and output tokens consumed per model over time. The chart:

  • Shows one line per model, each in a distinct color (16-color palette)
  • Displays cumulative total tokens on the Y-axis with auto-scaled labels (K/M suffixes)
  • Updates every 10 seconds via polling
  • Queries the last 24 hours of completed jobs by default

The data is derived from input_tokens and output_tokens already recorded on each completed job — no additional database tables are required.

GPU Monitoring

The gpu-monitor service reads metrics from NVIDIA GPUs every 2 seconds via pynvml:

  • GPU utilization %
  • Memory used / total (MB)
  • Temperature (Celsius)
  • Power draw (Watts)

Metrics older than 1 hour are automatically pruned. The dashboard renders a live SVG line chart showing utilization, memory %, and temperature over the selected time window.

Project Structure

queuedagents/
├── backend/ # FastAPI application
│ ├── app/
│ │ ├── main.py # App entrypoint, CORS, router mounting
│ │ ├── routes/
│ │ │ ├── jobs.py # Job CRUD + stats endpoints
│ │ │ ├── gpu.py # GPU metrics endpoint
│ │ │ ├── comparisons.py # Model comparison endpoints
│ │ │ ├── models.py # Model listing, catalog, pull, show, delete
│ │ │ ├── prompts.py # Prompt CRUD endpoints
│ │ │ └── leaderboard.py # Model leaderboard endpoint
│ │ ├── model_catalog.py # Curated model catalog data
│ │ └── schemas.py # Pydantic request/response models
│ ├── alembic/ # Database migrations
│ ├── Dockerfile
│ └── requirements.txt
├── worker/ # Job processing worker
│ ├── app/
│ │ ├── main.py # Polling loop, job claim/complete/fail
│ │ └── ollama_client.py # Ollama HTTP client
│ ├── Dockerfile
│ └── requirements.txt
├── gpu-monitor/ # GPU metrics collector
│ ├── app/
│ │ └── main.py # pynvml polling loop
│ ├── Dockerfile
│ └── requirements.txt
├── frontend/ # React SPA
│ ├── src/
│ │ ├── api/client.ts
│ │ ├── components/ # GpuChart, TokenChart, JobList, JobDetail, StatsCards, Layout, etc.
│ │ ├── hooks/ # usePolling
│ │ ├── pages/ # Dashboard, JobDetail, Prompts, Compare, ComparisonDetail, Leaderboard, Models
│ │ └── types/
│ ├── Dockerfile
│ └── nginx.conf
├── shared/ # Shared Python package
│ ├── config.py # Pydantic settings
│ ├── database.py # SQLAlchemy async engine + session
│ └── models.py # Job, Comparison, GpuMetric, Prompt ORM models
├── tests/ # 136 pytest tests (97% coverage)
│ ├── conftest.py # In-memory DB, session, and ASGI client fixtures
│ └── test_*.py # 16 test modules
├── requirements-test.txt
├── pytest.ini
├── docker-compose.yml
└── .env.example

Testing

The backend has a comprehensive test suite covering all Python services: backend (FastAPI routes), worker (job processing), gpu-monitor (metrics collection), and shared (models, config, database).

Running Tests

# One-time setup
ln -sf gpu-monitor gpu_monitor
pip install -r requirements-test.txt
# Run all 136 tests with coverage
pytest --cov --cov-report=term-missing -v
# Run a single test file
pytest tests/test_routes_jobs.py -v

What Gets Mocked

The test suite runs entirely offline — no Docker, no GPU, no Ollama needed. Three external systems are mocked out:

SystemMock StrategyWhy
SQLite databaseReplaced with an in-memory SQLite engine (sqlite+aiosqlite://). Each test gets a fresh database via function-scoped fixtures — tables are created before the test and dropped after.Eliminates filesystem I/O, prevents test pollution, runs in milliseconds.
Ollama HTTP APIIntercepted at the httpx transport layer using respx. Routes that call Ollama (/api/models, /api/models/catalog, /api/models/show, /api/models/pull, DELETE /api/models) and the worker's generate() function all get deterministic fake responses.Ollama requires a running server with downloaded models. Mocking lets us test every code path — success, HTTP errors, missing fields, timeouts — without a live inference server.
NVIDIA GPU driver (pynvml)The entire pynvml module is replaced via unittest.mock.patch with a MagicMock that returns SimpleNamespace objects mimicking real GPU handles, utilization rates, memory info, temperature, and power readings.The gpu-monitor service calls pynvml.nvmlDeviceGetHandleByIndex() and related C library bindings that require an NVIDIA GPU. Mocking lets us verify unit conversions (milliwatts → watts, bytes → megabytes), multi-GPU iteration, metric pruning, and error handling.

Test Structure

tests/
├── conftest.py # Shared fixtures (engine, session, FastAPI client)
├── test_shared_config.py # Settings defaults and env overrides
├── test_shared_models.py # ORM defaults, relationships, enums
├── test_shared_database.py # Engine, Base metadata, get_session
├── test_backend_main.py # Health endpoint, CORS, router registration
├── test_backend_schemas.py # Pydantic validation on all request schemas
├── test_backend_seed.py # Prompt seeding logic and idempotency
├── test_model_catalog.py # Catalog structure and data integrity
├── test_routes_jobs.py # Job CRUD, token-usage cumulative logic, stats
├── test_routes_comparisons.py # Comparison CRUD, set/clear winner validation
├── test_routes_gpu.py # GPU metrics query with time filtering
├── test_routes_models.py # Ollama proxy endpoints (respx mocks)
├── test_routes_leaderboard.py # TPS calculation, win rate, sorting
├── test_routes_prompts.py # Prompt CRUD with partial updates
├── test_worker_main.py # Job claim/complete/fail lifecycle, main loop
├── test_worker_ollama.py # generate() request/response handling
└── test_gpu_monitor.py # Record/prune metrics, main loop resilience

Development

For local frontend development with hot reload:

cd frontend
npm install
npm run dev

This starts Vite on port 3000 with API requests proxied to localhost:8000.

License

MIT

About

application to manage a backlog of requests to be processed in ollama.

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages

, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Add copy buttons to all
 blocks\n(function() {\n function addCopyButtons() {\n document.querySelectorAll('pre code').forEach(function(codeBlock) {\n if (codeBlock.parentElement.hasAttribute('data-copy-added')) return;\n codeBlock.parentElement.setAttribute('data-copy-added', 'true');\n \n var btn = document.createElement('button');\n btn.textContent = 'Copy';\n btn.style.cssText = 'position:absolute;top:4px;right:4px;padding:2px 8px;font-size:11px;background:#4ecdc4;border:none;border-radius:4px;color:#1a1a2e;cursor:pointer;opacity:0.7;transition:opacity 0.2s;';\n btn.onmouseover = function() { this.style.opacity = '1'; };\n btn.onmouseout = function() { this.style.opacity = '0.7'; };\n btn.onclick = function() {\n navigator.clipboard.writeText(codeBlock.textContent).then(function() {\n btn.textContent = 'Copied!';\n setTimeout(function() { btn.textContent = 'Copy'; }, 1500);\n });\n };\n codeBlock.parentElement.style.position = 'relative';\n codeBlock.parentElement.appendChild(btn);\n });\n }\n \n addCopyButtons();\n \n // Re-run on dynamic content\n var observer = new MutationObserver(addCopyButtons);\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Add Copy Buttons to Code Blocks");
}
} catch(__e) { console.warn('[Userscript:Add Copy Buttons to Code Blocks]', __e); }
})();
(function(){
try {
var __m = "github.com";
var __re = new RegExp('^' + "github\\.com" + '
Skip to content

Repository files navigation

Queued Agents

A full-stack job queue dashboard for running LLM inference through Ollama with real-time GPU monitoring. Submit prompts, track job status, and observe GPU utilization, memory, temperature, and token throughput from a single UI.

Architecture

┌────────────┐ ┌──────────┐ ┌──────────┐ ┌────────┐
│ Frontend │────▶│ Backend │────▶│ SQLite │◀────│ Worker │
│ React/Nginx │ │ FastAPI │ │ (WAL) │ │ Python │
└────────────┘ └──────────┘ └──────────┘ └───┬────┘
▲ │
│ ▼
┌─────┴──────┐ ┌────────┐
│ GPU Monitor │ │ Ollama │
│ pynvml │ │ LLMs │
└─────────────┘ └────────┘

Five Docker services:

ServiceRolePort
frontendReact 19 + Vite + Tailwind, served via Nginx3001
backendFastAPI REST API8001
workerPolls for pending jobs, calls Ollama, writes results-
gpu-monitorPolls NVIDIA GPU metrics via pynvml, writes to DB-
ollamaLLM inference server11435

All Python services share a shared/ package containing the SQLAlchemy models, database engine, and config.

Prerequisites

Quick Start

# 1. Clone and configure
git clone git@github.com:chrisfauerbach/queuedagents.git
cd queuedagents
cp .env.example .env
# 2. Launch everything
docker compose up --build -d
# 3. Pull a model into Ollama
docker compose exec ollama ollama pull gemma3:12b
# 4. Open the dashboard
open http://localhost:3001

Configuration

Environment variables (set in .env):

VariableDefaultDescription
DATABASE_URLsqlite+aiosqlite:///./data/queue.dbSQLAlchemy async database URL
OLLAMA_HOSThttp://ollama:11434Ollama API base URL
POLL_INTERVAL1.0Worker job polling interval (seconds)
GPU_POLL_INTERVAL2.0GPU metrics polling interval (seconds)

API

All endpoints are prefixed with /api.

Jobs

MethodPathDescription
POST/api/jobsSubmit a new job
GET/api/jobsList jobs (query: status, limit, offset)
GET/api/jobs/:idGet a single job
GET/api/statsAggregate job status counts
GET/api/token-usage?hours=24Cumulative token usage per model (1-168 hour window)

GPU Metrics

MethodPathDescription
GET/api/gpu/metrics?minutes=10GPU time-series data (1-60 min window)

Comparisons

MethodPathDescription
POST/api/comparisonsCreate a comparison (runs same prompt across N models)
GET/api/comparisonsList all comparisons with their jobs
GET/api/comparisons/:idGet a single comparison with jobs

Models

MethodPathDescription
GET/api/modelsList available Ollama models
GET/api/models/catalogCurated model catalog with installed status
POST/api/models/pullPull/download a model (streams NDJSON progress)
POST/api/models/showGet detailed model info (license, family, quantization)
DELETE/api/modelsDelete a local model

Prompts

MethodPathDescription
POST/api/promptsSave a reusable prompt
GET/api/promptsList all saved prompts
GET/api/prompts/:idGet a single prompt
PUT/api/prompts/:idUpdate a prompt
DELETE/api/prompts/:idDelete a prompt

Leaderboard

MethodPathDescription
GET/api/leaderboardModel performance leaderboard with win rates

Health

MethodPathDescription
GET/api/healthReturns {"status": "ok"}

Job Lifecycle

  1. Submit a job via the dashboard or API with a model name, prompt, and optional parameters (system prompt, temperature, max tokens).
  2. The worker picks up the oldest pending job, marks it as processing, and sends it to Ollama.
  3. On completion, the worker records the result along with input tokens, output tokens, and generation time from the Ollama response.
  4. The dashboard polls for updates and displays status, results, and token throughput.

Model Management

The Models page (/models) provides a full model management interface:

  • Installed models table — Shows name, size, family, parameters, and quantization for all local models. Two-click delete confirmation.
  • Curated catalog — Browse ~15 model families across 6 categories (General Purpose, Code, Reasoning, Chat/Instruct, Small/Fast, Multilingual). Click a variant chip to pull it.
  • Real-time download progress — Pull streams NDJSON from Ollama with animated progress bars.
  • Custom pull — Text input for pulling any model by name (e.g. llama3.1:8b).

Model Comparison

The Compare feature (/compare) lets you run the same prompt against multiple models side-by-side. A comparison creates one job per selected model, all sharing the same prompt and parameters. Results are displayed in a side-by-side grid with per-model status, output, token counts, and generation speed. The detail page auto-refreshes until all jobs complete.

Prompt Library

The Prompts page (/prompts) lets you save reusable prompts with parameters (system prompt, temperature, max tokens). Saved prompts can be edited, deleted, run directly as a comparison against selected models, or sent to the Compare page pre-filled.

Model Leaderboard

The Leaderboard page (/leaderboard) ranks models by performance metrics including average tokens per second, generation time, total token usage, and comparison win rate.

Token Usage Tracking

The dashboard includes a cumulative token usage chart that tracks input and output tokens consumed per model over time. The chart:

  • Shows one line per model, each in a distinct color (16-color palette)
  • Displays cumulative total tokens on the Y-axis with auto-scaled labels (K/M suffixes)
  • Updates every 10 seconds via polling
  • Queries the last 24 hours of completed jobs by default

The data is derived from input_tokens and output_tokens already recorded on each completed job — no additional database tables are required.

GPU Monitoring

The gpu-monitor service reads metrics from NVIDIA GPUs every 2 seconds via pynvml:

  • GPU utilization %
  • Memory used / total (MB)
  • Temperature (Celsius)
  • Power draw (Watts)

Metrics older than 1 hour are automatically pruned. The dashboard renders a live SVG line chart showing utilization, memory %, and temperature over the selected time window.

Project Structure

queuedagents/
├── backend/ # FastAPI application
│ ├── app/
│ │ ├── main.py # App entrypoint, CORS, router mounting
│ │ ├── routes/
│ │ │ ├── jobs.py # Job CRUD + stats endpoints
│ │ │ ├── gpu.py # GPU metrics endpoint
│ │ │ ├── comparisons.py # Model comparison endpoints
│ │ │ ├── models.py # Model listing, catalog, pull, show, delete
│ │ │ ├── prompts.py # Prompt CRUD endpoints
│ │ │ └── leaderboard.py # Model leaderboard endpoint
│ │ ├── model_catalog.py # Curated model catalog data
│ │ └── schemas.py # Pydantic request/response models
│ ├── alembic/ # Database migrations
│ ├── Dockerfile
│ └── requirements.txt
├── worker/ # Job processing worker
│ ├── app/
│ │ ├── main.py # Polling loop, job claim/complete/fail
│ │ └── ollama_client.py # Ollama HTTP client
│ ├── Dockerfile
│ └── requirements.txt
├── gpu-monitor/ # GPU metrics collector
│ ├── app/
│ │ └── main.py # pynvml polling loop
│ ├── Dockerfile
│ └── requirements.txt
├── frontend/ # React SPA
│ ├── src/
│ │ ├── api/client.ts
│ │ ├── components/ # GpuChart, TokenChart, JobList, JobDetail, StatsCards, Layout, etc.
│ │ ├── hooks/ # usePolling
│ │ ├── pages/ # Dashboard, JobDetail, Prompts, Compare, ComparisonDetail, Leaderboard, Models
│ │ └── types/
│ ├── Dockerfile
│ └── nginx.conf
├── shared/ # Shared Python package
│ ├── config.py # Pydantic settings
│ ├── database.py # SQLAlchemy async engine + session
│ └── models.py # Job, Comparison, GpuMetric, Prompt ORM models
├── tests/ # 136 pytest tests (97% coverage)
│ ├── conftest.py # In-memory DB, session, and ASGI client fixtures
│ └── test_*.py # 16 test modules
├── requirements-test.txt
├── pytest.ini
├── docker-compose.yml
└── .env.example

Testing

The backend has a comprehensive test suite covering all Python services: backend (FastAPI routes), worker (job processing), gpu-monitor (metrics collection), and shared (models, config, database).

Running Tests

# One-time setup
ln -sf gpu-monitor gpu_monitor
pip install -r requirements-test.txt
# Run all 136 tests with coverage
pytest --cov --cov-report=term-missing -v
# Run a single test file
pytest tests/test_routes_jobs.py -v

What Gets Mocked

The test suite runs entirely offline — no Docker, no GPU, no Ollama needed. Three external systems are mocked out:

SystemMock StrategyWhy
SQLite databaseReplaced with an in-memory SQLite engine (sqlite+aiosqlite://). Each test gets a fresh database via function-scoped fixtures — tables are created before the test and dropped after.Eliminates filesystem I/O, prevents test pollution, runs in milliseconds.
Ollama HTTP APIIntercepted at the httpx transport layer using respx. Routes that call Ollama (/api/models, /api/models/catalog, /api/models/show, /api/models/pull, DELETE /api/models) and the worker's generate() function all get deterministic fake responses.Ollama requires a running server with downloaded models. Mocking lets us test every code path — success, HTTP errors, missing fields, timeouts — without a live inference server.
NVIDIA GPU driver (pynvml)The entire pynvml module is replaced via unittest.mock.patch with a MagicMock that returns SimpleNamespace objects mimicking real GPU handles, utilization rates, memory info, temperature, and power readings.The gpu-monitor service calls pynvml.nvmlDeviceGetHandleByIndex() and related C library bindings that require an NVIDIA GPU. Mocking lets us verify unit conversions (milliwatts → watts, bytes → megabytes), multi-GPU iteration, metric pruning, and error handling.

Test Structure

tests/
├── conftest.py # Shared fixtures (engine, session, FastAPI client)
├── test_shared_config.py # Settings defaults and env overrides
├── test_shared_models.py # ORM defaults, relationships, enums
├── test_shared_database.py # Engine, Base metadata, get_session
├── test_backend_main.py # Health endpoint, CORS, router registration
├── test_backend_schemas.py # Pydantic validation on all request schemas
├── test_backend_seed.py # Prompt seeding logic and idempotency
├── test_model_catalog.py # Catalog structure and data integrity
├── test_routes_jobs.py # Job CRUD, token-usage cumulative logic, stats
├── test_routes_comparisons.py # Comparison CRUD, set/clear winner validation
├── test_routes_gpu.py # GPU metrics query with time filtering
├── test_routes_models.py # Ollama proxy endpoints (respx mocks)
├── test_routes_leaderboard.py # TPS calculation, win rate, sorting
├── test_routes_prompts.py # Prompt CRUD with partial updates
├── test_worker_main.py # Job claim/complete/fail lifecycle, main loop
├── test_worker_ollama.py # generate() request/response handling
└── test_gpu_monitor.py # Record/prune metrics, main loop resilience

Development

For local frontend development with hot reload:

cd frontend
npm install
npm run dev

This starts Vite on port 3000 with API requests proxied to localhost:8000.

License

MIT

About

application to manage a backlog of requests to be processed in ollama.

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages

, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Force GitHub README to respect dark mode\n(function() {\n var style = document.createElement('style');\n style.textContent = '\n .markdown-body {\n color-scheme: dark light;\n }\n .markdown-body pre { background: #161b22 !important; }\n .markdown-body code { background: rgba(110, 118, 129, 0.4) !important; }\n .markdown-body table th, .markdown-body table td { border-color: #30363d !important; }\n .markdown-body img { background: #0d1117; }\n .markdown-body blockquote { border-left-color: #8b949e; }\n .markdown-body hr { border-color: #30363d; }\n ';\n document.head.appendChild(style);\n})();", "GitHub Dark Mode README Fix"); } } catch(__e) { console.warn('[Userscript:GitHub Dark Mode README Fix]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

Repository files navigation

Queued Agents

A full-stack job queue dashboard for running LLM inference through Ollama with real-time GPU monitoring. Submit prompts, track job status, and observe GPU utilization, memory, temperature, and token throughput from a single UI.

Architecture

┌────────────┐ ┌──────────┐ ┌──────────┐ ┌────────┐
│ Frontend │────▶│ Backend │────▶│ SQLite │◀────│ Worker │
│ React/Nginx │ │ FastAPI │ │ (WAL) │ │ Python │
└────────────┘ └──────────┘ └──────────┘ └───┬────┘
▲ │
│ ▼
┌─────┴──────┐ ┌────────┐
│ GPU Monitor │ │ Ollama │
│ pynvml │ │ LLMs │
└─────────────┘ └────────┘

Five Docker services:

ServiceRolePort
frontendReact 19 + Vite + Tailwind, served via Nginx3001
backendFastAPI REST API8001
workerPolls for pending jobs, calls Ollama, writes results-
gpu-monitorPolls NVIDIA GPU metrics via pynvml, writes to DB-
ollamaLLM inference server11435

All Python services share a shared/ package containing the SQLAlchemy models, database engine, and config.

Prerequisites

Quick Start

# 1. Clone and configure
git clone git@github.com:chrisfauerbach/queuedagents.git
cd queuedagents
cp .env.example .env
# 2. Launch everything
docker compose up --build -d
# 3. Pull a model into Ollama
docker compose exec ollama ollama pull gemma3:12b
# 4. Open the dashboard
open http://localhost:3001

Configuration

Environment variables (set in .env):

VariableDefaultDescription
DATABASE_URLsqlite+aiosqlite:///./data/queue.dbSQLAlchemy async database URL
OLLAMA_HOSThttp://ollama:11434Ollama API base URL
POLL_INTERVAL1.0Worker job polling interval (seconds)
GPU_POLL_INTERVAL2.0GPU metrics polling interval (seconds)

API

All endpoints are prefixed with /api.

Jobs

MethodPathDescription
POST/api/jobsSubmit a new job
GET/api/jobsList jobs (query: status, limit, offset)
GET/api/jobs/:idGet a single job
GET/api/statsAggregate job status counts
GET/api/token-usage?hours=24Cumulative token usage per model (1-168 hour window)

GPU Metrics

MethodPathDescription
GET/api/gpu/metrics?minutes=10GPU time-series data (1-60 min window)

Comparisons

MethodPathDescription
POST/api/comparisonsCreate a comparison (runs same prompt across N models)
GET/api/comparisonsList all comparisons with their jobs
GET/api/comparisons/:idGet a single comparison with jobs

Models

MethodPathDescription
GET/api/modelsList available Ollama models
GET/api/models/catalogCurated model catalog with installed status
POST/api/models/pullPull/download a model (streams NDJSON progress)
POST/api/models/showGet detailed model info (license, family, quantization)
DELETE/api/modelsDelete a local model

Prompts

MethodPathDescription
POST/api/promptsSave a reusable prompt
GET/api/promptsList all saved prompts
GET/api/prompts/:idGet a single prompt
PUT/api/prompts/:idUpdate a prompt
DELETE/api/prompts/:idDelete a prompt

Leaderboard

MethodPathDescription
GET/api/leaderboardModel performance leaderboard with win rates

Health

MethodPathDescription
GET/api/healthReturns {"status": "ok"}

Job Lifecycle

  1. Submit a job via the dashboard or API with a model name, prompt, and optional parameters (system prompt, temperature, max tokens).
  2. The worker picks up the oldest pending job, marks it as processing, and sends it to Ollama.
  3. On completion, the worker records the result along with input tokens, output tokens, and generation time from the Ollama response.
  4. The dashboard polls for updates and displays status, results, and token throughput.

Model Management

The Models page (/models) provides a full model management interface:

  • Installed models table — Shows name, size, family, parameters, and quantization for all local models. Two-click delete confirmation.
  • Curated catalog — Browse ~15 model families across 6 categories (General Purpose, Code, Reasoning, Chat/Instruct, Small/Fast, Multilingual). Click a variant chip to pull it.
  • Real-time download progress — Pull streams NDJSON from Ollama with animated progress bars.
  • Custom pull — Text input for pulling any model by name (e.g. llama3.1:8b).

Model Comparison

The Compare feature (/compare) lets you run the same prompt against multiple models side-by-side. A comparison creates one job per selected model, all sharing the same prompt and parameters. Results are displayed in a side-by-side grid with per-model status, output, token counts, and generation speed. The detail page auto-refreshes until all jobs complete.

Prompt Library

The Prompts page (/prompts) lets you save reusable prompts with parameters (system prompt, temperature, max tokens). Saved prompts can be edited, deleted, run directly as a comparison against selected models, or sent to the Compare page pre-filled.

Model Leaderboard

The Leaderboard page (/leaderboard) ranks models by performance metrics including average tokens per second, generation time, total token usage, and comparison win rate.

Token Usage Tracking

The dashboard includes a cumulative token usage chart that tracks input and output tokens consumed per model over time. The chart:

  • Shows one line per model, each in a distinct color (16-color palette)
  • Displays cumulative total tokens on the Y-axis with auto-scaled labels (K/M suffixes)
  • Updates every 10 seconds via polling
  • Queries the last 24 hours of completed jobs by default

The data is derived from input_tokens and output_tokens already recorded on each completed job — no additional database tables are required.

GPU Monitoring

The gpu-monitor service reads metrics from NVIDIA GPUs every 2 seconds via pynvml:

  • GPU utilization %
  • Memory used / total (MB)
  • Temperature (Celsius)
  • Power draw (Watts)

Metrics older than 1 hour are automatically pruned. The dashboard renders a live SVG line chart showing utilization, memory %, and temperature over the selected time window.

Project Structure

queuedagents/
├── backend/ # FastAPI application
│ ├── app/
│ │ ├── main.py # App entrypoint, CORS, router mounting
│ │ ├── routes/
│ │ │ ├── jobs.py # Job CRUD + stats endpoints
│ │ │ ├── gpu.py # GPU metrics endpoint
│ │ │ ├── comparisons.py # Model comparison endpoints
│ │ │ ├── models.py # Model listing, catalog, pull, show, delete
│ │ │ ├── prompts.py # Prompt CRUD endpoints
│ │ │ └── leaderboard.py # Model leaderboard endpoint
│ │ ├── model_catalog.py # Curated model catalog data
│ │ └── schemas.py # Pydantic request/response models
│ ├── alembic/ # Database migrations
│ ├── Dockerfile
│ └── requirements.txt
├── worker/ # Job processing worker
│ ├── app/
│ │ ├── main.py # Polling loop, job claim/complete/fail
│ │ └── ollama_client.py # Ollama HTTP client
│ ├── Dockerfile
│ └── requirements.txt
├── gpu-monitor/ # GPU metrics collector
│ ├── app/
│ │ └── main.py # pynvml polling loop
│ ├── Dockerfile
│ └── requirements.txt
├── frontend/ # React SPA
│ ├── src/
│ │ ├── api/client.ts
│ │ ├── components/ # GpuChart, TokenChart, JobList, JobDetail, StatsCards, Layout, etc.
│ │ ├── hooks/ # usePolling
│ │ ├── pages/ # Dashboard, JobDetail, Prompts, Compare, ComparisonDetail, Leaderboard, Models
│ │ └── types/
│ ├── Dockerfile
│ └── nginx.conf
├── shared/ # Shared Python package
│ ├── config.py # Pydantic settings
│ ├── database.py # SQLAlchemy async engine + session
│ └── models.py # Job, Comparison, GpuMetric, Prompt ORM models
├── tests/ # 136 pytest tests (97% coverage)
│ ├── conftest.py # In-memory DB, session, and ASGI client fixtures
│ └── test_*.py # 16 test modules
├── requirements-test.txt
├── pytest.ini
├── docker-compose.yml
└── .env.example

Testing

The backend has a comprehensive test suite covering all Python services: backend (FastAPI routes), worker (job processing), gpu-monitor (metrics collection), and shared (models, config, database).

Running Tests

# One-time setup
ln -sf gpu-monitor gpu_monitor
pip install -r requirements-test.txt
# Run all 136 tests with coverage
pytest --cov --cov-report=term-missing -v
# Run a single test file
pytest tests/test_routes_jobs.py -v

What Gets Mocked

The test suite runs entirely offline — no Docker, no GPU, no Ollama needed. Three external systems are mocked out:

SystemMock StrategyWhy
SQLite databaseReplaced with an in-memory SQLite engine (sqlite+aiosqlite://). Each test gets a fresh database via function-scoped fixtures — tables are created before the test and dropped after.Eliminates filesystem I/O, prevents test pollution, runs in milliseconds.
Ollama HTTP APIIntercepted at the httpx transport layer using respx. Routes that call Ollama (/api/models, /api/models/catalog, /api/models/show, /api/models/pull, DELETE /api/models) and the worker's generate() function all get deterministic fake responses.Ollama requires a running server with downloaded models. Mocking lets us test every code path — success, HTTP errors, missing fields, timeouts — without a live inference server.
NVIDIA GPU driver (pynvml)The entire pynvml module is replaced via unittest.mock.patch with a MagicMock that returns SimpleNamespace objects mimicking real GPU handles, utilization rates, memory info, temperature, and power readings.The gpu-monitor service calls pynvml.nvmlDeviceGetHandleByIndex() and related C library bindings that require an NVIDIA GPU. Mocking lets us verify unit conversions (milliwatts → watts, bytes → megabytes), multi-GPU iteration, metric pruning, and error handling.

Test Structure

tests/
├── conftest.py # Shared fixtures (engine, session, FastAPI client)
├── test_shared_config.py # Settings defaults and env overrides
├── test_shared_models.py # ORM defaults, relationships, enums
├── test_shared_database.py # Engine, Base metadata, get_session
├── test_backend_main.py # Health endpoint, CORS, router registration
├── test_backend_schemas.py # Pydantic validation on all request schemas
├── test_backend_seed.py # Prompt seeding logic and idempotency
├── test_model_catalog.py # Catalog structure and data integrity
├── test_routes_jobs.py # Job CRUD, token-usage cumulative logic, stats
├── test_routes_comparisons.py # Comparison CRUD, set/clear winner validation
├── test_routes_gpu.py # GPU metrics query with time filtering
├── test_routes_models.py # Ollama proxy endpoints (respx mocks)
├── test_routes_leaderboard.py # TPS calculation, win rate, sorting
├── test_routes_prompts.py # Prompt CRUD with partial updates
├── test_worker_main.py # Job claim/complete/fail lifecycle, main loop
├── test_worker_ollama.py # generate() request/response handling
└── test_gpu_monitor.py # Record/prune metrics, main loop resilience

Development

For local frontend development with hot reload:

cd frontend
npm install
npm run dev

This starts Vite on port 3000 with API requests proxied to localhost:8000.

License

MIT

About

application to manage a backlog of requests to be processed in ollama.

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages

, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Highlight search terms from Google/DuckDuckGo/Bing referrer\n(function() {\n var ref = document.referrer;\n var terms = [];\n \n if (ref.includes('google.com') || ref.includes('duckduckgo.com') || ref.includes('bing.com')) {\n var url = new URL(ref);\n var q = url.searchParams.get('q') || url.searchParams.get('p');\n if (q) {\n terms = q.split(/\\s+/).filter(function(t) { return t.length > 2; });\n }\n }\n \n if (terms.length === 0) return;\n \n var style = document.createElement('style');\n style.textContent = '.userscript-highlight { background: #fbbf24; color: #1a1a2e; padding: 1px 3px; border-radius: 2px; }';\n document.head.appendChild(style);\n \n function highlight(node) {\n if (node.nodeType === 3) { // text node\n var text = node.textContent;\n var found = false;\n terms.forEach(function(term) {\n var regex = new RegExp('(' + term.replace(/[.*+?^${}()|[\\]\\\\]/g, '\\\\') + ')', 'gi');\n if (regex.test(text)) {\n found = true;\n var frag = document.createDocumentFragment();\n var parts = text.split(regex);\n parts.forEach(function(part, i) {\n if (i % 2 === 0) {\n frag.appendChild(document.createTextNode(part));\n } else {\n var span = document.createElement('span');\n span.className = 'userscript-highlight';\n span.textContent = part;\n frag.appendChild(span);\n }\n });\n node.parentNode.replaceChild(frag, node);\n }\n });\n } else if (node.nodeType === 1 && node.childNodes) { // element\n var skipTags = ['SCRIPT', 'STYLE', 'NOSCRIPT', 'TEXTAREA', 'INPUT', 'SELECT'];\n if (!skipTags.includes(node.tagName)) {\n Array.from(node.childNodes).forEach(highlight);\n }\n }\n }\n \n highlight(document.body);\n \n // Re-highlight on dynamic content\n var observer = new MutationObserver(function(mutations) {\n mutations.forEach(function(m) {\n m.addedNodes.forEach(function(node) {\n if (node.nodeType === 1 || node.nodeType === 3) highlight(node);\n });\n });\n });\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Highlight Search Terms"); } } catch(__e) { console.warn('[Userscript:Highlight Search Terms]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

Repository files navigation

Queued Agents

A full-stack job queue dashboard for running LLM inference through Ollama with real-time GPU monitoring. Submit prompts, track job status, and observe GPU utilization, memory, temperature, and token throughput from a single UI.

Architecture

┌────────────┐ ┌──────────┐ ┌──────────┐ ┌────────┐
│ Frontend │────▶│ Backend │────▶│ SQLite │◀────│ Worker │
│ React/Nginx │ │ FastAPI │ │ (WAL) │ │ Python │
└────────────┘ └──────────┘ └──────────┘ └───┬────┘
▲ │
│ ▼
┌─────┴──────┐ ┌────────┐
│ GPU Monitor │ │ Ollama │
│ pynvml │ │ LLMs │
└─────────────┘ └────────┘

Five Docker services:

ServiceRolePort
frontendReact 19 + Vite + Tailwind, served via Nginx3001
backendFastAPI REST API8001
workerPolls for pending jobs, calls Ollama, writes results-
gpu-monitorPolls NVIDIA GPU metrics via pynvml, writes to DB-
ollamaLLM inference server11435

All Python services share a shared/ package containing the SQLAlchemy models, database engine, and config.

Prerequisites

Quick Start

# 1. Clone and configure
git clone git@github.com:chrisfauerbach/queuedagents.git
cd queuedagents
cp .env.example .env
# 2. Launch everything
docker compose up --build -d
# 3. Pull a model into Ollama
docker compose exec ollama ollama pull gemma3:12b
# 4. Open the dashboard
open http://localhost:3001

Configuration

Environment variables (set in .env):

VariableDefaultDescription
DATABASE_URLsqlite+aiosqlite:///./data/queue.dbSQLAlchemy async database URL
OLLAMA_HOSThttp://ollama:11434Ollama API base URL
POLL_INTERVAL1.0Worker job polling interval (seconds)
GPU_POLL_INTERVAL2.0GPU metrics polling interval (seconds)

API

All endpoints are prefixed with /api.

Jobs

MethodPathDescription
POST/api/jobsSubmit a new job
GET/api/jobsList jobs (query: status, limit, offset)
GET/api/jobs/:idGet a single job
GET/api/statsAggregate job status counts
GET/api/token-usage?hours=24Cumulative token usage per model (1-168 hour window)

GPU Metrics

MethodPathDescription
GET/api/gpu/metrics?minutes=10GPU time-series data (1-60 min window)

Comparisons

MethodPathDescription
POST/api/comparisonsCreate a comparison (runs same prompt across N models)
GET/api/comparisonsList all comparisons with their jobs
GET/api/comparisons/:idGet a single comparison with jobs

Models

MethodPathDescription
GET/api/modelsList available Ollama models
GET/api/models/catalogCurated model catalog with installed status
POST/api/models/pullPull/download a model (streams NDJSON progress)
POST/api/models/showGet detailed model info (license, family, quantization)
DELETE/api/modelsDelete a local model

Prompts

MethodPathDescription
POST/api/promptsSave a reusable prompt
GET/api/promptsList all saved prompts
GET/api/prompts/:idGet a single prompt
PUT/api/prompts/:idUpdate a prompt
DELETE/api/prompts/:idDelete a prompt

Leaderboard

MethodPathDescription
GET/api/leaderboardModel performance leaderboard with win rates

Health

MethodPathDescription
GET/api/healthReturns {"status": "ok"}

Job Lifecycle

  1. Submit a job via the dashboard or API with a model name, prompt, and optional parameters (system prompt, temperature, max tokens).
  2. The worker picks up the oldest pending job, marks it as processing, and sends it to Ollama.
  3. On completion, the worker records the result along with input tokens, output tokens, and generation time from the Ollama response.
  4. The dashboard polls for updates and displays status, results, and token throughput.

Model Management

The Models page (/models) provides a full model management interface:

  • Installed models table — Shows name, size, family, parameters, and quantization for all local models. Two-click delete confirmation.
  • Curated catalog — Browse ~15 model families across 6 categories (General Purpose, Code, Reasoning, Chat/Instruct, Small/Fast, Multilingual). Click a variant chip to pull it.
  • Real-time download progress — Pull streams NDJSON from Ollama with animated progress bars.
  • Custom pull — Text input for pulling any model by name (e.g. llama3.1:8b).

Model Comparison

The Compare feature (/compare) lets you run the same prompt against multiple models side-by-side. A comparison creates one job per selected model, all sharing the same prompt and parameters. Results are displayed in a side-by-side grid with per-model status, output, token counts, and generation speed. The detail page auto-refreshes until all jobs complete.

Prompt Library

The Prompts page (/prompts) lets you save reusable prompts with parameters (system prompt, temperature, max tokens). Saved prompts can be edited, deleted, run directly as a comparison against selected models, or sent to the Compare page pre-filled.

Model Leaderboard

The Leaderboard page (/leaderboard) ranks models by performance metrics including average tokens per second, generation time, total token usage, and comparison win rate.

Token Usage Tracking

The dashboard includes a cumulative token usage chart that tracks input and output tokens consumed per model over time. The chart:

  • Shows one line per model, each in a distinct color (16-color palette)
  • Displays cumulative total tokens on the Y-axis with auto-scaled labels (K/M suffixes)
  • Updates every 10 seconds via polling
  • Queries the last 24 hours of completed jobs by default

The data is derived from input_tokens and output_tokens already recorded on each completed job — no additional database tables are required.

GPU Monitoring

The gpu-monitor service reads metrics from NVIDIA GPUs every 2 seconds via pynvml:

  • GPU utilization %
  • Memory used / total (MB)
  • Temperature (Celsius)
  • Power draw (Watts)

Metrics older than 1 hour are automatically pruned. The dashboard renders a live SVG line chart showing utilization, memory %, and temperature over the selected time window.

Project Structure

queuedagents/
├── backend/ # FastAPI application
│ ├── app/
│ │ ├── main.py # App entrypoint, CORS, router mounting
│ │ ├── routes/
│ │ │ ├── jobs.py # Job CRUD + stats endpoints
│ │ │ ├── gpu.py # GPU metrics endpoint
│ │ │ ├── comparisons.py # Model comparison endpoints
│ │ │ ├── models.py # Model listing, catalog, pull, show, delete
│ │ │ ├── prompts.py # Prompt CRUD endpoints
│ │ │ └── leaderboard.py # Model leaderboard endpoint
│ │ ├── model_catalog.py # Curated model catalog data
│ │ └── schemas.py # Pydantic request/response models
│ ├── alembic/ # Database migrations
│ ├── Dockerfile
│ └── requirements.txt
├── worker/ # Job processing worker
│ ├── app/
│ │ ├── main.py # Polling loop, job claim/complete/fail
│ │ └── ollama_client.py # Ollama HTTP client
│ ├── Dockerfile
│ └── requirements.txt
├── gpu-monitor/ # GPU metrics collector
│ ├── app/
│ │ └── main.py # pynvml polling loop
│ ├── Dockerfile
│ └── requirements.txt
├── frontend/ # React SPA
│ ├── src/
│ │ ├── api/client.ts
│ │ ├── components/ # GpuChart, TokenChart, JobList, JobDetail, StatsCards, Layout, etc.
│ │ ├── hooks/ # usePolling
│ │ ├── pages/ # Dashboard, JobDetail, Prompts, Compare, ComparisonDetail, Leaderboard, Models
│ │ └── types/
│ ├── Dockerfile
│ └── nginx.conf
├── shared/ # Shared Python package
│ ├── config.py # Pydantic settings
│ ├── database.py # SQLAlchemy async engine + session
│ └── models.py # Job, Comparison, GpuMetric, Prompt ORM models
├── tests/ # 136 pytest tests (97% coverage)
│ ├── conftest.py # In-memory DB, session, and ASGI client fixtures
│ └── test_*.py # 16 test modules
├── requirements-test.txt
├── pytest.ini
├── docker-compose.yml
└── .env.example

Testing

The backend has a comprehensive test suite covering all Python services: backend (FastAPI routes), worker (job processing), gpu-monitor (metrics collection), and shared (models, config, database).

Running Tests

# One-time setup
ln -sf gpu-monitor gpu_monitor
pip install -r requirements-test.txt
# Run all 136 tests with coverage
pytest --cov --cov-report=term-missing -v
# Run a single test file
pytest tests/test_routes_jobs.py -v

What Gets Mocked

The test suite runs entirely offline — no Docker, no GPU, no Ollama needed. Three external systems are mocked out:

SystemMock StrategyWhy
SQLite databaseReplaced with an in-memory SQLite engine (sqlite+aiosqlite://). Each test gets a fresh database via function-scoped fixtures — tables are created before the test and dropped after.Eliminates filesystem I/O, prevents test pollution, runs in milliseconds.
Ollama HTTP APIIntercepted at the httpx transport layer using respx. Routes that call Ollama (/api/models, /api/models/catalog, /api/models/show, /api/models/pull, DELETE /api/models) and the worker's generate() function all get deterministic fake responses.Ollama requires a running server with downloaded models. Mocking lets us test every code path — success, HTTP errors, missing fields, timeouts — without a live inference server.
NVIDIA GPU driver (pynvml)The entire pynvml module is replaced via unittest.mock.patch with a MagicMock that returns SimpleNamespace objects mimicking real GPU handles, utilization rates, memory info, temperature, and power readings.The gpu-monitor service calls pynvml.nvmlDeviceGetHandleByIndex() and related C library bindings that require an NVIDIA GPU. Mocking lets us verify unit conversions (milliwatts → watts, bytes → megabytes), multi-GPU iteration, metric pruning, and error handling.

Test Structure

tests/
├── conftest.py # Shared fixtures (engine, session, FastAPI client)
├── test_shared_config.py # Settings defaults and env overrides
├── test_shared_models.py # ORM defaults, relationships, enums
├── test_shared_database.py # Engine, Base metadata, get_session
├── test_backend_main.py # Health endpoint, CORS, router registration
├── test_backend_schemas.py # Pydantic validation on all request schemas
├── test_backend_seed.py # Prompt seeding logic and idempotency
├── test_model_catalog.py # Catalog structure and data integrity
├── test_routes_jobs.py # Job CRUD, token-usage cumulative logic, stats
├── test_routes_comparisons.py # Comparison CRUD, set/clear winner validation
├── test_routes_gpu.py # GPU metrics query with time filtering
├── test_routes_models.py # Ollama proxy endpoints (respx mocks)
├── test_routes_leaderboard.py # TPS calculation, win rate, sorting
├── test_routes_prompts.py # Prompt CRUD with partial updates
├── test_worker_main.py # Job claim/complete/fail lifecycle, main loop
├── test_worker_ollama.py # generate() request/response handling
└── test_gpu_monitor.py # Record/prune metrics, main loop resilience

Development

For local frontend development with hot reload:

cd frontend
npm install
npm run dev

This starts Vite on port 3000 with API requests proxied to localhost:8000.

License

MIT

About

application to manage a backlog of requests to be processed in ollama.

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages

, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Strip utm_, fbclid, gclid, etc. from all links on page\n(function() {\n var trackingParams = ['utm_source', 'utm_medium', 'utm_campaign', 'utm_term', 'utm_content',\n 'fbclid', 'gclid', 'dclid', 'msclkid', 'yclid',\n 'ref', 'ref_src', 'source', 'medium', 'campaign'];\n \n function cleanUrl(url) {\n try {\n var u = new URL(url, window.location.origin);\n var changed = false;\n trackingParams.forEach(function(p) {\n if (u.searchParams.has(p)) {\n u.searchParams.delete(p);\n changed = true;\n }\n });\n return changed ? u.toString() : url;\n } catch (e) {\n return url;\n }\n }\n \n function cleanLinks() {\n document.querySelectorAll('a[href]').forEach(function(a) {\n var clean = cleanUrl(a.href);\n if (clean !== a.href) a.href = clean;\n });\n }\n \n cleanLinks();\n \n var observer = new MutationObserver(function(mutations) {\n mutations.forEach(function(m) {\n m.addedNodes.forEach(function(node) {\n if (node.nodeType === 1) {\n if (node.tagName === 'A') cleanLinks();\n node.querySelectorAll('a[href]').forEach(function(a) {\n var clean = cleanUrl(a.href);\n if (clean !== a.href) a.href = clean;\n });\n }\n });\n });\n });\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Remove Tracking Parameters from Links"); } } catch(__e) { console.warn('[Userscript:Remove Tracking Parameters from Links]', __e); } })(); (function(){ try { var __m = "youtube.com"; var __re = new RegExp('^' + "youtube\\.com" + '
Skip to content

Repository files navigation

Queued Agents

A full-stack job queue dashboard for running LLM inference through Ollama with real-time GPU monitoring. Submit prompts, track job status, and observe GPU utilization, memory, temperature, and token throughput from a single UI.

Architecture

┌────────────┐ ┌──────────┐ ┌──────────┐ ┌────────┐
│ Frontend │────▶│ Backend │────▶│ SQLite │◀────│ Worker │
│ React/Nginx │ │ FastAPI │ │ (WAL) │ │ Python │
└────────────┘ └──────────┘ └──────────┘ └───┬────┘
▲ │
│ ▼
┌─────┴──────┐ ┌────────┐
│ GPU Monitor │ │ Ollama │
│ pynvml │ │ LLMs │
└─────────────┘ └────────┘

Five Docker services:

ServiceRolePort
frontendReact 19 + Vite + Tailwind, served via Nginx3001
backendFastAPI REST API8001
workerPolls for pending jobs, calls Ollama, writes results-
gpu-monitorPolls NVIDIA GPU metrics via pynvml, writes to DB-
ollamaLLM inference server11435

All Python services share a shared/ package containing the SQLAlchemy models, database engine, and config.

Prerequisites

Quick Start

# 1. Clone and configure
git clone git@github.com:chrisfauerbach/queuedagents.git
cd queuedagents
cp .env.example .env
# 2. Launch everything
docker compose up --build -d
# 3. Pull a model into Ollama
docker compose exec ollama ollama pull gemma3:12b
# 4. Open the dashboard
open http://localhost:3001

Configuration

Environment variables (set in .env):

VariableDefaultDescription
DATABASE_URLsqlite+aiosqlite:///./data/queue.dbSQLAlchemy async database URL
OLLAMA_HOSThttp://ollama:11434Ollama API base URL
POLL_INTERVAL1.0Worker job polling interval (seconds)
GPU_POLL_INTERVAL2.0GPU metrics polling interval (seconds)

API

All endpoints are prefixed with /api.

Jobs

MethodPathDescription
POST/api/jobsSubmit a new job
GET/api/jobsList jobs (query: status, limit, offset)
GET/api/jobs/:idGet a single job
GET/api/statsAggregate job status counts
GET/api/token-usage?hours=24Cumulative token usage per model (1-168 hour window)

GPU Metrics

MethodPathDescription
GET/api/gpu/metrics?minutes=10GPU time-series data (1-60 min window)

Comparisons

MethodPathDescription
POST/api/comparisonsCreate a comparison (runs same prompt across N models)
GET/api/comparisonsList all comparisons with their jobs
GET/api/comparisons/:idGet a single comparison with jobs

Models

MethodPathDescription
GET/api/modelsList available Ollama models
GET/api/models/catalogCurated model catalog with installed status
POST/api/models/pullPull/download a model (streams NDJSON progress)
POST/api/models/showGet detailed model info (license, family, quantization)
DELETE/api/modelsDelete a local model

Prompts

MethodPathDescription
POST/api/promptsSave a reusable prompt
GET/api/promptsList all saved prompts
GET/api/prompts/:idGet a single prompt
PUT/api/prompts/:idUpdate a prompt
DELETE/api/prompts/:idDelete a prompt

Leaderboard

MethodPathDescription
GET/api/leaderboardModel performance leaderboard with win rates

Health

MethodPathDescription
GET/api/healthReturns {"status": "ok"}

Job Lifecycle

  1. Submit a job via the dashboard or API with a model name, prompt, and optional parameters (system prompt, temperature, max tokens).
  2. The worker picks up the oldest pending job, marks it as processing, and sends it to Ollama.
  3. On completion, the worker records the result along with input tokens, output tokens, and generation time from the Ollama response.
  4. The dashboard polls for updates and displays status, results, and token throughput.

Model Management

The Models page (/models) provides a full model management interface:

  • Installed models table — Shows name, size, family, parameters, and quantization for all local models. Two-click delete confirmation.
  • Curated catalog — Browse ~15 model families across 6 categories (General Purpose, Code, Reasoning, Chat/Instruct, Small/Fast, Multilingual). Click a variant chip to pull it.
  • Real-time download progress — Pull streams NDJSON from Ollama with animated progress bars.
  • Custom pull — Text input for pulling any model by name (e.g. llama3.1:8b).

Model Comparison

The Compare feature (/compare) lets you run the same prompt against multiple models side-by-side. A comparison creates one job per selected model, all sharing the same prompt and parameters. Results are displayed in a side-by-side grid with per-model status, output, token counts, and generation speed. The detail page auto-refreshes until all jobs complete.

Prompt Library

The Prompts page (/prompts) lets you save reusable prompts with parameters (system prompt, temperature, max tokens). Saved prompts can be edited, deleted, run directly as a comparison against selected models, or sent to the Compare page pre-filled.

Model Leaderboard

The Leaderboard page (/leaderboard) ranks models by performance metrics including average tokens per second, generation time, total token usage, and comparison win rate.

Token Usage Tracking

The dashboard includes a cumulative token usage chart that tracks input and output tokens consumed per model over time. The chart:

  • Shows one line per model, each in a distinct color (16-color palette)
  • Displays cumulative total tokens on the Y-axis with auto-scaled labels (K/M suffixes)
  • Updates every 10 seconds via polling
  • Queries the last 24 hours of completed jobs by default

The data is derived from input_tokens and output_tokens already recorded on each completed job — no additional database tables are required.

GPU Monitoring

The gpu-monitor service reads metrics from NVIDIA GPUs every 2 seconds via pynvml:

  • GPU utilization %
  • Memory used / total (MB)
  • Temperature (Celsius)
  • Power draw (Watts)

Metrics older than 1 hour are automatically pruned. The dashboard renders a live SVG line chart showing utilization, memory %, and temperature over the selected time window.

Project Structure

queuedagents/
├── backend/ # FastAPI application
│ ├── app/
│ │ ├── main.py # App entrypoint, CORS, router mounting
│ │ ├── routes/
│ │ │ ├── jobs.py # Job CRUD + stats endpoints
│ │ │ ├── gpu.py # GPU metrics endpoint
│ │ │ ├── comparisons.py # Model comparison endpoints
│ │ │ ├── models.py # Model listing, catalog, pull, show, delete
│ │ │ ├── prompts.py # Prompt CRUD endpoints
│ │ │ └── leaderboard.py # Model leaderboard endpoint
│ │ ├── model_catalog.py # Curated model catalog data
│ │ └── schemas.py # Pydantic request/response models
│ ├── alembic/ # Database migrations
│ ├── Dockerfile
│ └── requirements.txt
├── worker/ # Job processing worker
│ ├── app/
│ │ ├── main.py # Polling loop, job claim/complete/fail
│ │ └── ollama_client.py # Ollama HTTP client
│ ├── Dockerfile
│ └── requirements.txt
├── gpu-monitor/ # GPU metrics collector
│ ├── app/
│ │ └── main.py # pynvml polling loop
│ ├── Dockerfile
│ └── requirements.txt
├── frontend/ # React SPA
│ ├── src/
│ │ ├── api/client.ts
│ │ ├── components/ # GpuChart, TokenChart, JobList, JobDetail, StatsCards, Layout, etc.
│ │ ├── hooks/ # usePolling
│ │ ├── pages/ # Dashboard, JobDetail, Prompts, Compare, ComparisonDetail, Leaderboard, Models
│ │ └── types/
│ ├── Dockerfile
│ └── nginx.conf
├── shared/ # Shared Python package
│ ├── config.py # Pydantic settings
│ ├── database.py # SQLAlchemy async engine + session
│ └── models.py # Job, Comparison, GpuMetric, Prompt ORM models
├── tests/ # 136 pytest tests (97% coverage)
│ ├── conftest.py # In-memory DB, session, and ASGI client fixtures
│ └── test_*.py # 16 test modules
├── requirements-test.txt
├── pytest.ini
├── docker-compose.yml
└── .env.example

Testing

The backend has a comprehensive test suite covering all Python services: backend (FastAPI routes), worker (job processing), gpu-monitor (metrics collection), and shared (models, config, database).

Running Tests

# One-time setup
ln -sf gpu-monitor gpu_monitor
pip install -r requirements-test.txt
# Run all 136 tests with coverage
pytest --cov --cov-report=term-missing -v
# Run a single test file
pytest tests/test_routes_jobs.py -v

What Gets Mocked

The test suite runs entirely offline — no Docker, no GPU, no Ollama needed. Three external systems are mocked out:

SystemMock StrategyWhy
SQLite databaseReplaced with an in-memory SQLite engine (sqlite+aiosqlite://). Each test gets a fresh database via function-scoped fixtures — tables are created before the test and dropped after.Eliminates filesystem I/O, prevents test pollution, runs in milliseconds.
Ollama HTTP APIIntercepted at the httpx transport layer using respx. Routes that call Ollama (/api/models, /api/models/catalog, /api/models/show, /api/models/pull, DELETE /api/models) and the worker's generate() function all get deterministic fake responses.Ollama requires a running server with downloaded models. Mocking lets us test every code path — success, HTTP errors, missing fields, timeouts — without a live inference server.
NVIDIA GPU driver (pynvml)The entire pynvml module is replaced via unittest.mock.patch with a MagicMock that returns SimpleNamespace objects mimicking real GPU handles, utilization rates, memory info, temperature, and power readings.The gpu-monitor service calls pynvml.nvmlDeviceGetHandleByIndex() and related C library bindings that require an NVIDIA GPU. Mocking lets us verify unit conversions (milliwatts → watts, bytes → megabytes), multi-GPU iteration, metric pruning, and error handling.

Test Structure

tests/
├── conftest.py # Shared fixtures (engine, session, FastAPI client)
├── test_shared_config.py # Settings defaults and env overrides
├── test_shared_models.py # ORM defaults, relationships, enums
├── test_shared_database.py # Engine, Base metadata, get_session
├── test_backend_main.py # Health endpoint, CORS, router registration
├── test_backend_schemas.py # Pydantic validation on all request schemas
├── test_backend_seed.py # Prompt seeding logic and idempotency
├── test_model_catalog.py # Catalog structure and data integrity
├── test_routes_jobs.py # Job CRUD, token-usage cumulative logic, stats
├── test_routes_comparisons.py # Comparison CRUD, set/clear winner validation
├── test_routes_gpu.py # GPU metrics query with time filtering
├── test_routes_models.py # Ollama proxy endpoints (respx mocks)
├── test_routes_leaderboard.py # TPS calculation, win rate, sorting
├── test_routes_prompts.py # Prompt CRUD with partial updates
├── test_worker_main.py # Job claim/complete/fail lifecycle, main loop
├── test_worker_ollama.py # generate() request/response handling
└── test_gpu_monitor.py # Record/prune metrics, main loop resilience

Development

For local frontend development with hot reload:

cd frontend
npm install
npm run dev

This starts Vite on port 3000 with API requests proxied to localhost:8000.

License

MIT

About

application to manage a backlog of requests to be processed in ollama.

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages

, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Auto-enable theater mode on YouTube\n(function() {\n function tryTheater() {\n var btn = document.querySelector('button[aria-label=\"Theater mode\"], ytd-player #player button[title=\"Theater mode\"]');\n if (btn && !btn.classList.contains('activated')) {\n btn.click();\n }\n }\n \n // Try immediately\n tryTheater();\n \n // Try after navigation (SPA)\n var lastUrl = location.href;\n setInterval(function() {\n if (location.href !== lastUrl) {\n lastUrl = location.href;\n setTimeout(tryTheater, 500);\n }\n }, 1000);\n \n // Also try on player load\n var observer = new MutationObserver(tryTheater);\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "YouTube Theater Mode Default"); } } catch(__e) { console.warn('[Userscript:YouTube Theater Mode Default]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

Repository files navigation

Queued Agents

A full-stack job queue dashboard for running LLM inference through Ollama with real-time GPU monitoring. Submit prompts, track job status, and observe GPU utilization, memory, temperature, and token throughput from a single UI.

Architecture

┌────────────┐ ┌──────────┐ ┌──────────┐ ┌────────┐
│ Frontend │────▶│ Backend │────▶│ SQLite │◀────│ Worker │
│ React/Nginx │ │ FastAPI │ │ (WAL) │ │ Python │
└────────────┘ └──────────┘ └──────────┘ └───┬────┘
▲ │
│ ▼
┌─────┴──────┐ ┌────────┐
│ GPU Monitor │ │ Ollama │
│ pynvml │ │ LLMs │
└─────────────┘ └────────┘

Five Docker services:

ServiceRolePort
frontendReact 19 + Vite + Tailwind, served via Nginx3001
backendFastAPI REST API8001
workerPolls for pending jobs, calls Ollama, writes results-
gpu-monitorPolls NVIDIA GPU metrics via pynvml, writes to DB-
ollamaLLM inference server11435

All Python services share a shared/ package containing the SQLAlchemy models, database engine, and config.

Prerequisites

Quick Start

# 1. Clone and configure
git clone git@github.com:chrisfauerbach/queuedagents.git
cd queuedagents
cp .env.example .env
# 2. Launch everything
docker compose up --build -d
# 3. Pull a model into Ollama
docker compose exec ollama ollama pull gemma3:12b
# 4. Open the dashboard
open http://localhost:3001

Configuration

Environment variables (set in .env):

VariableDefaultDescription
DATABASE_URLsqlite+aiosqlite:///./data/queue.dbSQLAlchemy async database URL
OLLAMA_HOSThttp://ollama:11434Ollama API base URL
POLL_INTERVAL1.0Worker job polling interval (seconds)
GPU_POLL_INTERVAL2.0GPU metrics polling interval (seconds)

API

All endpoints are prefixed with /api.

Jobs

MethodPathDescription
POST/api/jobsSubmit a new job
GET/api/jobsList jobs (query: status, limit, offset)
GET/api/jobs/:idGet a single job
GET/api/statsAggregate job status counts
GET/api/token-usage?hours=24Cumulative token usage per model (1-168 hour window)

GPU Metrics

MethodPathDescription
GET/api/gpu/metrics?minutes=10GPU time-series data (1-60 min window)

Comparisons

MethodPathDescription
POST/api/comparisonsCreate a comparison (runs same prompt across N models)
GET/api/comparisonsList all comparisons with their jobs
GET/api/comparisons/:idGet a single comparison with jobs

Models

MethodPathDescription
GET/api/modelsList available Ollama models
GET/api/models/catalogCurated model catalog with installed status
POST/api/models/pullPull/download a model (streams NDJSON progress)
POST/api/models/showGet detailed model info (license, family, quantization)
DELETE/api/modelsDelete a local model

Prompts

MethodPathDescription
POST/api/promptsSave a reusable prompt
GET/api/promptsList all saved prompts
GET/api/prompts/:idGet a single prompt
PUT/api/prompts/:idUpdate a prompt
DELETE/api/prompts/:idDelete a prompt

Leaderboard

MethodPathDescription
GET/api/leaderboardModel performance leaderboard with win rates

Health

MethodPathDescription
GET/api/healthReturns {"status": "ok"}

Job Lifecycle

  1. Submit a job via the dashboard or API with a model name, prompt, and optional parameters (system prompt, temperature, max tokens).
  2. The worker picks up the oldest pending job, marks it as processing, and sends it to Ollama.
  3. On completion, the worker records the result along with input tokens, output tokens, and generation time from the Ollama response.
  4. The dashboard polls for updates and displays status, results, and token throughput.

Model Management

The Models page (/models) provides a full model management interface:

  • Installed models table — Shows name, size, family, parameters, and quantization for all local models. Two-click delete confirmation.
  • Curated catalog — Browse ~15 model families across 6 categories (General Purpose, Code, Reasoning, Chat/Instruct, Small/Fast, Multilingual). Click a variant chip to pull it.
  • Real-time download progress — Pull streams NDJSON from Ollama with animated progress bars.
  • Custom pull — Text input for pulling any model by name (e.g. llama3.1:8b).

Model Comparison

The Compare feature (/compare) lets you run the same prompt against multiple models side-by-side. A comparison creates one job per selected model, all sharing the same prompt and parameters. Results are displayed in a side-by-side grid with per-model status, output, token counts, and generation speed. The detail page auto-refreshes until all jobs complete.

Prompt Library

The Prompts page (/prompts) lets you save reusable prompts with parameters (system prompt, temperature, max tokens). Saved prompts can be edited, deleted, run directly as a comparison against selected models, or sent to the Compare page pre-filled.

Model Leaderboard

The Leaderboard page (/leaderboard) ranks models by performance metrics including average tokens per second, generation time, total token usage, and comparison win rate.

Token Usage Tracking

The dashboard includes a cumulative token usage chart that tracks input and output tokens consumed per model over time. The chart:

  • Shows one line per model, each in a distinct color (16-color palette)
  • Displays cumulative total tokens on the Y-axis with auto-scaled labels (K/M suffixes)
  • Updates every 10 seconds via polling
  • Queries the last 24 hours of completed jobs by default

The data is derived from input_tokens and output_tokens already recorded on each completed job — no additional database tables are required.

GPU Monitoring

The gpu-monitor service reads metrics from NVIDIA GPUs every 2 seconds via pynvml:

  • GPU utilization %
  • Memory used / total (MB)
  • Temperature (Celsius)
  • Power draw (Watts)

Metrics older than 1 hour are automatically pruned. The dashboard renders a live SVG line chart showing utilization, memory %, and temperature over the selected time window.

Project Structure

queuedagents/
├── backend/ # FastAPI application
│ ├── app/
│ │ ├── main.py # App entrypoint, CORS, router mounting
│ │ ├── routes/
│ │ │ ├── jobs.py # Job CRUD + stats endpoints
│ │ │ ├── gpu.py # GPU metrics endpoint
│ │ │ ├── comparisons.py # Model comparison endpoints
│ │ │ ├── models.py # Model listing, catalog, pull, show, delete
│ │ │ ├── prompts.py # Prompt CRUD endpoints
│ │ │ └── leaderboard.py # Model leaderboard endpoint
│ │ ├── model_catalog.py # Curated model catalog data
│ │ └── schemas.py # Pydantic request/response models
│ ├── alembic/ # Database migrations
│ ├── Dockerfile
│ └── requirements.txt
├── worker/ # Job processing worker
│ ├── app/
│ │ ├── main.py # Polling loop, job claim/complete/fail
│ │ └── ollama_client.py # Ollama HTTP client
│ ├── Dockerfile
│ └── requirements.txt
├── gpu-monitor/ # GPU metrics collector
│ ├── app/
│ │ └── main.py # pynvml polling loop
│ ├── Dockerfile
│ └── requirements.txt
├── frontend/ # React SPA
│ ├── src/
│ │ ├── api/client.ts
│ │ ├── components/ # GpuChart, TokenChart, JobList, JobDetail, StatsCards, Layout, etc.
│ │ ├── hooks/ # usePolling
│ │ ├── pages/ # Dashboard, JobDetail, Prompts, Compare, ComparisonDetail, Leaderboard, Models
│ │ └── types/
│ ├── Dockerfile
│ └── nginx.conf
├── shared/ # Shared Python package
│ ├── config.py # Pydantic settings
│ ├── database.py # SQLAlchemy async engine + session
│ └── models.py # Job, Comparison, GpuMetric, Prompt ORM models
├── tests/ # 136 pytest tests (97% coverage)
│ ├── conftest.py # In-memory DB, session, and ASGI client fixtures
│ └── test_*.py # 16 test modules
├── requirements-test.txt
├── pytest.ini
├── docker-compose.yml
└── .env.example

Testing

The backend has a comprehensive test suite covering all Python services: backend (FastAPI routes), worker (job processing), gpu-monitor (metrics collection), and shared (models, config, database).

Running Tests

# One-time setup
ln -sf gpu-monitor gpu_monitor
pip install -r requirements-test.txt
# Run all 136 tests with coverage
pytest --cov --cov-report=term-missing -v
# Run a single test file
pytest tests/test_routes_jobs.py -v

What Gets Mocked

The test suite runs entirely offline — no Docker, no GPU, no Ollama needed. Three external systems are mocked out:

SystemMock StrategyWhy
SQLite databaseReplaced with an in-memory SQLite engine (sqlite+aiosqlite://). Each test gets a fresh database via function-scoped fixtures — tables are created before the test and dropped after.Eliminates filesystem I/O, prevents test pollution, runs in milliseconds.
Ollama HTTP APIIntercepted at the httpx transport layer using respx. Routes that call Ollama (/api/models, /api/models/catalog, /api/models/show, /api/models/pull, DELETE /api/models) and the worker's generate() function all get deterministic fake responses.Ollama requires a running server with downloaded models. Mocking lets us test every code path — success, HTTP errors, missing fields, timeouts — without a live inference server.
NVIDIA GPU driver (pynvml)The entire pynvml module is replaced via unittest.mock.patch with a MagicMock that returns SimpleNamespace objects mimicking real GPU handles, utilization rates, memory info, temperature, and power readings.The gpu-monitor service calls pynvml.nvmlDeviceGetHandleByIndex() and related C library bindings that require an NVIDIA GPU. Mocking lets us verify unit conversions (milliwatts → watts, bytes → megabytes), multi-GPU iteration, metric pruning, and error handling.

Test Structure

tests/
├── conftest.py # Shared fixtures (engine, session, FastAPI client)
├── test_shared_config.py # Settings defaults and env overrides
├── test_shared_models.py # ORM defaults, relationships, enums
├── test_shared_database.py # Engine, Base metadata, get_session
├── test_backend_main.py # Health endpoint, CORS, router registration
├── test_backend_schemas.py # Pydantic validation on all request schemas
├── test_backend_seed.py # Prompt seeding logic and idempotency
├── test_model_catalog.py # Catalog structure and data integrity
├── test_routes_jobs.py # Job CRUD, token-usage cumulative logic, stats
├── test_routes_comparisons.py # Comparison CRUD, set/clear winner validation
├── test_routes_gpu.py # GPU metrics query with time filtering
├── test_routes_models.py # Ollama proxy endpoints (respx mocks)
├── test_routes_leaderboard.py # TPS calculation, win rate, sorting
├── test_routes_prompts.py # Prompt CRUD with partial updates
├── test_worker_main.py # Job claim/complete/fail lifecycle, main loop
├── test_worker_ollama.py # generate() request/response handling
└── test_gpu_monitor.py # Record/prune metrics, main loop resilience

Development

For local frontend development with hot reload:

cd frontend
npm install
npm run dev

This starts Vite on port 3000 with API requests proxied to localhost:8000.

License

MIT

About

application to manage a backlog of requests to be processed in ollama.

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages

, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Remove or un-stick sticky/fixed headers that block content\n(function() {\n function unstick() {\n document.querySelectorAll('header, nav, [role=\"banner\"], .header, .navbar, .sticky, .fixed-top, [style*=\"position: fixed\"], [style*=\"position:sticky\"]').forEach(function(el) {\n if (el.style.position === 'fixed' || el.style.position === 'sticky' || \n getComputedStyle(el).position === 'fixed' || getComputedStyle(el).position === 'sticky') {\n el.style.position = 'static';\n el.style.top = 'auto';\n el.style.zIndex = 'auto';\n }\n });\n }\n \n unstick();\n \n var observer = new MutationObserver(unstick);\n observer.observe(document.body, { childList: true, subtree: true, attributes: true, attributeFilter: ['style', 'class'] });\n})();", "Kill Sticky Headers"); } } catch(__e) { console.warn('[Userscript:Kill Sticky Headers]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

Repository files navigation

Queued Agents

A full-stack job queue dashboard for running LLM inference through Ollama with real-time GPU monitoring. Submit prompts, track job status, and observe GPU utilization, memory, temperature, and token throughput from a single UI.

Architecture

┌────────────┐ ┌──────────┐ ┌──────────┐ ┌────────┐
│ Frontend │────▶│ Backend │────▶│ SQLite │◀────│ Worker │
│ React/Nginx │ │ FastAPI │ │ (WAL) │ │ Python │
└────────────┘ └──────────┘ └──────────┘ └───┬────┘
▲ │
│ ▼
┌─────┴──────┐ ┌────────┐
│ GPU Monitor │ │ Ollama │
│ pynvml │ │ LLMs │
└─────────────┘ └────────┘

Five Docker services:

ServiceRolePort
frontendReact 19 + Vite + Tailwind, served via Nginx3001
backendFastAPI REST API8001
workerPolls for pending jobs, calls Ollama, writes results-
gpu-monitorPolls NVIDIA GPU metrics via pynvml, writes to DB-
ollamaLLM inference server11435

All Python services share a shared/ package containing the SQLAlchemy models, database engine, and config.

Prerequisites

Quick Start

# 1. Clone and configure
git clone git@github.com:chrisfauerbach/queuedagents.git
cd queuedagents
cp .env.example .env
# 2. Launch everything
docker compose up --build -d
# 3. Pull a model into Ollama
docker compose exec ollama ollama pull gemma3:12b
# 4. Open the dashboard
open http://localhost:3001

Configuration

Environment variables (set in .env):

VariableDefaultDescription
DATABASE_URLsqlite+aiosqlite:///./data/queue.dbSQLAlchemy async database URL
OLLAMA_HOSThttp://ollama:11434Ollama API base URL
POLL_INTERVAL1.0Worker job polling interval (seconds)
GPU_POLL_INTERVAL2.0GPU metrics polling interval (seconds)

API

All endpoints are prefixed with /api.

Jobs

MethodPathDescription
POST/api/jobsSubmit a new job
GET/api/jobsList jobs (query: status, limit, offset)
GET/api/jobs/:idGet a single job
GET/api/statsAggregate job status counts
GET/api/token-usage?hours=24Cumulative token usage per model (1-168 hour window)

GPU Metrics

MethodPathDescription
GET/api/gpu/metrics?minutes=10GPU time-series data (1-60 min window)

Comparisons

MethodPathDescription
POST/api/comparisonsCreate a comparison (runs same prompt across N models)
GET/api/comparisonsList all comparisons with their jobs
GET/api/comparisons/:idGet a single comparison with jobs

Models

MethodPathDescription
GET/api/modelsList available Ollama models
GET/api/models/catalogCurated model catalog with installed status
POST/api/models/pullPull/download a model (streams NDJSON progress)
POST/api/models/showGet detailed model info (license, family, quantization)
DELETE/api/modelsDelete a local model

Prompts

MethodPathDescription
POST/api/promptsSave a reusable prompt
GET/api/promptsList all saved prompts
GET/api/prompts/:idGet a single prompt
PUT/api/prompts/:idUpdate a prompt
DELETE/api/prompts/:idDelete a prompt

Leaderboard

MethodPathDescription
GET/api/leaderboardModel performance leaderboard with win rates

Health

MethodPathDescription
GET/api/healthReturns {"status": "ok"}

Job Lifecycle

  1. Submit a job via the dashboard or API with a model name, prompt, and optional parameters (system prompt, temperature, max tokens).
  2. The worker picks up the oldest pending job, marks it as processing, and sends it to Ollama.
  3. On completion, the worker records the result along with input tokens, output tokens, and generation time from the Ollama response.
  4. The dashboard polls for updates and displays status, results, and token throughput.

Model Management

The Models page (/models) provides a full model management interface:

  • Installed models table — Shows name, size, family, parameters, and quantization for all local models. Two-click delete confirmation.
  • Curated catalog — Browse ~15 model families across 6 categories (General Purpose, Code, Reasoning, Chat/Instruct, Small/Fast, Multilingual). Click a variant chip to pull it.
  • Real-time download progress — Pull streams NDJSON from Ollama with animated progress bars.
  • Custom pull — Text input for pulling any model by name (e.g. llama3.1:8b).

Model Comparison

The Compare feature (/compare) lets you run the same prompt against multiple models side-by-side. A comparison creates one job per selected model, all sharing the same prompt and parameters. Results are displayed in a side-by-side grid with per-model status, output, token counts, and generation speed. The detail page auto-refreshes until all jobs complete.

Prompt Library

The Prompts page (/prompts) lets you save reusable prompts with parameters (system prompt, temperature, max tokens). Saved prompts can be edited, deleted, run directly as a comparison against selected models, or sent to the Compare page pre-filled.

Model Leaderboard

The Leaderboard page (/leaderboard) ranks models by performance metrics including average tokens per second, generation time, total token usage, and comparison win rate.

Token Usage Tracking

The dashboard includes a cumulative token usage chart that tracks input and output tokens consumed per model over time. The chart:

  • Shows one line per model, each in a distinct color (16-color palette)
  • Displays cumulative total tokens on the Y-axis with auto-scaled labels (K/M suffixes)
  • Updates every 10 seconds via polling
  • Queries the last 24 hours of completed jobs by default

The data is derived from input_tokens and output_tokens already recorded on each completed job — no additional database tables are required.

GPU Monitoring

The gpu-monitor service reads metrics from NVIDIA GPUs every 2 seconds via pynvml:

  • GPU utilization %
  • Memory used / total (MB)
  • Temperature (Celsius)
  • Power draw (Watts)

Metrics older than 1 hour are automatically pruned. The dashboard renders a live SVG line chart showing utilization, memory %, and temperature over the selected time window.

Project Structure

queuedagents/
├── backend/ # FastAPI application
│ ├── app/
│ │ ├── main.py # App entrypoint, CORS, router mounting
│ │ ├── routes/
│ │ │ ├── jobs.py # Job CRUD + stats endpoints
│ │ │ ├── gpu.py # GPU metrics endpoint
│ │ │ ├── comparisons.py # Model comparison endpoints
│ │ │ ├── models.py # Model listing, catalog, pull, show, delete
│ │ │ ├── prompts.py # Prompt CRUD endpoints
│ │ │ └── leaderboard.py # Model leaderboard endpoint
│ │ ├── model_catalog.py # Curated model catalog data
│ │ └── schemas.py # Pydantic request/response models
│ ├── alembic/ # Database migrations
│ ├── Dockerfile
│ └── requirements.txt
├── worker/ # Job processing worker
│ ├── app/
│ │ ├── main.py # Polling loop, job claim/complete/fail
│ │ └── ollama_client.py # Ollama HTTP client
│ ├── Dockerfile
│ └── requirements.txt
├── gpu-monitor/ # GPU metrics collector
│ ├── app/
│ │ └── main.py # pynvml polling loop
│ ├── Dockerfile
│ └── requirements.txt
├── frontend/ # React SPA
│ ├── src/
│ │ ├── api/client.ts
│ │ ├── components/ # GpuChart, TokenChart, JobList, JobDetail, StatsCards, Layout, etc.
│ │ ├── hooks/ # usePolling
│ │ ├── pages/ # Dashboard, JobDetail, Prompts, Compare, ComparisonDetail, Leaderboard, Models
│ │ └── types/
│ ├── Dockerfile
│ └── nginx.conf
├── shared/ # Shared Python package
│ ├── config.py # Pydantic settings
│ ├── database.py # SQLAlchemy async engine + session
│ └── models.py # Job, Comparison, GpuMetric, Prompt ORM models
├── tests/ # 136 pytest tests (97% coverage)
│ ├── conftest.py # In-memory DB, session, and ASGI client fixtures
│ └── test_*.py # 16 test modules
├── requirements-test.txt
├── pytest.ini
├── docker-compose.yml
└── .env.example

Testing

The backend has a comprehensive test suite covering all Python services: backend (FastAPI routes), worker (job processing), gpu-monitor (metrics collection), and shared (models, config, database).

Running Tests

# One-time setup
ln -sf gpu-monitor gpu_monitor
pip install -r requirements-test.txt
# Run all 136 tests with coverage
pytest --cov --cov-report=term-missing -v
# Run a single test file
pytest tests/test_routes_jobs.py -v

What Gets Mocked

The test suite runs entirely offline — no Docker, no GPU, no Ollama needed. Three external systems are mocked out:

SystemMock StrategyWhy
SQLite databaseReplaced with an in-memory SQLite engine (sqlite+aiosqlite://). Each test gets a fresh database via function-scoped fixtures — tables are created before the test and dropped after.Eliminates filesystem I/O, prevents test pollution, runs in milliseconds.
Ollama HTTP APIIntercepted at the httpx transport layer using respx. Routes that call Ollama (/api/models, /api/models/catalog, /api/models/show, /api/models/pull, DELETE /api/models) and the worker's generate() function all get deterministic fake responses.Ollama requires a running server with downloaded models. Mocking lets us test every code path — success, HTTP errors, missing fields, timeouts — without a live inference server.
NVIDIA GPU driver (pynvml)The entire pynvml module is replaced via unittest.mock.patch with a MagicMock that returns SimpleNamespace objects mimicking real GPU handles, utilization rates, memory info, temperature, and power readings.The gpu-monitor service calls pynvml.nvmlDeviceGetHandleByIndex() and related C library bindings that require an NVIDIA GPU. Mocking lets us verify unit conversions (milliwatts → watts, bytes → megabytes), multi-GPU iteration, metric pruning, and error handling.

Test Structure

tests/
├── conftest.py # Shared fixtures (engine, session, FastAPI client)
├── test_shared_config.py # Settings defaults and env overrides
├── test_shared_models.py # ORM defaults, relationships, enums
├── test_shared_database.py # Engine, Base metadata, get_session
├── test_backend_main.py # Health endpoint, CORS, router registration
├── test_backend_schemas.py # Pydantic validation on all request schemas
├── test_backend_seed.py # Prompt seeding logic and idempotency
├── test_model_catalog.py # Catalog structure and data integrity
├── test_routes_jobs.py # Job CRUD, token-usage cumulative logic, stats
├── test_routes_comparisons.py # Comparison CRUD, set/clear winner validation
├── test_routes_gpu.py # GPU metrics query with time filtering
├── test_routes_models.py # Ollama proxy endpoints (respx mocks)
├── test_routes_leaderboard.py # TPS calculation, win rate, sorting
├── test_routes_prompts.py # Prompt CRUD with partial updates
├── test_worker_main.py # Job claim/complete/fail lifecycle, main loop
├── test_worker_ollama.py # generate() request/response handling
└── test_gpu_monitor.py # Record/prune metrics, main loop resilience

Development

For local frontend development with hot reload:

cd frontend
npm install
npm run dev

This starts Vite on port 3000 with API requests proxied to localhost:8000.

License

MIT

About

application to manage a backlog of requests to be processed in ollama.

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages

, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Universal Dark Mode - works on any site\n(function() {\n var enabled = true;\n \n function applyDarkMode() {\n if (!enabled) return;\n \n // Create style element if it doesn't exist\n var style = document.getElementById('universal-dark-mode-style');\n if (!style) {\n style = document.createElement('style');\n style.id = 'universal-dark-mode-style';\n document.head.appendChild(style);\n }\n \n // Dark mode CSS - inverts colors but preserves images/video\n style.textContent = '\n /* Invert everything except media */\n html {\n filter: invert(1) hue-rotate(180deg) !important;\n background: #1a1a2e !important;\n }\n \n /* Restore images, videos, iframes, canvas */\n img, video, iframe, canvas, svg, picture, [style*=\"background-image\"] {\n filter: invert(1) hue-rotate(180deg) !important;\n }\n \n /* Preserve specific elements that should not be inverted */\n .no-dark-mode, .no-dark-mode *,\n [data-theme=\"light\"], [data-theme=\"light\"],\n .ace_editor, .ace_editor *,\n .CodeMirror, .CodeMirror *,\n .monaco-editor, .monaco-editor *,\n .markdown-body pre, .markdown-body pre *,\n .highlight, .highlight *,\n pre code, pre code * {\n filter: none !important;\n }\n \n /* Fix common UI elements */\n .modal, .popup, .dropdown-menu, .tooltip, .popover {\n filter: invert(1) hue-rotate(180deg) !important;\n background: #2d2d44 !important;\n border-color: #444 !important;\n }\n \n /* Scrollbars */\n ::-webkit-scrollbar { background: #1a1a2e !important; }\n ::-webkit-scrollbar-thumb { background: #444 !important; }\n ::-webkit-scrollbar-thumb:hover { background: #555 !important; }\n \n /* Selection */\n ::selection { background: #4ecdc4 !important; color: #1a1a2e !important; }\n ::-moz-selection { background: #4ecdc4 !important; color: #1a1a2e !important; }\n ';\n }\n \n function removeDarkMode() {\n var style = document.getElementById('universal-dark-mode-style');\n if (style) style.remove();\n }\n \n // Toggle with Alt+Shift+D\n document.addEventListener('keydown', function(e) {\n if (e.altKey && e.shiftKey && e.key === 'D') {\n e.preventDefault();\n enabled = !enabled;\n if (enabled) {\n applyDarkMode();\n console.log('[Universal Dark Mode] Enabled');\n } else {\n removeDarkMode();\n console.log('[Universal Dark Mode] Disabled');\n }\n }\n });\n \n // Apply on load\n applyDarkMode();\n \n // Re-apply on dynamic content\n var observer = new MutationObserver(function(mutations) {\n if (enabled && !document.getElementById('universal-dark-mode-style')) {\n applyDarkMode();\n }\n });\n observer.observe(document.head, { childList: true });\n \n console.log('[Universal Dark Mode] Loaded - Press Alt+Shift+D to toggle');\n})();", "Universal Dark Mode"); } } catch(__e) { console.warn('[Userscript:Universal Dark Mode]', __e); } })(); })();
Skip to content

Repository files navigation

Queued Agents

A full-stack job queue dashboard for running LLM inference through Ollama with real-time GPU monitoring. Submit prompts, track job status, and observe GPU utilization, memory, temperature, and token throughput from a single UI.

Architecture

┌────────────┐ ┌──────────┐ ┌──────────┐ ┌────────┐
│ Frontend │────▶│ Backend │────▶│ SQLite │◀────│ Worker │
│ React/Nginx │ │ FastAPI │ │ (WAL) │ │ Python │
└────────────┘ └──────────┘ └──────────┘ └───┬────┘
▲ │
│ ▼
┌─────┴──────┐ ┌────────┐
│ GPU Monitor │ │ Ollama │
│ pynvml │ │ LLMs │
└─────────────┘ └────────┘

Five Docker services:

ServiceRolePort
frontendReact 19 + Vite + Tailwind, served via Nginx3001
backendFastAPI REST API8001
workerPolls for pending jobs, calls Ollama, writes results-
gpu-monitorPolls NVIDIA GPU metrics via pynvml, writes to DB-
ollamaLLM inference server11435

All Python services share a shared/ package containing the SQLAlchemy models, database engine, and config.

Prerequisites

Quick Start

# 1. Clone and configure
git clone git@github.com:chrisfauerbach/queuedagents.git
cd queuedagents
cp .env.example .env
# 2. Launch everything
docker compose up --build -d
# 3. Pull a model into Ollama
docker compose exec ollama ollama pull gemma3:12b
# 4. Open the dashboard
open http://localhost:3001

Configuration

Environment variables (set in .env):

VariableDefaultDescription
DATABASE_URLsqlite+aiosqlite:///./data/queue.dbSQLAlchemy async database URL
OLLAMA_HOSThttp://ollama:11434Ollama API base URL
POLL_INTERVAL1.0Worker job polling interval (seconds)
GPU_POLL_INTERVAL2.0GPU metrics polling interval (seconds)

API

All endpoints are prefixed with /api.

Jobs

MethodPathDescription
POST/api/jobsSubmit a new job
GET/api/jobsList jobs (query: status, limit, offset)
GET/api/jobs/:idGet a single job
GET/api/statsAggregate job status counts
GET/api/token-usage?hours=24Cumulative token usage per model (1-168 hour window)

GPU Metrics

MethodPathDescription
GET/api/gpu/metrics?minutes=10GPU time-series data (1-60 min window)

Comparisons

MethodPathDescription
POST/api/comparisonsCreate a comparison (runs same prompt across N models)
GET/api/comparisonsList all comparisons with their jobs
GET/api/comparisons/:idGet a single comparison with jobs

Models

MethodPathDescription
GET/api/modelsList available Ollama models
GET/api/models/catalogCurated model catalog with installed status
POST/api/models/pullPull/download a model (streams NDJSON progress)
POST/api/models/showGet detailed model info (license, family, quantization)
DELETE/api/modelsDelete a local model

Prompts

MethodPathDescription
POST/api/promptsSave a reusable prompt
GET/api/promptsList all saved prompts
GET/api/prompts/:idGet a single prompt
PUT/api/prompts/:idUpdate a prompt
DELETE/api/prompts/:idDelete a prompt

Leaderboard

MethodPathDescription
GET/api/leaderboardModel performance leaderboard with win rates

Health

MethodPathDescription
GET/api/healthReturns {"status": "ok"}

Job Lifecycle

  1. Submit a job via the dashboard or API with a model name, prompt, and optional parameters (system prompt, temperature, max tokens).
  2. The worker picks up the oldest pending job, marks it as processing, and sends it to Ollama.
  3. On completion, the worker records the result along with input tokens, output tokens, and generation time from the Ollama response.
  4. The dashboard polls for updates and displays status, results, and token throughput.

Model Management

The Models page (/models) provides a full model management interface:

  • Installed models table — Shows name, size, family, parameters, and quantization for all local models. Two-click delete confirmation.
  • Curated catalog — Browse ~15 model families across 6 categories (General Purpose, Code, Reasoning, Chat/Instruct, Small/Fast, Multilingual). Click a variant chip to pull it.
  • Real-time download progress — Pull streams NDJSON from Ollama with animated progress bars.
  • Custom pull — Text input for pulling any model by name (e.g. llama3.1:8b).

Model Comparison

The Compare feature (/compare) lets you run the same prompt against multiple models side-by-side. A comparison creates one job per selected model, all sharing the same prompt and parameters. Results are displayed in a side-by-side grid with per-model status, output, token counts, and generation speed. The detail page auto-refreshes until all jobs complete.

Prompt Library

The Prompts page (/prompts) lets you save reusable prompts with parameters (system prompt, temperature, max tokens). Saved prompts can be edited, deleted, run directly as a comparison against selected models, or sent to the Compare page pre-filled.

Model Leaderboard

The Leaderboard page (/leaderboard) ranks models by performance metrics including average tokens per second, generation time, total token usage, and comparison win rate.

Token Usage Tracking

The dashboard includes a cumulative token usage chart that tracks input and output tokens consumed per model over time. The chart:

  • Shows one line per model, each in a distinct color (16-color palette)
  • Displays cumulative total tokens on the Y-axis with auto-scaled labels (K/M suffixes)
  • Updates every 10 seconds via polling
  • Queries the last 24 hours of completed jobs by default

The data is derived from input_tokens and output_tokens already recorded on each completed job — no additional database tables are required.

GPU Monitoring

The gpu-monitor service reads metrics from NVIDIA GPUs every 2 seconds via pynvml:

  • GPU utilization %
  • Memory used / total (MB)
  • Temperature (Celsius)
  • Power draw (Watts)

Metrics older than 1 hour are automatically pruned. The dashboard renders a live SVG line chart showing utilization, memory %, and temperature over the selected time window.

Project Structure

queuedagents/
├── backend/ # FastAPI application
│ ├── app/
│ │ ├── main.py # App entrypoint, CORS, router mounting
│ │ ├── routes/
│ │ │ ├── jobs.py # Job CRUD + stats endpoints
│ │ │ ├── gpu.py # GPU metrics endpoint
│ │ │ ├── comparisons.py # Model comparison endpoints
│ │ │ ├── models.py # Model listing, catalog, pull, show, delete
│ │ │ ├── prompts.py # Prompt CRUD endpoints
│ │ │ └── leaderboard.py # Model leaderboard endpoint
│ │ ├── model_catalog.py # Curated model catalog data
│ │ └── schemas.py # Pydantic request/response models
│ ├── alembic/ # Database migrations
│ ├── Dockerfile
│ └── requirements.txt
├── worker/ # Job processing worker
│ ├── app/
│ │ ├── main.py # Polling loop, job claim/complete/fail
│ │ └── ollama_client.py # Ollama HTTP client
│ ├── Dockerfile
│ └── requirements.txt
├── gpu-monitor/ # GPU metrics collector
│ ├── app/
│ │ └── main.py # pynvml polling loop
│ ├── Dockerfile
│ └── requirements.txt
├── frontend/ # React SPA
│ ├── src/
│ │ ├── api/client.ts
│ │ ├── components/ # GpuChart, TokenChart, JobList, JobDetail, StatsCards, Layout, etc.
│ │ ├── hooks/ # usePolling
│ │ ├── pages/ # Dashboard, JobDetail, Prompts, Compare, ComparisonDetail, Leaderboard, Models
│ │ └── types/
│ ├── Dockerfile
│ └── nginx.conf
├── shared/ # Shared Python package
│ ├── config.py # Pydantic settings
│ ├── database.py # SQLAlchemy async engine + session
│ └── models.py # Job, Comparison, GpuMetric, Prompt ORM models
├── tests/ # 136 pytest tests (97% coverage)
│ ├── conftest.py # In-memory DB, session, and ASGI client fixtures
│ └── test_*.py # 16 test modules
├── requirements-test.txt
├── pytest.ini
├── docker-compose.yml
└── .env.example

Testing

The backend has a comprehensive test suite covering all Python services: backend (FastAPI routes), worker (job processing), gpu-monitor (metrics collection), and shared (models, config, database).

Running Tests

# One-time setup
ln -sf gpu-monitor gpu_monitor
pip install -r requirements-test.txt
# Run all 136 tests with coverage
pytest --cov --cov-report=term-missing -v
# Run a single test file
pytest tests/test_routes_jobs.py -v

What Gets Mocked

The test suite runs entirely offline — no Docker, no GPU, no Ollama needed. Three external systems are mocked out:

SystemMock StrategyWhy
SQLite databaseReplaced with an in-memory SQLite engine (sqlite+aiosqlite://). Each test gets a fresh database via function-scoped fixtures — tables are created before the test and dropped after.Eliminates filesystem I/O, prevents test pollution, runs in milliseconds.
Ollama HTTP APIIntercepted at the httpx transport layer using respx. Routes that call Ollama (/api/models, /api/models/catalog, /api/models/show, /api/models/pull, DELETE /api/models) and the worker's generate() function all get deterministic fake responses.Ollama requires a running server with downloaded models. Mocking lets us test every code path — success, HTTP errors, missing fields, timeouts — without a live inference server.
NVIDIA GPU driver (pynvml)The entire pynvml module is replaced via unittest.mock.patch with a MagicMock that returns SimpleNamespace objects mimicking real GPU handles, utilization rates, memory info, temperature, and power readings.The gpu-monitor service calls pynvml.nvmlDeviceGetHandleByIndex() and related C library bindings that require an NVIDIA GPU. Mocking lets us verify unit conversions (milliwatts → watts, bytes → megabytes), multi-GPU iteration, metric pruning, and error handling.

Test Structure

tests/
├── conftest.py # Shared fixtures (engine, session, FastAPI client)
├── test_shared_config.py # Settings defaults and env overrides
├── test_shared_models.py # ORM defaults, relationships, enums
├── test_shared_database.py # Engine, Base metadata, get_session
├── test_backend_main.py # Health endpoint, CORS, router registration
├── test_backend_schemas.py # Pydantic validation on all request schemas
├── test_backend_seed.py # Prompt seeding logic and idempotency
├── test_model_catalog.py # Catalog structure and data integrity
├── test_routes_jobs.py # Job CRUD, token-usage cumulative logic, stats
├── test_routes_comparisons.py # Comparison CRUD, set/clear winner validation
├── test_routes_gpu.py # GPU metrics query with time filtering
├── test_routes_models.py # Ollama proxy endpoints (respx mocks)
├── test_routes_leaderboard.py # TPS calculation, win rate, sorting
├── test_routes_prompts.py # Prompt CRUD with partial updates
├── test_worker_main.py # Job claim/complete/fail lifecycle, main loop
├── test_worker_ollama.py # generate() request/response handling
└── test_gpu_monitor.py # Record/prune metrics, main loop resilience

Development

For local frontend development with hot reload:

cd frontend
npm install
npm run dev

This starts Vite on port 3000 with API requests proxied to localhost:8000.

License

MIT

About

application to manage a backlog of requests to be processed in ollama.

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages