Archetype runs as a single archetype serve process. The API layer is a FastAPI application that exposes the service layer over HTTP. The CLI is a thin httpx client that talks to that server. All worlds live in one event loop, and both interfaces share the same ServiceContainer.
Application Factory¶
create_app() builds the FastAPI application with four routers and a lifespan manager:
@asynccontextmanager
async def lifespan(app: FastAPI):
container = get_container()
await container.world_service.discover_worlds() # rehydrate from registry
try:
yield
finally:
await container.shutdown()
set_container(None)
def create_app() -> FastAPI:
app = FastAPI(title="Archetype ECS", version="0.1.0", lifespan=lifespan)
app.include_router(worlds.router) # /worlds
app.include_router(commands.router) # /worlds/{id}/commands
app.include_router(simulation.router) # /worlds/{id}/step, /run
app.include_router(query.router) # /worlds/{id}/state, /entities, /components
return app
On startup, discover_worlds() reads the WorldRegistry and rehydrates any previously created worlds so state survives server restarts. On shutdown, all storage backends are flushed and closed.
Dependency Injection¶
deps.py holds a module-level singleton ServiceContainer and provides getter functions for FastAPI's Depends():
_container: ServiceContainer | None = None
_default_ctx = ActorCtx(id=uuid7(), roles={"admin"})
def get_container() -> ServiceContainer:
global _container
if _container is None:
_container = ServiceContainer(registry_path=default_registry_path())
return _container
def get_world_service() -> WorldService:
return get_container().world_service
def get_command_service() -> CommandService:
return get_container().command_service
# ... one getter per service
Route handlers inject services via Depends():
@router.post("/worlds/{world_id}/step")
async def step_world(
world_id: str,
sim: SimulationService = Depends(get_simulation_service),
):
...
The container is initialized lazily on the first get_container() call. The set_container(None) call during shutdown ensures a fresh container on restart.
Default Actor Context¶
In v0.1, there is no real auth. get_actor_ctx() returns a default ActorCtx with roles={"admin"}, granting wildcard permissions. Route handlers pass this context to CommandService.submit(), which forwards it to the broker's RBAC guardrails.
Route Structure¶
Routes are thin. They validate request payloads, construct Command objects, delegate to services, and return response models.
Worlds¶
| Endpoint | Method | What it does |
|---|---|---|
/worlds |
POST | Create world via CommandService (RBAC + audit), then apply immediately |
/worlds |
GET | List all managed worlds |
/worlds/{id} |
GET | Get world by ID |
/worlds/{id} |
DELETE | Remove world via CommandService |
/worlds/{id}/fork |
POST | Fork world via CommandService |
World mutations (create, remove, fork) go through CommandService.submit() for RBAC validation and audit logging, then apply_world_lifecycle() executes immediately -- world lifecycle commands are not tick-scheduled.
Commands¶
| Endpoint | Method | What it does |
|---|---|---|
/worlds/{id}/commands |
POST | Submit single command to broker |
/worlds/{id}/commands/batch |
POST | Submit batch (all-or-nothing RBAC) |
/worlds/{id}/commands |
GET | Get command history |
/worlds/{id}/commands/pending |
GET | Get pending command count |
Simulation¶
| Endpoint | Method | What it does |
|---|---|---|
/worlds/{id}/step |
POST | Execute one tick (drain + step) |
/worlds/{id}/run |
POST | Execute N ticks |
/worlds/{id}/processors |
GET | List active processors |
Query¶
| Endpoint | Method | What it does |
|---|---|---|
/worlds/{id}/state |
GET | World snapshot at optional tick |
/worlds/{id}/entities/{eid} |
GET | Single entity state |
/worlds/{id}/components |
GET | Query by component types |
/worlds/{id}/history |
GET | Command history (read path) |
See the REST API Reference for full request/response schemas.
Route Pattern¶
Every mutation route follows the same pattern. Here is create_world as an example:
@router.post("", response_model=WorldResponse)
async def create_world(
req: CreateWorldRequest,
cs: CommandService = Depends(get_command_service),
ctx: ActorCtx = Depends(get_actor_ctx),
):
# 1. Construct a Command from the request payload
cmd = Command(
type=CommandType.CREATE_WORLD,
payload={"config": {"name": req.name}, "storage_uri": req.storage_uri, ...},
)
# 2. Submit through the broker (RBAC + audit)
await cs.submit("__global__", cmd, ctx)
# 3. Apply (world lifecycle commands execute immediately)
world = await cs.apply_world_lifecycle(cmd)
# 4. Return response model
return WorldResponse(world_id=str(world.world_id), name=world.name, tick=0)
The route does not contain business logic. It translates HTTP into a Command, submits it for governance, and returns the result. The service layer handles everything else.
CLI¶
The CLI (archetype command) is a thin HTTP client built with Typer. It does not import the service layer.
archetype serve Starts uvicorn with the FastAPI app
archetype world create POST /worlds
archetype world list GET /worlds
archetype world inspect GET /worlds/{id}
archetype world fork POST /worlds/{id}/fork
archetype world remove DELETE /worlds/{id}
archetype step POST /worlds/{id}/step
archetype run POST /worlds/{id}/run
archetype query GET /worlds/{id}/state
archetype history GET /worlds/{id}/history
archetype status GET /worlds
Each command creates a fresh httpx.Client, makes one request, and exits. The server URL defaults to http://localhost:8000 and can be overridden with the ARCHETYPE_URL environment variable.
See the CLI Reference for full command documentation.
Why a Thin Client¶
This design means:
- All worlds share one event loop. The server process owns all simulation state. Multiple CLI invocations (or API clients) interact with the same worlds.
- The CLI is stateless. No local state to manage, no import of heavy dependencies.
- Distributed access. Point
ARCHETYPE_URLat a remote server and the CLI works identically.
Source Reference¶
- App factory:
src/archetype/api/app.py - Dependency injection:
src/archetype/api/deps.py - Request/response models:
src/archetype/api/models.py - Routes:
src/archetype/api/routes/worlds.py,commands.py,simulation.py,query.py - CLI:
src/archetype/cli/main.py