Architecture¶
Maestro turns a single prompt into a bounded, resumable, reviewable run. This page covers the agent hierarchy, what guarantees each layer carries, and the storage and security model underneath.
Agent hierarchy¶
User prompt
│
▼
ORCHESTRATOR Routing only. Classifies the task domain; produces no work itself.
│
▼
MAIN AGENT Domain expert (finance, software, marketing, ...).
│ Builds the subtask plan and coordinates subagents.
├──────────────┐
▼ ▼
SUBAGENT SUBAGENT One atomic task each (fetch data, analyze, summarize).
│ │
└──────┬───────┘
▼
REVIEWER Optional. Validates subagent output and sends it back with
issues. Bounded by max_review_iterations.
Every agent carries a system prompt, an explicit tool list, and an iteration bound.
The layers¶
Orchestrator. Classifies the prompt into a domain and a complexity tier, and produces
nothing else. Complexity is judged by how many kinds of work a request contains, not by how
long it is: simple gets one member and skips review entirely, standard gets four,
complex gets up to eight. Custom agents published by users are merged into the routing
catalog, so the orchestrator can route to a team it did not ship with.
Main agent. A domain expert — fifteen built-in squads covering finance, software, marketing, SEO, legal, data, community, open source and more. It decomposes the request into a subtask plan, assigns each member a task-specific brief, and declares dependencies between members. It may pause once to ask the user a clarifying question when the prompt is genuinely ambiguous.
Subagents. Each executes one atomic task with a bounded tool set and its own token budget. Members that do not depend on each other run in parallel. A member whose answer comes back blank is recorded as an error, not a success — this matters more than it sounds, because an empty string merged into a synthesis is invisible, and a run where every member came back blank would otherwise report as completed.
Reviewer. Optional and off for simple tasks. It grades output against a structured
rubric with weighted criteria and hard_fail conditions, and returns specific issues plus
retry hints rather than a verdict.
Contracts between layers¶
Structured JSON, never free text. A subagent returns:
{
"status": "success | error | needs_review",
"data": {},
"metadata": { "tokens_used": 0, "execution_time_ms": 0, "model_used": "string" }
}
The reviewer returns:
Free text between layers is how agent systems fail quietly. A schema is how a failed step stays a failed step.
Watching a run¶
The full execution streams to the browser over an authenticated WebSocket. The Architect view draws the live agent graph — which member is running, which tool it just called, which external provider it reached, and where the edges between them are.

Connected-API calls get their own rail, one edge per member-provider pair, so "two of four members went to GitHub" stays readable instead of drawing a line per call.
Durable execution¶
An agent run is long, expensive and easy to lose. Maestro treats it as a durable step loop rather than an in-memory coroutine.
- Checkpoints.
task_runs,task_checkpointsandtask_questionsin PostgreSQL record the state machine (ROUTE → EXECUTE → FINALIZE). A resumed run replays from its last checkpoint instead of restarting. - Leases and heartbeats. A worker holds a lease on a run and refreshes it. A reconciliation sweep finds runs whose lease expired and either resumes or finalizes them, so nothing hangs in "running" forever after a crash.
- Cross-worker coordination. With Redis configured, a pub/sub event bus carries the live stream, human-in-the-loop answers and cancellations between workers. Booting with more than one worker and no Redis is refused at startup rather than silently falling back to a process-local bus.
- Bounded everywhere.
max_iterations,max_review_iterationsandtask_timeout_secondsbound every run. A hierarchical token budget is enforced per wave and per call, with a quota re-check at each step boundary.
Terminal states are honest: a run where every member failed is failed, a run where some
failed is completed_with_warnings and carries a "Known gaps" section naming what is missing.
Tools¶
Subagents get a bounded tool set resolved per run:
| Tool | What it does |
|---|---|
web_search |
Search with an automatic query-simplification ladder, invisible to the tool budget |
data_fetch |
Fetch a page with TLS impersonation, optionally extracting by CSS selector |
repo_intel |
GitHub repository activity, contributors, releases, issue close times |
social_search |
X/Twitter search |
community_read |
Discord, Slack or Telegram channel reading |
places_intel |
Google Places lookups |
code_execution |
Sandboxed execution, off by default in production |
A missing service key degrades rather than stops. The tool is withheld from the enabled
set, the squad falls back to web_search, and a mandatory coverage section — enforced by a
hard_fail review criterion — states plainly what could not be reached. Stopping instead
would make every connected squad unusable for the majority of accounts, which hold no service
keys at all. A missing brain key is different: that stops the task, because there is
nothing to run.
Storage¶
Three stores, each doing what it is good at.
PostgreSQL — users, encrypted API keys, refresh-token families, subscriptions, the
append-only usage_records quota ledger, and durable task state. Schema changes go through
Alembic; SQL is never run by hand.
MongoDB — step-by-step agent_logs (sequence-ordered, the source for stream replay),
marketplace items, task sessions, custom agent configurations, and trace spans with a TTL.
Qdrant — conversation_memories and document_chunks. Vectors are scoped per user and
deleted on account purge.
Data isolation is per user across all three. Memory never crosses accounts.
Security model¶
BYOK. Provider keys are encrypted with AES-256-GCM under a master key held only in the
environment. Only provider and label are ever returned to the frontend — never the key.
Credentials are decrypted once per run at the engine edge and handed to the agent layer as a
value whose string representation renders provider names only, so a traceback or a log record
cannot print a secret.
Outbound requests. Every user- or model-supplied URL passes a guard: http(s) only, no embedded credentials, and every resolved address must be globally routable. Redirects use libcurl's SAFE mode, which refuses a hop to an internal address before the request is made, and the landed URL is re-validated afterwards. The connected-API tools have no SSRF surface at all — every host is a compile-time constant — but any value reaching a URL path, such as a repository slug or a channel id, is pattern-matched before a request can be built.
Prompt injection. Fetched content is delimited, labelled untrusted, and scanned before a
model sees it. Because a commit message or a social post is attacker-authored, connected-API
results are scanned and dropped individually rather than blanking a whole block.
Marketplace submissions are security-scanned on publish, custom system prompts are scanned on
write and sandboxed inside an <agent_persona> boundary at execution time, and installed
marketplace agents never touch the installing user's keys directly.
Everything else. JWT on every non-public endpoint including WebSockets, which authenticate before accepting the connection. An explicit rate limit on every route, enforced by a test that walks the route table and fails on any endpoint without one. Pydantic v2 validation on every input. Refresh-token rotation with reuse detection that revokes an entire session family. Optional TOTP 2FA with Argon2-hashed single-use recovery codes.
Full policy and the reasoning behind each decision: Security.
Stack¶
| Layer | Technology |
|---|---|
| Frontend | Next.js (App Router), React, TypeScript, Tailwind |
| Backend | FastAPI, Python 3.11+ |
| Relational | PostgreSQL via SQLAlchemy async, Alembic migrations |
| Documents | MongoDB via Motor |
| Vectors | Qdrant |
| Cache and bus | Redis |
| Realtime | WebSocket |
| Local models | Qwen3 and nomic-embed-text via Ollama |
| Reverse proxy | Caddy, single origin, automatic TLS |
Next: Configuration for what each setting changes, or Deployment for running it in production.