Infrastructure backend: protocol abstractions and community-tier implementations for akgentic
Project description
akgentic-infra
Status: Beta — community tier complete; department and enterprise tiers implemented in the sibling akgentic-infra-department and akgentic-infra-enterprise packages.
What is akgentic-infra?
Infrastructure backend for the Akgentic platform. It provides protocol abstractions that decouple the server and CLI from any specific deployment model, plus a complete set of community-tier implementations for single-process deployment. The department (akgentic-infra-department, Docker Compose) and enterprise (akgentic-infra-enterprise, Kubernetes/Dapr) tiers implement these same protocols for distributed deployment.
Three-Tier Architecture
| Capability | Community | Department | Enterprise |
|---|---|---|---|
| Auth | NoAuth (anonymous) |
OAuth2 + API key | OAuth2 + API key + SSO + RBAC |
| Placement | LocalPlacement |
HttpPlacement |
DaprPlacement (LabelMatch → Weighted → ZoneAware) |
| Worker lifecycle | LocalWorkerHandle |
HttpWorkerHandle |
DaprWorkerHandle |
| Team interaction | LocalTeamHandle |
HttpTeamHandle |
RemoteTeamHandle |
| Runtime cache | LocalRuntimeCache |
worker LocalRuntimeCache + server HttpRuntimeCache (no-op) |
worker LocalRuntimeCache + server RemoteRuntimeCache (no-op) |
| Persistence | YamlEventStore |
MongoDB | MongoDB + Dapr State |
| Health monitoring | None (single process) | RedisHealthMonitor |
DaprHealthMonitor |
| Recovery | None (single process) | MarkStoppedRecovery |
AutoRestoreRecovery / NotifyOnlyRecovery |
| Channels | YamlChannelRegistry |
MongoChannelRegistry |
DaprChannelRegistry |
| Worker discovery | N/A (same process) | HTTP via Redis-registered URLs | Dapr service invocation |
| Observability | Logfire (direct) | Logfire (direct) | Logfire + OTel Collector |
| Workspace storage | Local filesystem | Docker named volume | NFS / EFS |
Auth row — one contract, per-tier dispatch. The per-tier glosses above name only what differs (the credential sources a tier accepts). All three tiers implement the same async
AuthStrategy.resolve_request_usercontract thatakgentic-infraowns; community's is a trivial anonymous resolver. The contract, the sharedRequireAuthenforcement middleware, and therequire_team_accessresource-ownership gate are documented in Authentication contract & enforcement (per ADR-034) — this README is the canonical source; department / enterprise docs point here.
Community (single process)
graph TB
subgraph "Single Process"
API[FastAPI Server]
SVC[TeamService]
NA["NoAuth<br/><AuthStrategy>"]
CAT[Catalog API<br/>YAML backend]
LP["LocalPlacement<br/><PlacementStrategy>"]
LWH["LocalWorkerHandle<br/><WorkerHandle>"]
LRC["LocalRuntimeCache<br/><RuntimeCache>"]
TM[TeamManager]
AS[ActorSystem]
YE[YamlEventStore]
PS["PersistenceSubscriber<br/><EventSubscriber>"]
TS["TelemetrySubscriber<br/><EventSubscriber>"]
ICD["InteractionChannelDispatcher<br/><EventSubscriber>"]
ESS["EventStreamSubscriber<br/><EventSubscriber>"]
LES["LocalEventStream<br/><EventStream>"]
LI["LocalIngestion<br/><InteractionChannelIngestion>"]
YCR["YamlChannelRegistry<br/><ChannelRegistry>"]
end
subgraph Clients [" "]
direction LR
FE[Angular Frontend<br/>browser]
CLI[ak-infra CLI]
end
FE -->|REST + WS| API
CLI -->|REST + WS| API
API --> NA
API --> SVC
API --> CAT
API --> LI
API -->|WS: read stream| LES
SVC --> LP
SVC --> LWH
SVC --> LRC
LP --> TM
LWH --> TM
TM --> AS
TM --> YE
AS --> PS
AS --> TS
AS --> ICD
AS --> ESS
ESS --> LES
PS --> YE
LI --> SVC
LI --> YCR
style FE fill:#4CAF50,color:white
style API fill:#2196F3,color:white
style SVC fill:#FF9800,color:white
style TM fill:#FF9800,color:white
style LES fill:#F44336,color:white
Department (Docker Compose)
graph TB
subgraph Clients [" "]
direction LR
FE[Angular Frontend<br/>browser]
CLI[ak-infra CLI]
end
subgraph "Server Container"
SRV[FastAPI Server<br/>stateless]
SVC_S[TeamService]
AUTH["OAuth2 + API Key<br/><AuthStrategy>"]
PS_SRV["HttpPlacement<br/><PlacementStrategy>"]
HM["RedisHealthMonitor<br/><HealthMonitor>"]
RP["MarkStoppedRecovery<br/><RecoveryPolicy>"]
RWH["HttpWorkerHandle<br/><WorkerHandle>"]
RES_R["RedisEventStream<br/><EventStream>"]
CAT[Catalog API<br/>MongoDB backend]
end
subgraph "Worker 1"
W1_API[FastAPI Worker]
W1_LWH["LocalWorkerHandle<br/><WorkerHandle>"]
W1_TM[TeamManager]
W1_AS[ActorSystem]
W1_HB[Heartbeat Loop]
W1_PS["PersistenceSubscriber<br/><EventSubscriber>"]
W1_RSS["RedisStreamSubscriber<br/><EventSubscriber>"]
W1_RES["RedisEventStream<br/><EventStream>"]
W1_TS["TelemetrySubscriber<br/><EventSubscriber>"]
W1_ICD["InteractionChannelDispatcher<br/><EventSubscriber>"]
end
subgraph "Infrastructure"
MONGO[(MongoDB)]
REDIS[("Redis<br/>controller:{team_id}:events")]
end
%% Clients → Server
FE -->|REST + WS| SRV
CLI -->|REST + WS| SRV
%% Server-internal wiring
SRV --> AUTH
SRV --> SVC_S
SRV --> CAT
SRV -->|WS: subscribe| RES_R
SVC_S -->|create| PS_SRV
SVC_S -->|stop / delete / resume / get| RWH
HM -->|expired workers| RP
%% Server → Worker
PS_SRV -->|POST /teams create| W1_API
RWH -->|HTTP proxy| W1_API
%% Worker-internal wiring
W1_API -->|stop / delete / resume| W1_LWH
W1_API -->|create| W1_TM
W1_LWH --> W1_TM
W1_TM --> W1_AS
W1_AS --> W1_PS
W1_AS --> W1_RSS
W1_AS --> W1_TS
W1_AS --> W1_ICD
W1_RSS -->|append| W1_RES
%% → Infrastructure
CAT --> MONGO
W1_PS --> MONGO
RES_R -->|XREAD / XRANGE| REDIS
W1_RES -->|XADD| REDIS
PS_SRV -->|find worker| REDIS
RWH -->|locate team| REDIS
HM -->|check heartbeat| REDIS
W1_HB -->|heartbeat TTL| REDIS
style FE fill:#4CAF50,color:white
style SRV fill:#2196F3,color:white
style SVC_S fill:#FF9800,color:white
style W1_API fill:#FF9800,color:white
style MONGO fill:#4CAF50,color:white
style REDIS fill:#F44336,color:white
style RES_R fill:#F44336,color:white
style W1_RES fill:#F44336,color:white
Enterprise (Kubernetes / Dapr)
graph TB
subgraph "Ingress"
ING[Ingress Controller<br/>TLS]
end
subgraph "Server Pod"
SRV[FastAPI Server<br/>stateless]
SVC_E[TeamService]
AUTH["OAuth2 + API Key + SSO + RBAC<br/><AuthStrategy>"]
CAT[Catalog API<br/>MongoDB backend]
PS_SRV["DaprPlacement · LabelMatch / Weighted / ZoneAware<br/><PlacementStrategy>"]
RWH_E["DaprWorkerHandle<br/><WorkerHandle>"]
DSR[DaprStateServiceRegistry]
HM_E["DaprHealthMonitor<br/><HealthMonitor>"]
RP_E["AutoRestoreRecovery<br/><RecoveryPolicy>"]
DES_R["DaprEventStream<br/><EventStream>"]
SRV_DAPR[Dapr Sidecar]
end
subgraph "Worker Pod 1"
W1_API[FastAPI Worker]
W1_TM[TeamManager]
W1_AS[ActorSystem]
W1_PS["PersistenceSubscriber<br/><EventSubscriber>"]
W1_DSS["DaprStreamSubscriber<br/><EventSubscriber>"]
W1_DES["DaprEventStream<br/><EventStream>"]
W1_TS["TelemetrySubscriber<br/><EventSubscriber>"]
W1_ICD["InteractionChannelDispatcher<br/><EventSubscriber>"]
W1_DAPR[Dapr Sidecar]
end
subgraph "Worker Pod N"
WN_API[FastAPI Worker]
WN_DAPR[Dapr Sidecar]
end
subgraph "Infrastructure"
MONGO[(MongoDB)]
OTEL[OTel Collector]
end
subgraph "Dapr Components"
PUBSUB["Pub/Sub<br/>Redis / NATS / Kafka"]
STATE[State Store<br/>Redis / PostgreSQL / Cosmos DB]
end
ING --> SRV
SRV --> AUTH
SRV --> SVC_E
SRV --> CAT
SVC_E -->|create| PS_SRV
SVC_E -->|stop / delete / resume / get| RWH_E
PS_SRV --> DSR
DSR --> SRV_DAPR
RWH_E --> SRV_DAPR
SRV_DAPR -->|invoke POST /teams create| W1_DAPR
SRV_DAPR -->|invoke POST /teams create| WN_DAPR
SRV_DAPR -->|invoke stop / delete / resume / get| W1_DAPR
SRV_DAPR --> STATE
SRV -->|WS: subscribe| DES_R
DES_R -->|subscribe| SRV_DAPR
HM_E -->|check health| SRV_DAPR
HM_E -->|expired workers| RP_E
W1_DAPR --> W1_API
WN_DAPR --> WN_API
W1_API --> W1_TM
W1_TM --> W1_AS
W1_AS --> W1_PS
W1_AS --> W1_DSS
W1_AS --> W1_TS
W1_AS --> W1_ICD
W1_PS --> MONGO
W1_DSS -->|append| W1_DES
W1_DES -->|publish| W1_DAPR
W1_DAPR --> PUBSUB
W1_TS --> OTEL
CAT --> MONGO
style ING fill:#9C27B0,color:white
style SRV fill:#2196F3,color:white
style SVC_E fill:#FF9800,color:white
style W1_API fill:#FF9800,color:white
style WN_API fill:#FF9800,color:white
style MONGO fill:#4CAF50,color:white
style PUBSUB fill:#F44336,color:white
style STATE fill:#F44336,color:white
style DES_R fill:#F44336,color:white
style W1_DES fill:#F44336,color:white
style OTEL fill:#607D8B,color:white
style SRV_DAPR fill:#E91E63,color:white
style W1_DAPR fill:#E91E63,color:white
style WN_DAPR fill:#E91E63,color:white
Source Layout
src/akgentic/infra/
protocols/ Protocol definitions (the contracts)
auth.py AuthStrategy
placement.py PlacementStrategy
worker_handle.py WorkerHandle
team_handle.py TeamHandle
runtime_cache.py RuntimeCache
channels.py InteractionChannelAdapter, Ingestion, Parser, Registry
health.py HealthMonitor
recovery.py RecoveryPolicy
adapters/ Protocol implementations
community/ Single-process adapters (NoAuth, LocalPlacement, etc.)
shared/ Tier-agnostic adapters (Telegram, telemetry, WebSocket)
server/ FastAPI application
routes/ REST, WebSocket, webhook, and frontend adapter routes
services/ TeamService (tier-agnostic orchestrator)
settings.py Pydantic-settings configuration classes
state_keys.py Typed app.state key declarations (server tier)
app.py Application factory (create_app)
cli/ Typer-based CLI (ak-infra)
utils.py StateKey[T] — typed app.state handle factory
wiring.py Dependency injection — wires adapters into services
worker/ Worker module (planned for department/enterprise tiers)
state_keys.py Typed app.state key declarations (worker tier)
Quick Start
1. Start the server (from the akgentic-framework root):
# src/infra_server.py
from pathlib import Path
import uvicorn
from akgentic.infra.server.app import create_app
from akgentic.infra.server.settings import CommunitySettings
from akgentic.infra.wiring import wire_community
settings = CommunitySettings(catalog_path=Path("./src/catalog"))
services = wire_community(settings)
app = create_app(services, settings)
if __name__ == "__main__":
uvicorn.run(app, host=settings.host, port=settings.port, timeout_graceful_shutdown=1)
python src/infra_server.py
2. Connect with the CLI (in a second terminal):
# Create a team from the catalog and open the chat TUI
ak-infra chat --create agent-team
Protocols
These are the contracts that department/enterprise tiers must implement. All use structural subtyping (typing.Protocol) — no inheritance required.
The Used in column refers to the role in the distributed (department / enterprise) tiers; in the community tier the server and worker run in a single process.
| Protocol | File | Abstracts | Used in |
|---|---|---|---|
PlacementStrategy |
placement.py |
Worker selection and team creation | Server |
WorkerHandle |
worker_handle.py |
Team stop / delete / resume / get | Both — server-side remote handle delegates to the worker's local handle |
TeamHandle |
team_handle.py |
Send messages, route human input, subscribe | Both — server-side remote handle delegates to the worker's local handle |
RuntimeCache |
runtime_cache.py |
Map team IDs to live TeamHandle instances | Both — real cache on the worker, stateless no-op resolver on the server |
AuthStrategy |
auth.py |
Async resolve_request_user(connection) -> RequestUser (raises 401) + get_auth_routes — see Authentication contract & enforcement |
Server |
InteractionChannelAdapter |
channels.py |
Outbound message delivery to external channels | Worker — runs in the orchestrator's actor thread |
InteractionChannelIngestion |
channels.py |
Inbound webhook routing to teams | Server |
ChannelParser |
channels.py |
Parse channel-specific webhook payloads | Server |
ChannelRegistry |
channels.py |
Map external channel users to active teams | Server |
EventStream |
event_stream.py |
Tier-agnostic event streaming with replay and fan-out (ADR-010) | Both — worker appends, server reads / fans out |
StreamReader |
event_stream.py |
Cursor-based blocking reader for a team's event stream | Server — read side of the WebSocket fan-out |
HealthMonitor |
health.py |
Worker liveness detection | Server |
RecoveryPolicy |
recovery.py |
Recovery behavior on worker failure | Server |
Server Architecture
The server is built around a tier-agnostic TeamService that delegates all infrastructure concerns to protocol implementations. The create_app() factory wires everything together.
REST API
| Method | Path | Description |
|---|---|---|
POST |
/teams/ |
Create a team from a catalog entry |
GET |
/teams/ |
List all teams |
GET |
/teams/{team_id} |
Get team metadata |
DELETE |
/teams/{team_id} |
Stop and delete a team |
POST |
/teams/{team_id}/message |
Send a message to a running team |
POST |
/teams/{team_id}/human-input |
Provide human input to an agent |
POST |
/teams/{team_id}/stop |
Stop a team (preserve data) |
POST |
/teams/{team_id}/restore |
Restore a stopped team |
GET |
/teams/{team_id}/events |
Get persisted events |
GET |
/workspace/{team_id}/tree |
List workspace files |
GET |
/workspace/{team_id}/file |
Read a workspace file |
POST |
/workspace/{team_id}/file |
Upload a file to workspace |
WS |
/ws/{team_id} |
Real-time event stream |
POST |
/webhook/{channel} |
Inbound channel webhook |
Catalog endpoints are mounted under /catalog/ and provided by akgentic-catalog.
Authentication contract & enforcement
Authentication is one tier-agnostic contract that akgentic-infra owns, plus a shared enforcement mechanism the tiers compose. Per ADR-034 (_bmad-output/akgentic-infra/decisions/adr-034-tier-agnostic-auth-contract.md — its current-vs-Design-D diagrams show the before/after assembly, the one-contract/one-mechanism target, the twice-vs-once request flow, and the ownership table), a tier no longer hand-wires its own copy of the auth assembly; it implements the resolver and composes the building block.
The contract — AuthStrategy (protocols/auth.py). A @runtime_checkable Protocol with one async resolver:
async def resolve_request_user(self, connection: HTTPConnection) -> RequestUser: ... # raises HTTPException(401)
def get_auth_routes(self) -> list[BaseRoute]: ... # community returns []
The boundary speaks the neutral infra RequestUser ({user_id, email, roles}, server/auth.py); a tier's richer identity type (e.g. an AuthenticatedUser carrying name/auth_method) is projected to RequestUser inside the resolver, not at a separate per-tier seam. The contract is async-native — there is no synchronous entry point (removed in Story 40.1). A tier that fails to implement the resolver fails isinstance(..., AuthStrategy) and the shared contract test, so the half-wiring that produced the enterprise /admin/catalog/* 401 becomes structurally impossible to ship silently.
The shared RequireAuth building block (server/middleware/require_auth.py). One ASGI middleware (RequireAuthMiddleware) that, per non-OPTIONS / non-allowlisted http/websocket scope:
- awaits
services.auth.resolve_request_user(connection)exactly once, - stashes the resolved
RequestUseronrequest.state.request_user(the same stash the gate, the caller-identity scope, and the mutation-log audit all read), and - on a raising resolver, rejects pre-routing — WebSocket close
1008, else aJSONResponse401.
It is parameterized by the allowlists the tier supplies: exact_allowlist (default frozenset({"/readiness"})) and prefix_allowlist (default ("/auth/",)).
Override seam (bounded extensibility). The block is pluggable at the edges only:
requires_principal(connection) -> bool— a tier predicate (richer than the static allowlists) that exempts paths authenticated by a different mechanism (e.g. an HMAC-verified signed-webhook or Dapr fan-out path) without treating them as anonymous.on_reject(connection, exc) -> Response— the tier shapes its own HTTP 401 (JSON vs redirect-to-login,WWW-Authenticateheader, etc.). The WebSocket1008close is fixed.- Guarded escape hatch — a tier MAY supply a wholly custom middleware only if it passes the shared stash-contract test (resolve once → stash
request.state.request_user→ 401-on-raise pre-routing).
The load-bearing invariant — resolve-once + stash key + 401-on-raise pre-routing — is never overridable; only the edges are.
The seam reads the stash; the gate is unchanged. get_request_user (server/auth.py) returns the stashed RequestUser when the middleware populated it, else the community anonymous default (RequestUser(user_id="anonymous") — never None, never raises). Auth therefore runs once per request, not twice. The catalog gate require_authenticated_principal keeps Depends(get_request_user) and still never 401s on its own — the strategy raises 401, the shared middleware is the pre-routing 401 path.
require_team_access — resource-ownership authorization (server/routes/_team_access.py). A per-route Depends (authorization, not authentication — middleware has no route/param knowledge) that resolves the team Process by team_id via the team-access seam (get_team_service → TeamService.get_team) and allows iff process.user_id == principal.user_id OR "admin" in principal.roles; otherwise it raises 404 (404-over-403 — no existence leak). It is mounted on the per-team_id routes (GET/DELETE /teams/{id}, POST /teams/{id}/message, GET /teams/{id}/events) and mirrors ADR-028's require_namespace_owner_or_admin. The check and the team-access seam are infra-owned; the RBAC role vocabulary and enterprise's tenant intersection stay tier-side.
Per-tier wiring (infra-owned vs tier-owned).
| Tier | Resolver | Middleware |
|---|---|---|
Community (NoAuth) |
trivial anonymous — returns RequestUser(user_id="anonymous"), never raises; get_auth_routes → [] |
mounts none (nothing to enforce) — behaviour byte-unchanged |
| Department | implements resolve_request_user (its credential dispatch, projecting to RequestUser) |
composes the shared RequireAuth block into its own stack with its own allowlists |
| Enterprise | implements resolve_request_user (its credential dispatch + tenant scoping) |
composes the shared RequireAuth block into its own stack with its own allowlists |
Infra owns the AuthStrategy contract, the RequireAuth building block, the stash + get_request_user seam, and require_team_access. Tiers own their credential dispatch (which sources, in what priority), their middleware-stack composition / layer ordering, their allowlist contents, and their RBAC role vocabulary. Department / enterprise document only their own composition and allowlists; they point here for the contract.
Running with real authentication (licensed). The community tier ships anonymous (auth_strategy="noauth", the default). To run it with real auth, install akgentic-infra-auth — a separately-licensed, non-open-source plugin — into the same environment as akgentic-infra from a private index, direct URL, or vendored wheel (not public PyPI, and not an akgentic-infra[auth] extra — infra's public metadata never names the private package). The plugin registers a zero-argument factory under the akgentic.infra.auth.strategies entry-point group; the operator then sets auth_strategy="oidc" (the plugin's registered name). The factory reads its own configuration (OIDC issuer, client id/secret, backing-store connection strings) — infra passes it no arguments, and CommunitySettings carries only the selector string, never auth-provider fields. Resolution is fail-closed: until the plugin registers its entry point, any non-"noauth" selector fails loud at wire time (UnknownAuthStrategyError, empty discoverable list) — never a silent anonymous fallback. So the community + real-auth path is present but becomes operational only once the licensed plugin registers that entry point (a separate akgentic-infra-auth follow-up). See ADR-037.
Namespace proximity — akgentic.infra.auth is the plugin's, not infra's. The plugin's akgentic.infra.auth namespace merges into infra's shared akgentic.infra.* namespace via pkgutil.extend_path, so it sits beside the infra-owned akgentic.infra.server.auth and akgentic.infra.protocols.auth — but it is not infra-owned. Infra does not depend on, import, or ship the plugin; the entry-point group is the only seam between them.
Frontend Adapter Plugin
An optional plugin system for translating API responses to legacy frontend formats. Configured via AKGENTIC_FRONTEND_ADAPTER (FQDN of the adapter class). When absent, the server serves the native V2 API only.
Shared Adapters
Tier-agnostic adapters that work across community, department, and enterprise deployments:
| Adapter | Description |
|---|---|
InteractionChannelDispatcher |
Per-team outbound message dispatcher — routes SentMessage events to registered channel adapters |
TelegramChannelAdapter |
Delivers outbound messages via the Telegram Bot API |
TelegramChannelParser |
Parses inbound Telegram webhook payloads |
ChannelParserRegistry |
Resolves and holds channel parsers/adapters from config |
EventStreamSubscriber |
Event subscriber that routes orchestrator events to the team's EventStream |
RuntimeCacheEvictionSubscriber |
Event subscriber that evicts a stopped team's handle from the worker's RuntimeCache |
TelemetrySubscriber |
Event subscriber that traces messages via Logfire |
Typed app.state access (StateKey[T])
create_app() stores its wired services on FastAPI's app.state so routes can reach them. app.state is a starlette.datastructures.State whose attribute reads are typed Any, so routes used to cast(...) every read. StateKey[T] (see ADR-030 — Typed app.state Access via a StateKey[T] Registry) replaces that with a typed, serialization-free handle to one slot. The API is three calls:
KEY.set(source, value)— the producer writes the slot.KEY.get(source) -> T | None— soft read; returns the key'sdefaultwhen the slot is unset (or raisesLookupErrorif the key isrequired=True).KEY.require(source) -> T— loud read; never returnsNone(raisesLookupErrorwhen unset/None).
source may be a FastAPI, Request, or WebSocket. A key is declared once as a module-level constant — that declaration is the registration; there is no central registry. StateKey("name", *, default=..., required=...) is the full constructor.
Producer / consumer. create_app() (the producer) sets each slot through its key, and routes (the consumers) read the same key handle:
# producer — server/app.py
SERVICES.set(app, services)
TEAM_SERVICE.set(app, team_service)
# consumer — server/routes/teams.py
team_service = TEAM_SERVICE.require(request)
Soft defaults. A key declared with a default reads that default back when its slot was never set: CHANNEL_PARSERS and FRONTEND_ADAPTER default to None, DRAINING defaults to False. So CHANNEL_PARSERS.get(request) returns ChannelParserRegistry | None without any getattr(..., None) at the call site.
Depends bridge. DI-shaped handlers wrap the same key in a one-line provider — no second source of truth:
def get_team_service(request: Request) -> TeamService:
return TEAM_SERVICE.require(request)
Key lives with its producer. Server keys are declared in server/state_keys.py, worker keys in worker/state_keys.py — each in the package that writes the slot. Both tiers export a SERVICES key, but they are different keys typed to different containers (TierServices server-side, WorkerServices worker-side); the worker route imports its own (from akgentic.infra.worker.state_keys import SERVICES). Department and enterprise tiers adopt these keys on their own branches/PRs — a tracked follow-up (see _bmad-output/akgentic-infra-department/migration-plan-lift-shared-auth-and-http-helpers-to-akgentic-infra.md); the coexistence with the older cast/getattr style during that rollout is intentional.
CLI
The ak-infra command provides a terminal interface to the server.
Team management
ak-infra team list # List all teams
ak-infra team get <team_id> # Show team detail
ak-infra team create <catalog_entry> # Create a team
ak-infra team delete <team_id> # Delete a team
ak-infra team restore <team_id> # Restore a stopped team
ak-infra team events <team_id> # Show team events
Messaging
ak-infra message <team_id> <content> # Send a message
ak-infra reply <team_id> <content> --message-id <id> # Reply to agent request
ak-infra chat [TEAM_ID] # Interactive REPL
ak-infra chat --create <catalog_entry> # Create + chat
Workspace
ak-infra workspace tree <team_id> # List files
ak-infra workspace read <team_id> <path> # Read a file
ak-infra workspace upload <team_id> <local_path> # Upload a file
REPL Commands
Inside ak-infra chat, use / for slash commands:
| Command | Description |
|---|---|
/help |
Show available commands |
/status |
Show team status |
/agents |
List team agents |
/history [N] |
Show recent messages |
/files |
Show workspace files |
/read <path> |
Read a workspace file |
/upload <path> |
Upload a file |
/stop |
Stop the team |
/restore |
Restore a stopped team |
/switch <team_id> |
Switch to another team |
Global Options
ak-infra --server http://localhost:8000 # Server URL (default)
ak-infra --api-key <key> # Credential for auth (see below)
ak-infra --format table|json # Output format
--api-key accepts either credential type and routes it to the correct
header automatically: a structured API key (the ak_<id>_<secret> form
issued by api-key bootstrap / POST /auth/apikeys) is sent as
X-API-Key, while any other value is treated as a pre-resolved OIDC
bearer token and sent as Authorization: Bearer.
Configuration
All settings are loaded from environment variables prefixed with AKGENTIC_.
Server Settings (all tiers)
| Variable | Default | Description |
|---|---|---|
AKGENTIC_HOST |
0.0.0.0 |
Bind address |
AKGENTIC_PORT |
8000 |
Port number |
AKGENTIC_LOG_LEVEL |
INFO |
Log level (DEBUG, INFO, WARNING, ERROR, CRITICAL). Invalid values fall back to INFO. |
AKGENTIC_CORS_ORIGINS |
["*"] |
Allowed CORS origins (JSON list) |
AKGENTIC_FRONTEND_ADAPTER |
None |
Frontend adapter plugin FQDN |
Community Settings (extends server)
| Variable | Default | Description |
|---|---|---|
AKGENTIC_WORKSPACES_ROOT |
workspaces |
Root directory for team workspace storage |
AKGENTIC_EVENT_STORE_PATH |
data/event_store |
Root directory for event store persistence |
AKGENTIC_CATALOG_PATH |
data/catalog |
Catalog directory for team/agent/tool/template definitions |
AKGENTIC_CHANNEL_REGISTRY_PATH |
None |
Path to channel registry YAML; disabled when unset |
Installation
Within Monorepo Workspace
# From workspace root
source .venv/bin/activate
# Package is already installed in editable mode via workspace
# No additional installation needed
Standalone Package
cd packages/akgentic-infra
uv venv
source .venv/bin/activate
uv pip install -e ".[dev]"
Development
# Run all tests
pytest packages/akgentic-infra/tests/
# Run integration tests (requires API keys in .env)
pytest packages/akgentic-infra/tests/integration/ -m integration
# Type checking (strict mode)
mypy packages/akgentic-infra/src/
# Lint
ruff check packages/akgentic-infra/src/
# Format
ruff format packages/akgentic-infra/src/
Coverage target: 90% (higher than other packages at 80%).
Test Markers
| Marker | Description |
|---|---|
integration |
Full server flow tests requiring real LLM and API keys |
llm |
Tests requiring LLM API keys (auto-skipped when OPENAI_API_KEY is absent) |
smoke |
End-to-end smoke tests using TestModel (no API key required) |
e2e |
Real end-to-end tests requiring a running server and OPENAI_API_KEY |
By default, integration tests are excluded (-m 'not integration'). Run them explicitly:
pytest packages/akgentic-infra/tests/ -m integration
Dependencies
Akgentic packages
akgentic-core, akgentic-team, akgentic-catalog, akgentic-agent, akgentic-llm, akgentic-tool
Third-party
| Package | Purpose |
|---|---|
fastapi |
HTTP server framework |
pydantic-settings |
Environment-based configuration |
typer |
CLI framework |
rich |
Terminal rendering |
httpx |
HTTP client (CLI to server) |
websockets |
WebSocket client and server |
pyyaml |
YAML persistence (event store, catalog) |
logfire |
Observability and logging |
Project details
Release history Release notifications | RSS feed
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file akgentic_infra-1.8.0.tar.gz.
File metadata
- Download URL: akgentic_infra-1.8.0.tar.gz
- Upload date:
- Size: 414.9 kB
- Tags: Source
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/7.0.0 CPython/3.13.14
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
06b3d100c1560aca7df4faa7499b6e51c45a6212f68200b43a6583993911a022
|
|
| MD5 |
75d2f2a60a9d1d0a9654054a38dcb8c7
|
|
| BLAKE2b-256 |
8782c111aeabdccd1d455735901d43a3e17dc263ddaa458b3ec69377098b1d8a
|
Provenance
The following attestation bundles were made for akgentic_infra-1.8.0.tar.gz:
Publisher:
publish-pypi.yml on b12consulting/akgentic-framework
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
akgentic_infra-1.8.0.tar.gz -
Subject digest:
06b3d100c1560aca7df4faa7499b6e51c45a6212f68200b43a6583993911a022 - Sigstore transparency entry: 2340494487
- Sigstore integration time:
-
Permalink:
b12consulting/akgentic-framework@a92a0fc36ee2246b201b69a05a61dee8baaa7710 -
Branch / Tag:
refs/heads/master - Owner: https://github.com/b12consulting
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
publish-pypi.yml@a92a0fc36ee2246b201b69a05a61dee8baaa7710 -
Trigger Event:
workflow_dispatch
-
Statement type:
File details
Details for the file akgentic_infra-1.8.0-py3-none-any.whl.
File metadata
- Download URL: akgentic_infra-1.8.0-py3-none-any.whl
- Upload date:
- Size: 231.0 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/7.0.0 CPython/3.13.14
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
005be75d7e3c0d1576a025803557e7583878f6b005cd439c1186da40bc8cc1d1
|
|
| MD5 |
3d8f46d7a1dd4e99a93b278f93fc9c1d
|
|
| BLAKE2b-256 |
08abe0bdce74fdfc10941c15f72127cb0d7ba97df05d5a2bcce55dfd1a9ea406
|
Provenance
The following attestation bundles were made for akgentic_infra-1.8.0-py3-none-any.whl:
Publisher:
publish-pypi.yml on b12consulting/akgentic-framework
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
akgentic_infra-1.8.0-py3-none-any.whl -
Subject digest:
005be75d7e3c0d1576a025803557e7583878f6b005cd439c1186da40bc8cc1d1 - Sigstore transparency entry: 2340494491
- Sigstore integration time:
-
Permalink:
b12consulting/akgentic-framework@a92a0fc36ee2246b201b69a05a61dee8baaa7710 -
Branch / Tag:
refs/heads/master - Owner: https://github.com/b12consulting
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
publish-pypi.yml@a92a0fc36ee2246b201b69a05a61dee8baaa7710 -
Trigger Event:
workflow_dispatch
-
Statement type: