نسخهٔ وب مستقل — پلتفرم نیروی کار هوش مصنوعی
مخزن اختصاصی برنامهٔ وب؛ محتوای پوشهٔ web در ریشهٔ مخزن قرار گرفت. تاریخچهٔ پروژهٔ ربات (agy-bot) در این مخزن وجود ندارد و هیچ وابستگی runtime به آن نیست.
This commit is contained in:
@@ -0,0 +1,51 @@
|
||||
# `@agy/ai-runtime`
|
||||
|
||||
Drains the queue of tasks nobody has run yet, through the real run engine.
|
||||
|
||||
```bash
|
||||
pnpm --filter @agy/ai-runtime run # one pass, then exit
|
||||
pnpm --filter @agy/ai-runtime dev # poll every 30s
|
||||
```
|
||||
|
||||
## Why it exists
|
||||
|
||||
The API can start a run inline — that is what the «اجرا» button does. But a
|
||||
platform that *only* runs work on the request thread has two problems: a slow
|
||||
model call holds a connection open, and a task created by anything other than a
|
||||
button press (an automation, a seed script, a future integration) never runs at
|
||||
all. This worker is the second driver.
|
||||
|
||||
## What it does
|
||||
|
||||
1. Find tasks in `new` or `triaged` state, oldest first.
|
||||
2. Re-read each through `WorkforceService.get_task_or_404` — so the tenant check
|
||||
and the "already closed" guard both apply. **The worker gets no privileged
|
||||
bypass.**
|
||||
3. Hand it to `WorkforceService.start_run` — the exact call the router makes.
|
||||
4. Record the outcome and continue. One poisoned task must not block the queue
|
||||
behind it.
|
||||
|
||||
Model routing, usage accounting, the run timeline and the artifact are all the
|
||||
service's business; the worker owns none of it.
|
||||
|
||||
## Idempotency
|
||||
|
||||
A task that has been run is no longer in `new`/`triaged`, so a second pass finds
|
||||
nothing. Re-running a completed task would double-bill the customer, which is
|
||||
why the query filters on state rather than on "has a run".
|
||||
|
||||
## Implementation
|
||||
|
||||
`apps/api/app/workers/ai_runtime.py`. Tests:
|
||||
`apps/api/tests/test_workers.py`.
|
||||
|
||||
## Extracting this into its own process
|
||||
|
||||
```bash
|
||||
DATABASE_URL=... python -m app.workers ai-runtime --loop --interval 30
|
||||
```
|
||||
|
||||
Note that the run engine currently executes inline within the worker process. To
|
||||
make runs *asynchronous* for the customer (return `queued`, complete later), the
|
||||
next step is to have the API enqueue and this worker claim, rather than having
|
||||
the API call `start_run` directly. The port boundary for that already exists.
|
||||
@@ -0,0 +1,11 @@
|
||||
{
|
||||
"name": "@agy/ai-runtime",
|
||||
"version": "0.1.0",
|
||||
"private": true,
|
||||
"description": "AI runtime worker — drains queued tasks through the run engine.",
|
||||
"scripts": {
|
||||
"once": "cd ../../apps/api && .venv/bin/python -m app.workers ai-runtime",
|
||||
"dev": "cd ../../apps/api && .venv/bin/python -m app.workers ai-runtime --loop --interval 30",
|
||||
"lint": "cd ../../apps/api && .venv/bin/python -m ruff check app/workers"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,39 @@
|
||||
# `@agy/billing-worker`
|
||||
|
||||
Rolls the **usage ledger** into draft invoices — one invoice per organization
|
||||
per billing period.
|
||||
|
||||
```bash
|
||||
pnpm --filter @agy/billing-worker run # one pass, then exit (cron shape)
|
||||
pnpm --filter @agy/billing-worker dev # long-running loop
|
||||
```
|
||||
|
||||
## What it does
|
||||
|
||||
For every organization, for the requested period:
|
||||
|
||||
1. Skip the organization if the period already has an invoice (**idempotent** —
|
||||
a cron job that fires twice must not bill twice).
|
||||
2. Otherwise sum the usage ledger for the period.
|
||||
3. Skip if the total is zero — no `$0` drafts littering the table.
|
||||
4. Otherwise create a `draft` invoice carrying the ledger's total.
|
||||
|
||||
## What it deliberately does **not** do
|
||||
|
||||
- **It never invents an amount.** The ledger is the source of truth; the worker
|
||||
only snapshots it. See `docs/architecture/overview.md` §8.
|
||||
- **It never moves money.** Charging a card is `BillingService.settle_payment`,
|
||||
behind an authenticated API call — not a background job.
|
||||
|
||||
## Implementation
|
||||
|
||||
`apps/api/app/workers/billing.py`. The service wiring comes from
|
||||
`apps/api/app/application/container.py`, so this worker and the API compose
|
||||
services identically. Tests: `apps/api/tests/test_workers.py`.
|
||||
|
||||
## Extracting this into its own process
|
||||
|
||||
The worker already opens its own database sessions and holds no request state,
|
||||
so extraction is a deployment change, not a code change: run
|
||||
`python -m app.workers billing --loop` as its own process with the same
|
||||
`DATABASE_URL`, and point the cron/loop interval at your billing cycle.
|
||||
@@ -0,0 +1,11 @@
|
||||
{
|
||||
"name": "@agy/billing-worker",
|
||||
"version": "0.1.0",
|
||||
"private": true,
|
||||
"description": "Billing worker — rolls the usage ledger into draft invoices, once per period.",
|
||||
"scripts": {
|
||||
"once": "cd ../../apps/api && .venv/bin/python -m app.workers billing",
|
||||
"dev": "cd ../../apps/api && .venv/bin/python -m app.workers billing --loop",
|
||||
"lint": "cd ../../apps/api && .venv/bin/python -m ruff check app/workers"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,43 @@
|
||||
# `@agy/sandbox-gateway`
|
||||
|
||||
Housekeeping for the only sanctioned path for running untrusted code.
|
||||
|
||||
```bash
|
||||
pnpm --filter @agy/sandbox-gateway run # one pass, then exit
|
||||
pnpm --filter @agy/sandbox-gateway dev # poll every 10 minutes
|
||||
```
|
||||
|
||||
## What it does
|
||||
|
||||
1. **Reaps idle sessions.** A sandbox session nobody has touched for hours is
|
||||
still consuming CPU and disk on the provider. The worker stops it.
|
||||
2. **Audits the stop.** It writes a `sandbox.reap` audit event, because "the
|
||||
platform killed my sandbox" must be answerable from the audit log. A session
|
||||
whose workspace has vanished is still stopped — just recorded without an
|
||||
organization.
|
||||
3. **Reports the safety envelope** at startup: which provider is live, the
|
||||
command timeout, the output cap and the default egress policy. An operator
|
||||
should be able to see the limits without reading the source.
|
||||
|
||||
## What it deliberately does **not** do
|
||||
|
||||
**It never executes a command.** The only command path is
|
||||
`SandboxService.run_command`, reachable from the API under a tenant check. This
|
||||
worker only *stops* things.
|
||||
|
||||
## Implementation
|
||||
|
||||
`apps/api/app/workers/sandbox_gateway.py`; the state change itself lives in
|
||||
`SandboxService.reap_idle_sessions`, which is explicitly **not** tenant-scoped
|
||||
and therefore not reachable from any tenant-scoped route. Tests:
|
||||
`apps/api/tests/test_workers.py`.
|
||||
|
||||
## Extracting this into its own process
|
||||
|
||||
```bash
|
||||
DATABASE_URL=... python -m app.workers sandbox-gateway --loop --interval 600
|
||||
```
|
||||
|
||||
When a real provider (`SANDBOX_PROVIDER=daytona|e2b`) is configured, this is the
|
||||
process that should own provider-side teardown, so that a crash of the API does
|
||||
not leak sandboxes.
|
||||
@@ -0,0 +1,11 @@
|
||||
{
|
||||
"name": "@agy/sandbox-gateway",
|
||||
"version": "0.1.0",
|
||||
"private": true,
|
||||
"description": "Sandbox gateway worker — stops idle sandbox sessions and audits the stop.",
|
||||
"scripts": {
|
||||
"once": "cd ../../apps/api && .venv/bin/python -m app.workers sandbox-gateway",
|
||||
"dev": "cd ../../apps/api && .venv/bin/python -m app.workers sandbox-gateway --loop --interval 600",
|
||||
"lint": "cd ../../apps/api && .venv/bin/python -m ruff check app/workers"
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user