Run code on any device from anywhere — using just HTTP.
A zero-infrastructure job coordination system with retries, atomic locking, priority scheduling, and cross-device workers. Built for developers who want something more reliable than cron, without the overhead of Redis, RabbitMQ, or Firebase.
📖 Why I built this · 📱 Cross-device automation guide
- Trigger your Android phone from a cloud server
- Run jobs across devices without opening ports
- Build distributed systems using just HTTP + curl
- Priority Queues — high-priority intents are always claimed first
- Capability Routing — workers advertise what they can do; jobs require what they need
- Dead-Letter Queue — failed jobs are archived, not lost
- No brokers, no queues, no infrastructure to maintain
No external brokers. Just a minimal Flask + SQLite core.
- A client POSTs a job to
/intent - Workers poll
/claimfor matching jobs - One worker atomically claims the job (
BEGIN IMMEDIATE+UPDATE ... RETURNING) - Worker executes and calls
/fulfill - If it crashes, the job is requeued with exponential backoff and retried up to
max_attemptstimes before being archived to the dead-letter queue
graph LR
A[Cloud Script<br/>PythonAnywhere] -->|POST /intent| B[Intent Bus<br/>Flask + SQLite]
B -->|claim + fulfill| C[Worker<br/>Termux / Linux / VPS]
C -->|execute task| D[📱 Phone / System Action]
| Tool | Problem |
|---|---|
| Cron | No coordination, no retries, silent failures |
| Redis / Celery | Requires running and maintaining a server |
| RabbitMQ | Heavy infra, steep learning curve |
| Firebase | Vendor lock-in, SDK bloat, pricing at scale |
| Intent Bus | ✅ Single file, deploy anywhere, zero ops |
- Developers running scripts across multiple machines
- People using Termux / Android automation
- Indie hackers avoiding infrastructure complexity
- Anyone who wants job queues without Redis or RabbitMQ
This project is designed for low-to-medium traffic workloads — hundreds to low thousands of jobs per day, with dozens of concurrent workers. At that scale SQLite is a strength: zero ops, zero config, zero cost.
Intent Bus supports two auth modes for regular clients and a separate admin auth layer.
X-API-KEY: your_key_here
Works with curl, bash scripts, and IoT devices. No replay protection.
- HMAC-SHA256 signed requests
- Nonce-based replay protection
- Canonical request serialization
- Handled automatically by the Python SDK
Enable globally with BUS_REQUIRE_SIGNATURES=true, or let clients opt in by including signature headers.
Admin endpoints (/admin/*) use a separate privileged credential:
X-Admin-Token: <BUS_ADMIN_SECRET>header, or- HTTP Basic auth (
admin/DASHBOARD_PASSWORD)
⚠️ SetBUS_ADMIN_SECRETseparately fromBUS_SECRETin production. IfBUS_ADMIN_SECRETis not configured, the server falls back to acceptingBUS_SECRETas the admin token — convenient for local dev, risky if your main key is ever exposed.
pip install intent-buspublish() returns an IntentStatus model.
from intent_bus import IntentClient
# Use as a context manager for automatic connection pooling cleanup
with IntentClient(api_key="your_key_here") as client:
published = client.publish(
goal="send_notification",
payload={"instruction": "Hello from the cloud"},
idempotency_key="task_123", # Prevents double-execution on retry
priority=500, # Higher = claimed first (0–1000, default 100)
)
print(f"Published: {published.id}")Job Visibility:
private(default) — only workers using the same API key as the publisher can claim this jobpublic— any authenticated worker in the same namespace can claim this job
⚠️ Public jobs can be claimed by any authenticated worker in the namespace. Do not usevisibility="public"for sensitive workloads unless every worker on your bus is trusted.
Priority: Higher numbers are claimed first. Default is 100. Range is 0–1000.
claim() returns a ClaimResponse[ClaimedIntent]. Use job.id and job.payload in handlers.
from intent_bus import IntentClient, WorkerRuntime
def handler(payload):
# payload is the dict passed to publish()
print("Received:", payload.get("instruction"))
# Return a dict for fulfillment; the SDK handles the /fulfill call
return {"result": "delivered", "result_type": "text"}
client = IntentClient(api_key="your_key_here")
runtime = WorkerRuntime(client=client)
# Resilient v2.0 loop with exponential backoff and jitter
runtime.listen(goal="send_notification", handler=handler)
⚠️ Workers must be idempotent. The same job may be delivered more than once if:
- the worker crashes mid-execution
- the lease expires before
/fulfillis called- the network drops after the server marks the job fulfilled but before the response arrives
- the bus retries due to an ambiguous failure
SDK repo: github.com/dsecurity49/Intent-Bus-sdk
curl -X POST https://dsecurity.pythonanywhere.com/intent \
-H "Content-Type: application/json" \
-H "X-API-KEY: your_key_here" \
-d '{"goal":"send_notification","payload":{"instruction":"Hello"}}'curl -X POST https://dsecurity.pythonanywhere.com/intent \
-H "Content-Type: application/json" \
-H "X-API-KEY: your_key_here" \
-d '{"goal":"send_notification","payload":{"instruction":"Urgent"},"priority":900,"delay":5.0}'# Claim (returns a JSON intent model in v2.0)
curl -s -X POST "https://dsecurity.pythonanywhere.com/claim?goal=send_notification" \
-H "X-API-KEY: your_key_here"
# Fulfill (v2.0 requires result_type)
curl -s -X POST "https://dsecurity.pythonanywhere.com/fulfill/<INTENT_ID>" \
-H "Content-Type: application/json" \
-H "X-API-KEY: your_key_here" \
-d '{"result":"done","result_type":"text"}'If a job isn't fulfilled within 60 seconds, it is automatically requeued with exponential backoff.
Worker polling: After a
204 No Content(no jobs available), workers SHOULD wait for theRetry-Afterduration (default 1s). Tight polling loops create unnecessary write pressure on SQLite under concurrency.
open ──► claimed ──► fulfilled
│
▼
open (retry with backoff, if attempts remain)
│
▼ (after max_attempts exhausted)
dead ──► dead-letter queue
Dead letters can be inspected at /admin/dead and retried via /admin/intents/<id>/retry.
Two routing primitives that go beyond simple goal matching.
Send a job directly to one named machine — useful when a task must run on a particular device (a phone, a GPU node, a Pi with attached hardware).
client.publish(
goal="run_backup",
payload={"instruction": "/data"},
target_worker="termux-phone-1", # only this worker can claim it
)The worker declares its ID at claim time:
curl -X POST "https://dsecurity.pythonanywhere.com/claim?goal=run_backup" \
-H "X-API-KEY: key" \
-H "X-Worker-ID: termux-phone-1"Route jobs to any worker that advertises the right capability — useful when you have a mixed fleet and only some workers can handle a given task.
client.publish(
goal="transcribe_audio",
payload={"instruction": "meeting.mp3"},
required_capability="whisper",
)Workers declare their capabilities at claim time:
curl -X POST "https://dsecurity.pythonanywhere.com/claim" \
-H "X-API-KEY: key" \
-H "X-Worker-Capabilities: whisper,ffmpeg,gpu"Both fields can be combined. A job with target_worker and required_capability must satisfy both conditions before any worker can claim it.
- Trigger a phone notification when a cloud scraper finishes
- Deploy to a Raspberry Pi behind a firewall without opening ports
- Relay alerts to Discord from any script
- Replace fragile cron pipelines with loosely coupled workers
- Coordinate a heterogeneous worker fleet using capability matching
- Reliable Delivery — jobs retried with exponential backoff up to
max_attempts - Atomic Locking —
BEGIN IMMEDIATE+RETURNINGprevents double-claiming - Dead-Letter Queue — exhausted jobs archived for inspection and one-click retry
- Priority Scheduling — higher-priority intents always claimed before lower ones
- Namespace Isolation — partition workloads across logical domains
- Worker Targeting — route a job directly to a named worker
- Capability Matching — require specific capabilities for specialized tasks
- Delayed Execution — publish now, make claimable later with
delay - Result Storage — workers store structured results (json/text); publishers poll
/result/<id> - Idempotency Keys — safe publisher retries with no duplicate jobs
- Hybrid Visibility — private by default, optionally open to the whole namespace
- Rate Limiting — 60 req/min per tester key, enforced per API key
- Ephemeral KV Store —
/setand/getwith configurable TTL - Lazy Cleanup — triggered by traffic, no background thread required
- HMAC Signing — optional replay-protected auth, handled by v2.0 SDK
- Admin Dashboard — live queue stats and management at
/admin/dashboard - Prometheus Metrics —
/metricswith intent counts by status and namespace
- Jobs are never silently lost
- Only one worker can claim a job at a time
- Workers can crash safely — jobs are requeued after lease expiry
- Delivery is at-least-once — design workers to be idempotent
- Dead intents are archived, not deleted
- SQLite has single-writer contention under high concurrency
- Best for hundreds to low thousands of jobs per day with dozens of workers
- Not a replacement for Kafka or RabbitMQ at scale
- Upgrade path: swap SQLite for PostgreSQL — the change is isolated to
get_db()
Requirement: SQLite 3.35.0+ (for the atomic RETURNING clause)
python -c "import sqlite3; print(sqlite3.sqlite_version)"git clone https://github.com/dsecurity49/Intent-Bus.git
cd Intent-Bus
pip install -r server-requirements.txtgit clone https://github.com/dsecurity49/Intent-Bus
cd Intent-Bus
docker-compose up -d| Variable | Default | Description |
|---|---|---|
BUS_SECRET |
— | Main API key. Required in production. |
BUS_ADMIN_SECRET |
— | Admin token. Falls back to BUS_SECRET if unset. |
DASHBOARD_PASSWORD |
— | Basic auth password for the admin dashboard. |
BUS_DB_PATH |
infrastructure.db |
Path to the SQLite database file. |
BUS_REQUIRE_SIGNATURES |
false |
Require HMAC signing on all client requests. |
BUS_CLEANUP_INTERVAL_SECONDS |
21600 |
Seconds between automatic cleanup passes. |
| Method | Endpoint | Auth | Description |
|---|---|---|---|
POST |
/intent |
API key | Publish a job |
POST |
/claim |
API key | Claim a job |
POST |
/fulfill/<id> |
API key | Mark job complete (v2.0 requires result_type) |
POST |
/fail/<id> |
API key | Fail a job (triggers retry or dead) |
GET |
/result/<id> |
API key | Get stored result and full status |
POST |
/set/<key> |
API key | Set a KV store entry with TTL |
GET |
/get/<key> |
API key | Get a KV store entry |
POST |
/admin/cleanup |
Admin | Run cleanup manually, returns stats |
I wanted to trigger scripts on my Android phone from a cloud server — without Firebase, open ports, or complex infrastructure.
So I built a tiny job bus using Flask + SQLite. It worked. Then I kept going.
MIT