feat: add standards wiki, MCP registry, and app template

This commit is contained in:
2026-10-11 22:09:30 +05:30
parent 1ef2efbeda
commit 1208945e1d
155 changed files with 24512 additions and 7 deletions

View File

@@ -0,0 +1,95 @@
# Worker & Progress
## Summary
Long-running work runs in a background worker. Jobs are durable documents that
move through an explicit status machine. Progress is streamed to clients.
---
## The status machine
```
pending -> processing -> completed
\-> failed (retried up to N times)
\-> paused (waiting for human input)
```
- `pending` — queued, eligible for pickup.
- `processing` — claimed by a worker.
- `paused` — blocked on a human-in-the-loop question.
- `failed` — error; `retry_count` increments until a max, then skipped.
- `completed` — terminal success.
## The poll loop
```python
async def poll(resources):
backoff = MIN_POLL
while True:
jobs = await job_svc.search(filter={"status": "pending"}, limit=100)
backoff = MIN_POLL if jobs else min(backoff * 2, MAX_POLL)
for job in jobs:
if (job.retry_count or 0) >= MAX_RETRIES:
continue
await job_svc.update_status(job.id, "processing")
try:
await process(job, resources)
except PauseForResolution:
await job_svc.update_status(job.id, "paused")
except Exception as e:
await job_svc.update_status(
job.id, "failed", error=str(e),
retry_count=(job.retry_count or 0) + 1,
)
await asyncio.sleep(backoff)
```
Tunables live in environment variables: `WORKER_POLL_MIN`, `WORKER_POLL_MAX`,
`WORKER_MAX_RETRIES`.
## Progress events
A worker publishes structured progress events to a **bus** (a port). The HTTP
edge exposes a streaming endpoint that replays events for a given job id.
```python
class ProgressEvent(BaseModel):
job_id: str
step: str
status: str # started | progress | completed | skipped | paused
message: dict | None = None
```
The bus is an in-memory adapter by default; because it is a port, it can be
swapped for a durable broker. Emit from the orchestrator at each stage:
```python
await self.bus.publish(ProgressEvent(job_id=jid, step="normalize",
status="completed", message={"count": n}))
```
## Streaming endpoint
```python
async def stream_job_events(request, id):
bus = request.app.state.resources.get("progress_bus")
if bus is None:
return {"events": []}
async def gen():
sent = 0
while True:
events = bus.get_events(id)
for e in events[sent:]:
yield f"data: {e.model_dump_json(exclude_none=True)}\n\n"
sent += 1
await asyncio.sleep(0.5)
return StreamingResponse(gen(), media_type="text/event-stream")
```
> **Known limitation.** The default in-memory bus is per-process. If the worker
> and the API run as separate processes, events published by the worker are not
> visible to the API. For production, back the bus with a shared broker. The
> port boundary makes this a one-adapter change.