Hub-and-Spoke Architecture
Prax uses a hub-and-spoke model: the orchestrator stays near the 50-tool ceiling (kernel + planning/meta + trace introspection + per spoke + a tiny core-reviewed plugin promotion allowlist) and delegates domain-specific work to focused…
f62d7985
View source ↗
Prax uses a hub-and-spoke model: the orchestrator stays near the 50-tool ceiling (kernel + planning/meta + trace introspection + delegate_* per spoke + a tiny core-reviewed plugin promotion allowlist) and delegates domain-specific work to focused spoke agents. This keeps the orchestrator’s context lean — research shows that tool selection accuracy degrades significantly past 20–50 tools, with Anthropic documenting a hard cliff around 50.
Fallback: If a delegated agent fails or can’t handle the task, Prax can read the full tool catalog from a generated markdown file and call any tool directly. The spoke system is the fast path; direct tool access is the safety net.
Orchestrator (Hub)
graph TB
Prax["Prax Orchestrator\nnear 50-tool ceiling"]
Prax --> Core["Kernel\nsearch, datetime, URL fetch, sandbox_shell"]
Prax --> Planning["Planning/Meta\nagent_plan, progress_*, user_notes, think, run_python, review_my_traces, trace_search, trace_detail"]
Prax --> Obs["Observability (opt-in)\nobs_query_logs/metrics/traces"]
Prax -->|delegate_browser| Browser["Browser Spoke\n+ analyze_image + browser_verify"]
Prax -->|delegate_content_editor| Content["Content Editor\nsub-hub"]
Prax -->|delegate_course| Course["Course Spoke"]
Prax -->|delegate_desktop| Desktop["Desktop Spoke"]
Prax -->|delegate_environment| Environment["Environment Spoke\nweather + local conditions"]
Prax -->|delegate_plugins| Plugins["Plugin Spoke\nmanifest-routed end-user tools"]
Prax -->|delegate_sysadmin| Sysadmin["Sysadmin\nsub-hub"]
Prax -->|delegate_sandbox (opt-in)| Sandbox["Sandbox Spoke\n+ sandbox_view/scroll/goto"]
Prax -->|delegate_finetune| Finetune["Finetune Spoke"]
Prax -->|delegate_knowledge| Knowledge["Knowledge Spoke"]
Prax -->|delegate_scheduler| Scheduler["Scheduler Spoke"]
Prax -->|delegate_tasks| Tasks["Tasks Spoke\ntodos + task_runner mgmt"]
Prax -->|delegate_workspace| Workspace["Workspace Spoke\n+ create_pdf/presentation/spreadsheet"]
Prax -->|delegate_research| Research["Research Spoke\n+ Professor capability"]
Prax -->|delegate_task| Generic["Generic Sub-Agent"]
Research -->|hard questions| Professor["multi_model_query\nOpenAI + Claude + Gemini\npro-tier models"]
style Prax fill:#4A90D9,color:#fff
style Browser fill:#F5A623,color:#fff
style Content fill:#E8543E,color:#fff
style Sysadmin fill:#E8543E,color:#fff
style Sandbox fill:#F5A623,color:#fff
style Course fill:#F5A623,color:#fff
style Desktop fill:#F5A623,color:#fff
style Environment fill:#F5A623,color:#fff
style Plugins fill:#F5A623,color:#fff
style Finetune fill:#F5A623,color:#fff
style Knowledge fill:#F5A623,color:#fff
style Scheduler fill:#F5A623,color:#fff
style Tasks fill:#F5A623,color:#fff
style Workspace fill:#F5A623,color:#fff
style Research fill:#F5A623,color:#fff
style Generic fill:#F5A623,color:#fff
Background work — the task runner
The hub/spoke diagram above captures synchronous delegations. Prax
also has a background task runner
(prax/services/task_runner_service.py, opt-in via
TASK_RUNNER_ENABLED=true) that polls every ~5 min for work
assigned to Prax on either the Library Kanban (assignees: ["prax"])
or the top-level todo list (assignee="prax"). Each pickup spawns a
synthetic orchestrator turn that runs to completion. Management
tools (task_runner_status / pause / resume) live in the
tasks spoke. Respects the agent_plan/Kanban wall — Prax’s internal
plan stays ephemeral; only the user-created task gets updated.
Layer enforcement
scripts/check_layers.py runs as part of make ci and enforces
mechanical boundaries:
- Plugins must not import
prax.services.*orprax.agent.*— they go through the capability gateway. - Services must not import
prax.agent.*(except thellm_factory/user_contextinfrastructure carve-outs). - Services must not import
prax.blueprints.*(services are HTTP-agnostic).
Grandfathered violations live in an ALLOWLIST in the linter itself;
new code must not add to it.
Media Agent
Handles images, PDFs, audio, video transcripts, and web content extraction.
graph LR
Media["📰 Media Agent"] --> analyze["analyze_image"]
Media --> pdf["pdf_summary_tool"]
Media --> yt["youtube_transcribe"]
Media --> arxiv["arxiv_fetch_papers"]
Media --> npr["npr_podcast_tool"]
Media --> web["web_summary_tool"]
Media --> dlf["deutschlandfunk_tool"]
Media --> fetch["fetch_url_content"]
style Media fill:#9013FE,color:#fff
Sandbox Agent
Executes code in an isolated Docker container with a full dev environment.
Direct code execution — no coding sessions.
delegate_sandboxis a headless sub-agent that writes and runs code directly in the container viasandbox_shell(no session lifecycle, no rounds, no archive/replay). The multi-round OpenCode coding-session tools were removed (2026-07): the sandbox image no longer ships a coding-agent server, and Prax codes natively (run_python,workspace_save/workspace_patch,source_read/source_grep,sandbox_shell). The spoke and all sandbox tools below are available wheneverSANDBOX_ENABLEDis set. See sandbox-execution-boundary.
graph LR
Sandbox["🐳 Sandbox Agent"] --> shell["sandbox_shell"]
Sandbox --> view["sandbox_view"]
Sandbox --> scroll["sandbox_scroll"]
Sandbox --> goto["sandbox_goto"]
Sandbox --> rebuild["sandbox_rebuild"]
Sandbox --> install["sandbox_install"]
Sandbox --> dshot["desktop_screenshot"]
Sandbox --> dclick["desktop_click"]
Sandbox --> dtype["desktop_type"]
Sandbox --> dkey["desktop_key"]
Sandbox --> dwin["desktop_list_windows"]
Sandbox --> dopen["desktop_open"]
style Sandbox fill:#4A90D9,color:#fff
Browser Agent
Automates web interactions via Playwright with persistent profiles and credential management.
graph LR
Browser["🌐 Browser Agent"] --> open["browser_navigate"]
Browser --> read["browser_read_page"]
Browser --> shot["browser_screenshot"]
Browser --> click["browser_click"]
Browser --> fill["browser_fill"]
Browser --> press["browser_press"]
Browser --> find["browser_find"]
Browser --> creds["browser_credentials"]
Browser --> login["browser_login"]
Browser --> close["browser_close"]
Browser --> profiles["browser_profiles"]
style Browser fill:#F5A623,color:#fff
Workspace Agent
Manages per-user file storage, git-backed workspaces, and link history.
graph LR
Workspace["📁 Workspace Agent"] --> save["workspace_save"]
Workspace --> rd["workspace_read"]
Workspace --> ls["workspace_list"]
Workspace --> arch["workspace_archive"]
Workspace --> srch["workspace_search"]
Workspace --> restore["workspace_restore"]
Workspace --> link["log_link"]
Workspace --> hist["links_history"]
Workspace --> push["workspace_push"]
Workspace --> share["workspace_share_file"]
style Workspace fill:#7ED321,color:#fff
Plugin Spoke
Routes installed end-user plugin capabilities by plugin.json manifest route.
The spoke sees tools declared as artifact, media, utility, vision, or
workspace. Research-route plugins stay inside the Research spoke, and plugin
management stays inside the Sysadmin spoke.
graph LR
Plugins["🔌 Plugin Spoke"] --> artifact["artifact tools\npresentation/video/export"]
Plugins --> media["media tools"]
Plugins --> utility["utility tools"]
Plugins --> vision["vision/OCR tools"]
Plugins --> workspace["workspace tools"]
style Plugins fill:#F5A623,color:#fff
Scheduler Agent
Manages recurring cron jobs and one-time reminders.
graph LR
Scheduler["⏰ Scheduler Agent"] --> create["schedule_create"]
Scheduler --> list["schedule_list"]
Scheduler --> update["schedule_update"]
Scheduler --> delete["schedule_delete"]
Scheduler --> tz["schedule_set_timezone"]
Scheduler --> reload["schedule_reload"]
Scheduler --> remind["schedule_reminder"]
Scheduler --> rlist["reminder_list"]
Scheduler --> rdel["reminder_delete"]
style Scheduler fill:#BD10E0,color:#fff
Plugin Management Agent
Creates, tests, imports, and manages hot-swappable plugins through the Sysadmin sub-hub. This is separate from the Plugin Spoke, which executes end-user plugin capabilities.
graph LR
Plugin["🔌 Plugin Agent"] --> plist["plugin_list"]
Plugin --> pread["plugin_read"]
Plugin --> pwrite["plugin_write"]
Plugin --> ptest["plugin_test"]
Plugin --> activate["plugin_activate"]
Plugin --> rollback["plugin_rollback"]
Plugin --> catalog["plugin_catalog"]
Plugin --> src["source_read / source_list"]
Plugin --> prompt["prompt_read / prompt_write"]
style Plugin fill:#D0021B,color:#fff
Self-Improvement Agent
Diagnoses bugs in Prax’s own code, writes patches in the sandbox, and deploys fixes.
graph LR
SelfImprove["🔧 Self-Improve Agent"] --> start["self_improve_start"]
SelfImprove --> rd["self_improve_read"]
SelfImprove --> wr["self_improve_write"]
SelfImprove --> test["self_improve_test"]
SelfImprove --> lint["self_improve_lint"]
SelfImprove --> verify["self_improve_verify"]
SelfImprove --> deploy["self_improve_deploy"]
SelfImprove --> logs["read_logs"]
style SelfImprove fill:#417505,color:#fff
Key Modules
| Module | Purpose |
|---|---|
prax/agent/orchestrator.py |
LangGraph ReAct agent with hot-swappable system prompt, plugin-aware graph rebuild, and per-component LLM routing |
prax/agent/subagent.py |
General sub-agent delegation: spawns focused LangGraph sub-graphs with per-category LLM config |
prax/agent/self_improve_agent.py |
Self-improvement sub-agent: diagnose bugs, patch via sandbox, deploy via codegen |
prax/agent/plugin_fix_agent.py |
Plugin engineering sub-agent: create/fix/test/activate plugins autonomously |
prax/agent/spokes/plugins/agent.py |
Plugin spoke: manifest-routed end-user plugin execution via delegate_plugins |
prax/agent/course_author_agent.py |
Content author sub-agent: produces rich course materials (mermaid, code, LaTeX) via iterative sandbox drafting |
prax/agent/tools.py |
Kernel tool wrappers (search, datetime, fetch_url) — reader tools migrated to plugins |
prax/agent/plugin_tools.py |
17 plugin management tools: plugin CRUD, catalog, prompt CRUD, LLM config, source_read/list |
prax/agent/workspace_tools.py |
26 workspace tools: notes, files, links, todos, task planning, instructions, conversation history/search, system status, diff-aware patch, self_upgrade_tier, run_python |
prax/agent/sandbox_tools.py |
16 sandbox tools: code execution sessions + 6 desktop interaction tools (xdotool/scrot) |
prax/agent/scheduler_tools.py |
9 scheduler tools: recurring cron + one-time reminders |
prax/agent/finetune_tools.py |
8 fine-tuning tools (harvest, train, verify, promote, rollback) |
prax/agent/codegen_tools.py |
10 self-improvement tools (worktree, edit, test, lint, verify, deploy, PR) |
prax/agent/note_tools.py |
7 note tools (create, update, list, search, note_from_url, pdf_to_note, note_link) |
prax/agent/project_tools.py |
6 research project tools (create, status, add note/link/source, brief) |
prax/agent/browser_tools.py |
14 browser tools (navigate, click, fill, screenshot, login, VNC) |
prax/agent/tool_registry.py |
Tool aggregation: built-in + spoke delegates + tiny manifest-backed plugin promotions + manually registered |
prax/agent/llm_factory.py |
Multi-provider LLM factory (OpenAI, Anthropic, Google, Ollama, vLLM) |
prax/plugins/loader.py |
Recursive plugin discovery (folder-per-plugin + flat), hot-swap, version tracking, auto-rollback, catalog generation |
prax/plugins/manifest.py |
Plugin metadata validation: identity, tool routes, risk, and requested orchestrator exposure |
prax/plugins/sandbox.py |
Subprocess-isolated plugin validation before activation |
prax/plugins/registry.py |
JSON-based version registry with rollback and failure monitoring |
prax/plugins/repo.py |
Plugin repository service: SSH deploy key auth, clone, commit, push to private repo branch |
prax/plugins/catalog.py |
Auto-generated CATALOG.md listing all available plugins with metadata |
prax/plugins/prompt_manager.py |
Hot-swappable system prompt loading with variable expansion |
prax/plugins/llm_config.py |
Per-component LLM routing (YAML-based, hot-reloaded) |
prax/plugins/monitored_tool.py |
Runtime monitoring wrapper: failure counting + auto-rollback |
prax/plugins/tools/*/plugin.py |
Built-in reader plugins (NPR, web summary, PDF, YouTube, arXiv, RSS, Deutschlandfunk) |
prax/services/sms_service.py |
SMS workflow: media handling, PDF pipeline, agent routing |
prax/services/voice_service.py |
Voice workflow: speech processing, TTS buffer management |
prax/services/conversation_service.py |
Shared conversation layer with workspace context injection |
prax/services/sandbox_service.py |
Docker sandbox execution: shell commands, file viewer, package install, container rebuild |
prax/services/scheduler_service.py |
APScheduler-backed cron service reading YAML definitions |
prax/services/finetune_service.py |
LoRA fine-tuning pipeline: harvest → train → verify → hot-swap |
prax/services/note_service.py |
Note CRUD, search, knowledge graph (related notes), Hugo page generation |
prax/services/project_service.py |
Research project CRUD, note/link/source aggregation, brief generation |
prax/services/codegen_service.py |
Self-modification via staging clone + verify + hot-swap / PR workflow |
prax/services/discord_service.py |
Discord bot: message handling, authorization, response delivery |
prax/services/browser_service.py |
Playwright browser automation with per-user sessions |
prax/services/pdf_service.py |
PDF download, extraction (opendataloader-pdf), arxiv detection |
prax/services/youtube_service.py |
YouTube audio download (yt-dlp) + Whisper transcription |
prax/services/workspace_service.py |
Git-backed per-user file operations with per-user locking |
scripts/watchdog.py |
Supervisor process: health checks Flask, auto-rollback on crash after self-improve deploy |
scripts/finetune_train.py |
Standalone Unsloth QLoRA training script (runs in GPU subprocess) |
prax/settings.py |
Pydantic BaseSettings — all config from .env |
prax/clients.py |
Shared lazy-initialized Twilio client |
prax/sms.py |
SMS chunking and sending utilities |
prax/call_state.py |
CallStateManager — typed call state with ensure() |
prax/conversation_memory.py |
SQLite storage with auto-summarization at 100k tokens |