Anuva Python Server System Description
Purpose
This document describes the Anuva Python Server as a system and defines the current expected worker-facing Anuva Web App API contract that the Python Server should call.
It is intended to be used as implementation context for Codex and engineers working on the communication boundary between:
- The Anuva Web App / Payload backend
- The Anuva Python Server worker
- The Unity Video Generator running as a Windows EXE or in Unity Editor Play Mode
This document is based on:
docs/Anuva_Python_Server_Requirements.md- The current worker code under
src/anuva_python_server/webapp/ - The current simulator contract under
src/anuva_python_server/simulator/
Where the final Payload route layout is not yet fixed, this document proposes a worker-facing API contract that matches the current Python worker model and can be implemented behind any Payload route structure.
1. System Overview
The Anuva Python Server is a single-job orchestration worker that runs beside Unity on a Windows machine or Windows GPU VM.
Its responsibilities are:
- Poll the Web App for render jobs or expose available jobs for manual claiming
- Atomically claim exactly one job at a time
- Download the render package and referenced assets
- Rewrite the package into a deterministic local workspace
- Serve local asset/config URLs to Unity over localhost HTTP/HTTPS
- Launch or connect to Unity
- Send
render.startto Unity over ZeroMQ - Translate Unity progress/failure/completion back into Web App job updates
- Validate the rendered MP4
- Upload the rendered MP4 and generated thumbnail metadata
- Mark the job completed, failed, cancelled, or upload-failed
- Emit health and diagnostic information for operators
The Python Server is not responsible for:
- Generating presentation content
- Compiling presentation semantics
- Making camera or scene decisions
- Polling or mutating arbitrary Web App domain objects beyond worker-facing render operations
2. Runtime Boundaries
2.1 Web App Responsibilities
The Web App owns:
- Presentations, compilations, and source media
- Render job creation and queueing
- Atomic claim semantics
- Signed upload issuance and upload confirmation
- Final media record creation
- Operator-visible render status as the source of truth
2.2 Python Server Responsibilities
The Python Server owns:
- Worker lifecycle and configuration
- Job acquisition mode:
auto,manual, orhybrid - Local workspace preparation
- Unity supervision and ZeroMQ integration
- Output validation and upload execution
- Rich failure diagnostics
2.3 Unity Responsibilities
Unity owns:
- Consuming the local presentation config URL
- Downloading referenced local assets over localhost HTTP/HTTPS
- Executing the actual render
- Emitting
unity.ready,render.accepted,render.progress,render.completed, andrender.failed
3. Core System Invariants
The Web App API should preserve these invariants because the current worker code assumes them:
- V1 workers process one active job at a time.
- A job is not safe to process until the claim endpoint succeeds.
- Manual claim and automatic claim must use the same backend claim rules.
- Worker-visible statuses include:
queuedclaimedinProgressuploadinguploadFailedcompletedfailedcancelled- Job progress is modeled as a
floatin the range0.0to1.0. - Asset filenames in the package must be safe basenames, not paths.
- Asset IDs must be unique within one job package.
- The Web App is the source of truth for cancellation and terminal job state.
4. Current Python Worker Interface
The worker currently expects a Web App client with this shape:
get_next_queued_job(worker_id: str) -> RenderJob | None
list_available_jobs(worker_id: str, filters: RenderJobListFilters | None = None) -> list[RenderJobSummary]
get_job_summary(job_id: str) -> RenderJobSummary
claim_job(job_id: str, worker_id: str) -> RenderJob
manual_claim_job(job_id: str, worker_id: str) -> RenderJob
get_job_package(job_id: str) -> RenderJobPackage
update_job_status(job_id: str, status: str, progress: float | None = None, message: str | None = None) -> None
send_worker_heartbeat(worker_id: str, payload: WorkerHeartbeat) -> None
upload_render_result(job_id: str, file_path: Path, metadata: dict[str, Any]) -> UploadedMedia
create_signed_upload(job_id: str, filename: str, content_type: str, file_size_bytes: int, metadata: dict[str, Any]) -> dict[str, Any]
confirm_signed_upload(job_id: str, upload: dict[str, Any], metadata: dict[str, Any]) -> UploadedMedia
complete_job(job_id: str, result_media_id: str) -> None
fail_job(job_id: str, error_code: str, error_message: str, diagnostics: dict[str, Any] | None = None) -> None
cancel_job(job_id: str, reason: str) -> None
This is the most important contract to preserve.
5. Route Design: Current vs Recommended
The current code defaults to /api/worker/v1 when webapp.mode is real, and to /sim/... when webapp.mode is simulated. Those paths remain configurable through webapp.endpoints if the contract evolves again.
Recommended production namespace:
Suggested mapping:
| Operation | Simulator default client path | Real Web App default client path |
|---|---|---|
| Get next queued job | GET /sim/jobs/available?workerId=... |
GET /api/worker/v1/jobs/next?workerId=... |
| List available jobs | GET /sim/jobs/available |
GET /api/worker/v1/jobs/available |
| Get job summary | GET /sim/jobs/{jobId} |
GET /api/worker/v1/jobs/{jobId} |
| Claim job | POST /sim/jobs/{jobId}/claim |
POST /api/worker/v1/jobs/{jobId}/claim |
| Get package | GET /sim/jobs/{jobId}/package |
GET /api/worker/v1/jobs/{jobId}/package |
| Update status | PATCH /sim/jobs/{jobId}/status |
PATCH /api/worker/v1/jobs/{jobId}/status |
| Worker heartbeat | POST /sim/workers/{workerId}/heartbeat |
POST /api/worker/v1/workers/{workerId}/heartbeat |
| Direct upload | POST /sim/jobs/{jobId}/upload |
POST /api/worker/v1/jobs/{jobId}/upload |
| Create signed upload | POST /sim/jobs/{jobId}/uploads/signed |
POST /api/worker/v1/jobs/{jobId}/uploads/signed |
| Confirm signed upload | POST /sim/jobs/{jobId}/uploads/confirm |
POST /api/worker/v1/jobs/{jobId}/uploads/confirm |
| Complete job | POST /sim/jobs/{jobId}/complete |
POST /api/worker/v1/jobs/{jobId}/complete |
| Fail job | POST /sim/jobs/{jobId}/fail |
POST /api/worker/v1/jobs/{jobId}/fail |
| Cancel job | POST /sim/jobs/{jobId}/cancel |
POST /api/worker/v1/jobs/{jobId}/cancel |
6. Authentication and Headers
The current worker client sends:
The Web App API should therefore support:
- A worker-scoped bearer token from
ANUVA_WORKER_API_TOKEN - JSON request and response bodies for all metadata endpoints
- Multipart file upload only for the direct Payload upload fallback endpoint
The worker will not persist secrets in YAML config; it expects the token to come from environment variables.
7. Data Structures
All examples below use the current worker field names and JSON aliases.
7.1 RenderJob
This is the full worker-facing job object returned by claim operations and sometimes by "next job" operations.
{
"jobId": "job_001",
"workspaceId": "workspace_001",
"presentationId": "presentation_001",
"compilationId": "compilation_001",
"jobType": "finalRender",
"priority": "standard",
"status": "claimed",
"progress": 0.0,
"message": "claimed by worker",
"workerId": "unity-worker-01",
"claimedAt": "2026-05-21T10:00:00Z",
"updatedAt": "2026-05-21T10:00:00Z",
"errorCode": null,
"errorMessage": null,
"resultMediaId": null
}
Notes:
jobTypeis currentlyprevieworfinalRender.priorityis currentlystandard,priority, orfastest.progressshould be0.0..1.0.updatedAtshould always be present.
7.2 RenderJobSummary
This is the safe operator-facing job listing shape used by the dashboard/manual claim flow.
{
"jobId": "job_001",
"workspaceId": "workspace_001",
"presentationId": "presentation_001",
"compilationId": "compilation_001",
"jobType": "finalRender",
"priority": "standard",
"status": "queued",
"title": "Q2 Product Launch",
"createdAt": "2026-05-21T09:55:00Z",
"updatedAt": "2026-05-21T09:55:00Z",
"estimatedDurationSeconds": 75,
"assetCount": 14
}
This is intentionally smaller than the full package and should avoid leaking:
- Signed asset URLs
- Raw compiled presentation config
- Unnecessary private user data
7.3 RenderJobListFilters
Current worker-supported filters:
The Web App does not need to support every filter immediately, but it should safely ignore unsupported optional filters or document its supported subset.
7.4 RenderJobPackage
This is the most important payload. It is what the worker downloads after a successful claim.
{
"jobId": "job_001",
"workspaceId": "workspace_001",
"presentationId": "presentation_001",
"compilationId": "compilation_001",
"jobType": "finalRender",
"priority": "standard",
"config": {
"presentationId": "presentation_001",
"scenes": [],
"renderHints": {}
},
"assets": [
{
"assetId": "asset_img_001",
"kind": "image",
"url": "https://signed-or-public-url.example.com/file.png",
"filename": "slide-01.png",
"mimeType": "image/png",
"checksum": "sha256:abc123",
"required": true
}
],
"render": {
"resolution": "1080p",
"frameRate": 24,
"aspectRatio": "16:9",
"format": "mp4",
"watermark": false
}
}
Contract details:
configis opaque to the worker except for local rewriting and delivery to Unity.assets[].urlmay be signed or otherwise protected.assets[].filenamemust be a safe basename.assets[].assetIdmust be unique within the package.render.formatis currently expected to bemp4.
7.5 WorkerHeartbeat
Current worker heartbeat shape:
{
"status": "idle",
"activeJobId": null,
"details": {
"workerId": "unity-worker-01",
"mode": "exe",
"unityReady": true
}
}
details is intentionally flexible. The Web App should treat it as an extensible object and store it without requiring a rigid schema.
7.6 UploadedMedia
This is the normalized media record returned to the worker after upload finalization.
{
"mediaId": "media_001",
"url": "https://cdn.example.com/renders/presentation_001_final.mp4",
"filename": "presentation_001_final.mp4",
"mimeType": "video/mp4",
"fileSizeBytes": 184000000,
"metadata": {
"jobId": "job_001",
"workerId": "unity-worker-01"
}
}
The worker uses mediaId when calling complete_job.
8. Endpoint Contract Details
8.1 Get Next Queued Job
Response:
200 OKwith aRenderJob200 OKwithnull204 No Content
The current Python client also tolerates a list response and picks the first item, but a single object or null is cleaner for production.
8.2 List Available Jobs
Response: RenderJobSummary[]
This endpoint supports the local dashboard and manual claim flows.
8.3 Get Job Summary
Response: RenderJobSummary
The worker currently uses this endpoint mainly to detect cancellation of the active job.
8.4 Claim Job
POST /api/worker/v1/jobs/{jobId}/claim
Content-Type: application/json
{
"workerId": "unity-worker-01"
}
Response: RenderJob
Rules:
- Claim must be atomic.
- Only
queuedjobs are claimable. - If another worker already claimed the job, return
409 Conflict.
Recommended conflict response:
8.5 Get Job Package
Response: RenderJobPackage
This must be available after claim. It should represent the exact package to render and should not depend on additional hidden state in the worker.
8.6 Update Job Status
PATCH /api/worker/v1/jobs/{jobId}/status
Content-Type: application/json
{
"status": "inProgress",
"progress": 0.42,
"message": "Unity render in progress"
}
Rules:
progressis optional.messageis optional.- The Web App should validate transitions or at least reject obviously invalid terminal rewrites.
Recommended accepted statuses:
claimedinProgressuploadinguploadFailedcompletedfailedcancelled
8.7 Worker Heartbeat
POST /api/worker/v1/workers/{workerId}/heartbeat
Content-Type: application/json
{
"status": "rendering",
"activeJobId": "job_001",
"details": {
"unityConnected": true,
"unityMode": "editor",
"dashboardReachable": true
}
}
Response:
This endpoint should be lightweight and safe to call frequently.
8.8 Direct Payload Upload Fallback
Parts:
file: the MP4 bytesmetadata: JSON string
Example metadata:
{
"jobId": "job_001",
"workerId": "unity-worker-01",
"renderStartedAt": "2026-05-21T10:00:00Z",
"renderCompletedAt": "2026-05-21T10:05:00Z",
"durationSec": 72.4,
"frameCount": 1737,
"fileSizeBytes": 184000000,
"thumbnailPath": "C:\\Anuva\\Worker\\jobs\\job_001\\output\\thumbnail.png"
}
Response: UploadedMedia
8.9 Preferred Signed S3 Upload Flow
This is the current desired production flow.
Step 1: Create Signed Upload
POST /api/worker/v1/jobs/{jobId}/uploads/signed
Content-Type: application/json
{
"filename": "presentation_001_final.mp4",
"contentType": "video/mp4",
"fileSizeBytes": 184000000,
"metadata": {
"jobId": "job_001",
"workerId": "unity-worker-01"
}
}
Recommended response:
{
"uploadUrl": "https://s3-presigned-url.example.com/...",
"headers": {
"Content-Type": "video/mp4"
},
"storageKey": "renders/job_001/presentation_001_final.mp4",
"uploadId": "upload_001"
}
The Python worker already expects at least uploadUrl and optional headers.
Step 2: Worker uploads bytes directly to S3
The worker does a raw PUT to uploadUrl.
Step 3: Confirm Signed Upload
POST /api/worker/v1/jobs/{jobId}/uploads/confirm
Content-Type: application/json
{
"upload": {
"uploadUrl": "https://s3-presigned-url.example.com/...",
"headers": {
"Content-Type": "video/mp4"
},
"storageKey": "renders/job_001/presentation_001_final.mp4",
"uploadId": "upload_001"
},
"metadata": {
"jobId": "job_001",
"workerId": "unity-worker-01"
}
}
Response: UploadedMedia
The Web App should create or resolve the final media record during this confirmation step.
8.10 Complete Job
POST /api/worker/v1/jobs/{jobId}/complete
Content-Type: application/json
{
"resultMediaId": "media_001"
}
Behavior:
- Transition the job to
completed - Associate the uploaded media with the render job
- Update presentation-level pointers such as latest preview/final media where applicable
8.11 Fail Job
POST /api/worker/v1/jobs/{jobId}/fail
Content-Type: application/json
{
"errorCode": "UNITY_RENDER_TIMEOUT",
"errorMessage": "Unity render exceeded 1800s timeout",
"diagnostics": {
"workerId": "unity-worker-01",
"jobId": "job_001",
"state": "rendering",
"retrySafe": true,
"lastUnityEvent": {
"type": "render.progress"
},
"logPaths": [
"C:\\Anuva\\Worker\\jobs\\job_001\\logs\\python_job.log",
"C:\\Anuva\\Worker\\jobs\\job_001\\logs\\unity_job.log",
"C:\\Anuva\\Worker\\jobs\\job_001\\logs\\zmq_messages.jsonl"
],
"diagnosticsPath": "C:\\Anuva\\Worker\\jobs\\job_001\\diagnostics"
}
}
Important notes:
- The worker already classifies some failures as retry-safe.
- The diagnostics object is intentionally flexible and should be stored, not heavily normalized at the API boundary.
- Signed URLs and secrets should never be echoed back in diagnostics.
8.12 Cancel Job
POST /api/worker/v1/jobs/{jobId}/cancel
Content-Type: application/json
{
"reason": "cancelled_by_webapp"
}
The worker may also discover cancellation by polling GET /jobs/{jobId} and seeing status == cancelled.
9. Status and Transition Semantics
Recommended valid transitions:
queued -> claimed
queued -> cancelled
claimed -> inProgress
claimed -> failed
claimed -> cancelled
inProgress -> uploading
inProgress -> uploadFailed
inProgress -> failed
inProgress -> cancelled
uploading -> completed
uploading -> uploadFailed
uploading -> failed
uploading -> cancelled
uploadFailed -> uploading
uploadFailed -> failed
uploadFailed -> cancelled
Terminal statuses:
completedfailedcancelled
The worker already expects uploadFailed to exist as a first-class state.
10. End-to-End Call Sequences
10.1 Automatic Polling Flow
- Worker calls
GET /jobs/next - Worker calls
POST /jobs/{jobId}/claim - Worker calls
GET /jobs/{jobId}/package - Worker downloads package assets from asset URLs
- Worker calls
PATCH /jobs/{jobId}/statuswithinProgress - Worker renders through Unity and sends more
PATCH /statuscalls - Worker creates signed upload or uses direct upload
- Worker uploads MP4
- Worker calls
POST /jobs/{jobId}/complete
10.2 Manual Claim Flow
- Dashboard asks worker to list jobs
- Worker calls
GET /jobs/available - Operator selects a job
- Worker calls
POST /jobs/{jobId}/claim - Remaining lifecycle is identical to automatic mode
10.3 Cancellation Flow
- Web App marks job
cancelledor exposes cancellation through summary lookup - Worker notices cancellation while idle or rendering
- Worker sends
render.cancelto Unity if needed - Worker calls
POST /jobs/{jobId}/cancelif it is the actor finalizing local cancellation
11. Asset Delivery Expectations
The current system direction is:
- The Web App gives the worker a package containing asset URLs
- The worker downloads those assets locally
- The worker rewrites the config so Unity consumes localhost HTTP/HTTPS URLs
- Unity should not depend on the original remote URLs during render
This means the Web App package should be stable enough for the worker to fully materialize the job offline except for the final upload.
12. Error Handling Expectations
Recommended API behavior:
401or403for invalid worker auth404for unknown jobs or packages409for claim conflicts or invalid state transitions422for malformed request payloads503for temporary worker-facing backend outages
The worker already treats these categories as operationally meaningful, especially:
- Web App unavailable
- Claim conflict
- Invalid job package
- Upload failure
- Cancellation
13. Payload / Implementation Notes for the Web App Team
The production Web App does not have to match the Python simulator's route names, but it should preserve these behaviors:
- A thin worker-facing API should exist even if internal Payload collections are more complex.
- Claim logic should be server-side and atomic.
- The job package response should be fully renderable by the worker without extra implicit fetches.
- The upload confirmation endpoint should return a normalized media object with
mediaId. - The summary endpoint should be cheap enough to poll for cancellation.
- The API should tolerate extensible
detailsanddiagnosticsobjects.
If the Payload backend wants different route names, the Python worker can adapt through webapp.endpoints config as long as the operation semantics stay the same.
14. Recommended Minimal V1 Contract
If the Web App team wants the smallest useful first implementation, this is the minimal production-safe set:
GET /api/worker/v1/jobs/nextGET /api/worker/v1/jobs/availableGET /api/worker/v1/jobs/{jobId}POST /api/worker/v1/jobs/{jobId}/claimGET /api/worker/v1/jobs/{jobId}/packagePATCH /api/worker/v1/jobs/{jobId}/statusPOST /api/worker/v1/workers/{workerId}/heartbeatPOST /api/worker/v1/jobs/{jobId}/uploads/signedPOST /api/worker/v1/jobs/{jobId}/uploads/confirmPOST /api/worker/v1/jobs/{jobId}/completePOST /api/worker/v1/jobs/{jobId}/failPOST /api/worker/v1/jobs/{jobId}/cancel
This set matches the current Python worker architecture cleanly and supports:
- Automatic polling
- Manual dashboard claiming
- Unity progress reporting
- Direct-to-S3 upload
- Failure diagnostics
- Cancellation handling
15. Codex-Oriented Summary
If Codex is generating code for either side of this boundary, the key assumptions should be:
- The Python worker is a deterministic single-job orchestration service.
- The worker communicates with the Web App through a dedicated client abstraction.
- The Web App should expose a worker-specific render-job API, not generic CMS objects directly.
- The most important objects are
RenderJob,RenderJobSummary,RenderJobPackage,WorkerHeartbeat, andUploadedMedia. - Claim must be atomic, package retrieval must be explicit, and upload finalization must return a
mediaId. - The preferred upload flow is signed S3, with direct multipart upload retained only as a fallback.
uploadFailedis a first-class job state and should be implemented.