Skip to content

Latest commit

 

History

1 Commit

Folders and files

NameName
Last commit message
Last commit date
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

Embr × Foundry — Worker Sample

A FastAPI sample showing how to run a background worker alongside an Embr-hosted app, even though Embr doesn't currently expose a worker / cron / queue-consumer app type.

The job queue is a Redis Stream (XADD / XREADGROUP) backed by Embr's embedded Valkey primitive. Each enqueued prompt is consumed by a worker thread that calls Foundry's Responses API and writes the result back into Redis under result:{job_id}.

⚠️ Why this is a workaround

Embr's embr.yaml only describes web apps with a single HTTP listener. There's no worker: block, no schedule:, no queue:. So this sample:

  1. Starts a daemon thread (worker.start_worker_thread()) on FastAPI startup.
  2. Lets uvicorn keep the container "alive" — Embr's health check stays green.
  3. Exposes the worker's status via /health and the dashboard so you can see whether it's actually running and what it's done.

This pattern works, but it has real downsides — see Trade-offs below. The sample exists in part to motivate adding a real worker primitive to Embr.

Architecture

   ┌─────────────────────────────────────────────┐
   │ One Embr deployment (single container)      │
   │                                             │
   │ uvicorn app.main:app                        │
   │   └─ FastAPI (HTTP)                         │
   │       ├─ POST /api/jobs   ───┐              │
   │       ├─ GET  /api/jobs/{id} │              │
   │       ├─ GET  /health        │ XADD jobs    │
   │       └─ /                   │              │
   │                              ▼              │
   │   ┌──────────────────────────────────┐      │
   │   │ Daemon thread: worker_loop()     │      │
   │   │   XREADGROUP > → call Foundry    │      │
   │   │   → SET result:{id}              │      │
   │   └──────────────────────────────────┘      │
   │                                             │
   │   Embedded Valkey (cache.enabled: true)     │
   └─────────────────────────────────────────────┘
                       │
                       ▼
                   Foundry
              (Responses API)

Quickstart (local)

python3 -m venv .venv && source .venv/bin/activate
pip install -r requirements.txt
docker run -d --rm --name worker-redis -p 6380:6379 redis:7-alpine
export REDIS_URL=redis://localhost:6380/0
export FOUNDRY_BASE_URL=https://<your-foundry>.services.ai.azure.com/api/projects/<project>/openai/v1
export FOUNDRY_API_KEY=<your-key>
export FOUNDRY_MODEL_DEPLOYMENT=gpt-4.1-mini
uvicorn app.main:app --reload --port 8000

Open http://localhost:8000, submit a prompt, watch it go from queueddone.

Deploy to Embr

embr quickstart deploy embr-devs/embr-foundry-worker-sample
embr variables set FOUNDRY_BASE_URL "<...>" -p $PROJ -e $ENV
embr variables set FOUNDRY_API_KEY  "<...>" -p $PROJ -e $ENV --secret
embr variables set FOUNDRY_MODEL_DEPLOYMENT "<deployment>" -p $PROJ -e $ENV
embr deployments trigger -c HEAD -p $PROJ -e $ENV

cache.enabled: true provisions the embedded Valkey and injects CACHE_URL / REDIS_URL. No other primitive is needed.

Trade-offs of this workaround

Concern What's wrong
Independent scaling Worker concurrency is coupled to web concurrency. You can't scale workers without scaling the HTTP listener.
Failure semantics If the worker thread crashes but uvicorn keeps responding, Embr won't know. We surface last_error on /health to mitigate, but the platform won't act on it.
No retry primitive We do XAUTOCLAIM for stuck messages but there's no built-in DLQ, exponential backoff, or visibility timeout.
No cron Want to run a job every 15 minutes? You'd need to build that yourself with time.sleep in another thread.
Resource accounting CPU/RAM are counted against the web pod, not the worker.
Long jobs A job longer than the request timeout still works (it's not a request) but you lose any direct feedback channel — the client must poll.

A real worker primitive (worker: block in embr.yaml) would solve all of these.

What's worth poking at

  • Submit a long prompt and watch the dashboard poll until it flips to done.
  • Hit /health while jobs are in flight — processed and last_processed_at update live.
  • Submit several jobs quickly — they're all serialized through one consumer; this is a great place to ask "why doesn't Embr just give me 5 worker replicas?"
  • Kill the worker by embr deployments trigger'ing during a long job — the next worker should XAUTOCLAIM the stuck message after 60s of idle time.

About

Background worker on Embr — Redis Streams queue + Foundry processing. Demonstrates Embr's missing worker primitive.

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages