Implements HybridStateManager providing fast local Redis operations with background IPNS sync for eventual consistency across L1 nodes. - hybrid_state.py: Centralized state management (cache, claims, analysis, plans, runs) - Updated execute_cid.py, analyze_cid.py, orchestrate_cid.py to use state manager - Background IPNS sync (configurable interval, disabled by default) - Atomic claiming with Redis SETNX for preventing duplicate work Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Art Celery
L1 rendering server for the Art DAG system. Manages distributed rendering jobs via Celery workers.
Dependencies
- artdag (GitHub): Core DAG execution engine
- artdag-effects (rose-ash): Effect implementations
- Redis: Message broker, result backend, and run persistence
Setup
# Install Redis
sudo apt install redis-server
# Install Python dependencies
pip install -r requirements.txt
# Start a worker
celery -A celery_app worker --loglevel=info
# Start the L1 server
python server.py
Web UI
The server provides a web interface at the root URL:
| Path | Description |
|---|---|
/ |
Home page with server info |
/runs |
View and manage rendering runs |
/run/{id} |
Run detail page |
/recipes |
Browse and run available recipes |
/recipe/{id} |
Recipe detail page |
/media |
Browse cached media files |
/auth |
Receive auth token from L2 |
/auth/revoke |
Revoke a specific token |
/auth/revoke-user |
Revoke all tokens for a user (called by L2 on logout) |
/logout |
Log out |
/download/client |
Download CLI client |
Authentication
L1 servers authenticate users via L2 (the ActivityPub registry). No shared secrets are required.
Configuration
export L1_PUBLIC_URL=https://celery-artdag.rose-ash.com
How it works
- User clicks "Attach" on L2's Renderers page
- L2 creates a scoped token bound to this specific L1
- User is redirected to L1's
/auth?auth_token=... - L1 calls L2's
/auth/verifyto validate the token - L2 checks: token valid, not revoked, scope matches this L1
- L1 sets a local cookie and records the token
Token revocation
When a user logs out of L2, L2 calls /auth/revoke-user on all attached L1s. L1 maintains a Redis-based token tracking and revocation system:
- Tokens registered per-user when authenticating (
artdag:user_tokens:{username}) /auth/revoke-userrevokes all tokens for a username- Revoked token hashes stored in Redis with 30-day expiry
- Every authenticated request checks the revocation list
- Revoked tokens are immediately rejected
Security
- Scoped tokens: Tokens are bound to a specific L1. A stolen token can't be used on other L1 servers.
- L2 verification: L1 verifies every token with L2, which checks its revocation table.
- No shared secrets: L1 doesn't need L2's JWT secret.
API
Interactive docs: http://localhost:8100/docs
Endpoints
| Method | Path | Description |
|---|---|---|
| GET | / |
Server info |
| POST | /runs |
Start a rendering run |
| GET | /runs |
List all runs |
| GET | /runs/{run_id} |
Get run status |
| DELETE | /runs/{run_id} |
Delete a run |
| GET | /cache |
List cached content hashes |
| GET | /cache/{hash} |
Download cached content |
| DELETE | /cache/{hash} |
Delete cached content |
| POST | /cache/import?path= |
Import local file to cache |
| POST | /cache/upload |
Upload file to cache |
| GET | /assets |
List known assets |
| POST | /configs/upload |
Upload a config YAML |
| GET | /configs |
List configs |
| GET | /configs/{id} |
Get config details |
| DELETE | /configs/{id} |
Delete a config |
| POST | /configs/{id}/run |
Run a config |
Configs
Configs are YAML files that define reusable DAG pipelines. They can have:
- Fixed inputs: Assets with pre-defined content hashes
- Variable inputs: Placeholders filled at run time
Example config:
name: my-effect
version: "1.0"
description: "Apply effect to user image"
registry:
effects:
dog:
hash: "abc123..."
dag:
nodes:
- id: user_image
type: SOURCE
config:
input: true # Variable input
name: "input_image"
- id: apply_dog
type: EFFECT
config:
effect: dog
inputs:
- user_image
output: apply_dog
Start a run
curl -X POST http://localhost:8100/runs \
-H "Content-Type: application/json" \
-d '{"recipe": "dog", "inputs": ["33268b6e..."], "output_name": "my-output"}'
Check run status
curl http://localhost:8100/runs/{run_id}
Delete a run
curl -X DELETE http://localhost:8100/runs/{run_id} \
-H "Authorization: Bearer <token>"
Note: Failed runs can always be deleted. Completed runs can only be deleted if their outputs haven't been published to L2.
Delete cached content
curl -X DELETE http://localhost:8100/cache/{hash} \
-H "Authorization: Bearer <token>"
Note: Items that are inputs/outputs of runs, or published to L2, cannot be deleted.
Storage
- Cache:
~/.artdag/cache/(content-addressed files) - Runs: Redis db 5, keys
artdag:run:*(persists across restarts)
CLI Usage
# Render cat through dog effect
python render.py dog cat --sync
# Render cat through identity effect
python render.py identity cat --sync
# Submit async (don't wait)
python render.py dog cat
Architecture
server.py (L1 Server - FastAPI)
│
├── POST /runs → Submit job
│ │
│ ▼
│ celery_app.py (Celery broker)
│ │
│ ▼
│ tasks.py (render_effect task)
│ │
│ ├── artdag (GitHub) - DAG execution
│ └── artdag-effects (rose-ash) - Effects
│ │
│ ▼
│ Output + Provenance
│
└── GET /cache/{hash} → Retrieve output
Provenance
Every render produces a provenance record:
{
"task_id": "celery-task-uuid",
"rendered_at": "2026-01-07T...",
"rendered_by": "@giles@artdag.rose-ash.com",
"output": {"name": "...", "content_hash": "..."},
"inputs": [...],
"effects": [...],
"infrastructure": {
"software": {"name": "infra:artdag", "content_hash": "..."},
"hardware": {"name": "infra:giles-hp", "content_hash": "..."}
}
}