Skip to content

Overview

Crewlet is an open-source engine for orchestrating hierarchically organized AI agent companies. It provides the runtime, event system, task management, agent lifecycle, and knowledge infrastructure needed to operate a network of AI agents modeled after a real corporate structure.

Crewlet ships as an engine plus a thin operational surface: the engine does the work, and a REST API + zero-build web dashboard (embedded in the engine process by default, or run as its own process) provide configuration, webhooks, and observability. Anything beyond that — custom UIs, metrics exporters, bespoke automations — is built as extensions on top of the engine.


Crewlet enables a founder to design a company structure, define its mission, and deploy a network of AI agents that operate within that structure. Each agent acts as a role within the company — with its own backstory, skills, and responsibilities — collaborating with other agents through the organizational hierarchy.

The framework models the same structures found in real companies:

  • Organizational hierarchy — departments, teams, and individual roles; seats are held by AI agents or human teammates
  • Communication — channels, direct messages, and external tools (Slack, the work-item tracker, the code host)
  • Task management — integrated with external PM tools (Jira, Plane, GitHub/GitLab issues)
  • Code hosting — agents read, review, and track code via GitHub or GitLab MCP tools, and author code through the code sandbox
  • Knowledge — query-time knowledge-base search (Confluence or Plane) for shared docs + per-agent private diary (pgvector — hybrid vector ∪ recency candidate selection)
  • Decision-making — structured DACI framework with clear authority

Crewlet treats the organizational hierarchy as its primary orchestration structure. Knowledge, permissions, communication, and downward delegation are all scoped by a seat’s position in the tree — the chart a founder draws is the graph the engine executes.

DimensionHow Crewlet models it
Mental modelA corporate org chart — departments, teams, and named seats
HierarchyA native tree: identity, downward delegation, manager-handoff target, and scoping all derive from it
CommunicationEvent-driven pub/sub with org-scoped channels
KnowledgeA knowledge base (Confluence or Plane) searched live at query time, plus a per-agent private diary
Decision modelThe DACI framework (Driver / Approver / Contributor / Informed)
LifetimeA long-running, persistent company
Config styleA YAML org chart, with Python overrides
ExtensibilityA full extension system — providers, hooks, middleware

The hierarchy is informational + delegation-routing, not a special upward escalation mechanism. When an agent is stuck, it hands off to its manager using the same colleague-surface tools (Slack mention, Jira comment, Confluence comment, A2A) that a human teammate would use; the manager’s handle comes from the agent’s identity prompt. Engine-detected failures (stall, max-iter, unhandled exception, LLM unavailable) surface to the operator via structured logs and a dashboard afk state — see Turn Engine and Task Engine.


  • Event-driven — agents are reactive; events trigger agent work, agent work produces events
  • Async-first — all I/O (LLM calls, storage, retrieval) is async
  • Provider-agnostic — pluggable LLM, storage, and embedding backends; external tools (including the knowledge base) via MCP
  • Config-driven AND programmatic — define companies via YAML or Python API
  • Extension-oriented — anything beyond running the company is an extension, not core
  • Observable — structured logging, tracing hooks, and metrics from day one

webhooks

MCP tools, called as each agent itself

crewlet run — one process by default

API + dashboardwebhook routes · REST · /config/* · live event stream

EngineAgent handlers (one turn engine per seat)Notification SVC (inbound routing + outbound sends)A2A service (agent-to-agent bus)———Organization model — hierarchy, roles, DACI decisionsProvider layer — LLM · embeddings · sandboxTool registry — builtins · per-role MCP · A2A

External surfacesSlack · Jira / Plane · GitHub / GitLab

Apache Pulsarcrewlet.agent.*.inboxcrewlet.notificationscrewlet.config.*

PostgreSQLTimescaleDB · pgvectorcompany_config · token_usageagent_diary · episodes · …

Infrastructure: Apache Pulsar + PostgreSQL with TimescaleDB and pgvector extensions (one database for operational state, the per-agent diary vector store, the episodic vector store, and the event store). OpenTelemetry for distributed tracing.

One process, or two: by default crewlet run serves the API and dashboard inside the engine process — that is the whole stack. The API can also run as its own process (crewlet run api) when you want to restart the engine without dropping webhooks, or put the two on different hosts. The halves communicate only through Pulsar, so the split is a deployment choice, not a code change. The engine itself is a single instance: agents are stateful seats, not interchangeable workers.


ComponentTechnologyRationale
LanguagePython 3.12+AI ecosystem, async/await, type hints, protocols
Async runtimeasyncioStandard library, broad compatibility
Data modelsPydantic v2Validation, serialization, JSON Schema generation
Config parsingPyYAML + PydanticYAML config → validated Pydantic models
Message queueApache PulsarPersistent pub/sub, durable shared subscriptions (consumer groups), dead-letter queues
DatabasePostgreSQL + pgvectorOperational state + per-agent diary vector store + episodic vector store
Event storeTimescaleDB (hypertable in the main PostgreSQL)LLM invocation observability, event dashboards (direct write via publish listeners)
TracingOpenTelemetry SDKW3C Trace Context, automatic propagation, OTLP export to Jaeger/Tempo
LLM clientsopenai + anthropic SDKsOfficial SDKs for each provider
Structured loggingstructlogMachine-parsable, context-rich logging
Testingpytest + pytest-asyncioStandard for async Python
PackaginguvModern Python packaging

All external dependencies are abstracted behind Protocol interfaces, enabling pluggable backends and easy testing.

The LLMProvider protocol defines two methods — complete() for single-shot completions (with optional tool definitions, temperature, max tokens, and tool choice) and stream() for streaming responses — plus a model: str attribute that names the model id the provider answers as. Telemetry (phase events, OTel spans, the dashboard’s per-model token breakdown) reads model directly, so every concrete provider must expose it; FallbackLLMProvider surfaces the wrapped provider’s value through a property.

Built-in providers: OpenAI, Anthropic (using their official SDKs). Different roles can use different providers/models (e.g., executives use Claude, junior agents use GPT-4o-mini).

Prompt caching. Each call’s large static prefix — the per-phase system prompt plus the tool-definition array — is the dominant repeated content of an agent turn: it is re-sent on every round of the tool loop and is byte-stable across successive turns for the same agent (org config does not change mid-run). Both built-in providers cache it so it is re-read at a fraction of the base input price instead of re-billed in full each round. The Anthropic provider sets explicit cache_control breakpoints on the system block and the final tool definition (caching the whole tools + system prefix); the OpenAI provider relies on the platform’s automatic prefix caching — the static system prompt is already first in the message array, which is what auto-caching requires. This is why the per-phase prompts can carry their full incident-hardened guidance (see Turn Engine) without the repetition dominating cost.

Completion.input_tokens always reports the full prompt-token count regardless of cache state, so the budget cascade and the token_usage ledger stay correct: Anthropic reports cache reads/writes separately from its raw input_tokens, so the provider sums all three; OpenAI’s prompt_tokens already includes the cached portion. Completion.cache_read_input_tokens / cache_creation_input_tokens break that total down for cost observability and are logged on every llm_complete event.

The EmbeddingProvider protocol defines embed() (batch text → vectors) and a dimensions property. Used by the agent-learning subsystem for vector-based retrieval over the agent’s private agent_diary (the vector half of the ## Personal memory prefetch’s hybrid candidate selection) and episodes (the ## Similar prior work prefetch and the query_episodes builtin). Knowledge-base content (Confluence or Plane) is searched live and is not embedded. Built-in provider: OpenAI (works with any OpenAI-compatible endpoint via base_url). Configured under providers.embeddings in YAML.

PostgreSQL via asyncpg. The schema is built from a forward-only migration sequence; the load-bearing tables:

  • token_usage — per-agent cumulative token consumption, upserted by the turn engine’s shared tool loop after each LLM completion that passes the budget check. Durable audit record; not used to hydrate the in-memory BudgetManager on startup.
  • agent_diary (pgvector) — each agent’s private observation log; the read-side counterpart of reflect_and_persist. Rows are embedded on write; the read path is hybrid candidate selection (vector top-K ∪ recency top-K, deduped, capped at 100) handed to an aux-LLM relevance filter. Shared knowledge is not in the database — the knowledge base (Confluence or Plane) is searched live (see knowledge system).
  • episodes (TimescaleDB hypertable, pgvector embedding) — one row per completed turn; raw + LLM-compacted aggregates share the same table.
  • synthesized_skills / synthesized_skill_versions — auto-drafted skills the agent can load via use_skill, with refinement history.
  • counterparty_profiles — per-(observer, subject) profiles built up from observed interactions.
  • agent_onboarding_markersmark_onboarded bookkeeping (one row per agent, UPSERT-keyed).
  • secret_values — the secret store: one encrypted row per env-var name, consulted ahead of os.environ when the config layer resolves a ${VAR} reference. Sealed with the Tier A keyring; no plaintext mode.

Everything else is YAML config, in-memory state, external PM tools, or Apache Pulsar. The full migration list is in src/crewlet/db/migrations/.


src/crewlet/
├── engine.py # Engine class — central entry point
├── config.py # YAML config → Pydantic models
├── cli.py # CLI commands (run, validate, api)
├── org/ # Organization model (hierarchy, roles)
├── agent/ # Agent runtime (definition, instance, pool, turn engine:
│ # turn, plan, execute, review, subagent, guards,
│ # prompts, turn_context, phase_model, llm_loop,
│ # skills/ — knowledge-base-sourced tool-skill registry)
├── queue/ # EventQueue protocol (Pulsar + memory)
├── a2a/ # Agent-to-agent bus (protocol, memory, service)
├── db/ # Database layer (asyncpg, migrations, token_usage,
│ # deterministic agent-id derivation)
├── secrets/ # Company-config encryption at rest (SecretCipher,
│ # AES-256-GCM keyring, redaction helpers) + the
│ # secret-store resolver installed ahead of
│ # os.environ for ${VAR} (see secret-store.md)
├── task/ # Task engine (models, tracker, escalation)
├── schedule/ # Scheduler — role/unit cron-style recurring work
├── events/ # Event types, routing (subscriptions via EventQueue)
├── knowledge/ # Shared-knowledge read (KnowledgeSearcher protocol
│ # + the query-time Confluence and Plane searchers,
│ # accessibility, shared markdown-doc helpers)
├── confluence/ # Confluence page write side — generic page ops,
│ # promotion writer, + unified `crewlet confluence
│ # import` CLI (routes each .md: trigger=skill,
│ # otherwise a knowledge doc whose space=parent
│ # dir, title=H1; `crewlet plane import` is the
│ # Plane analog, in plane/)
├── learning/ # Agent-learning subsystem (ReflectEngine, PersistDecider,
│ # SkillSynthesizer/Refiner, EpisodeStore + lifecycle,
│ # AgentDiary, CounterpartyProfiler, OnboardingMarkerStore)
├── providers/ # LLM + Embeddings protocols and implementations
├── sandbox/ # Code sandbox — the run_sandbox Execute tool's
│ # runtime (E2B provider, coding-agent runners,
│ # suspend/resume coordinator; see code-sandbox.md)
├── notifications/ # External notification system (outbound transports;
│ # typing_status.py — Slack "is thinking…" working
│ # indicator driven by the TurnEngine)
├── slack/ # Slack app provisioning (`crewlet slack provision`):
│ # canonical per-agent app manifest + App Manifest
│ # API client + OAuth install + .env/ledger writing
├── tools/ # Agent tool system (builtins + A2A tools)
├── github/ # GitHub integration (per-role remote MCP)
├── gitlab/ # GitLab integration (async REST client + the
│ # `crewlet gitlab provision` seat-provisioning CLI)
├── plane/ # Plane integration (REST client, provisioning,
│ # page import CLI, promotion writer)
├── mcp/ # MCP integration (stdio + HTTP/SSE)
├── timescaledb/ # TimescaleDB event store (observability)
├── api/ # Standalone REST API (Starlette): routes/ handlers,
│ # live_state.py projection (in-flight live_call),
│ # streaming.py StreamService (/ws/stream); serves
│ # the dashboard from the top-level static/ below
├── static/ # Web assets served by the API — static/dashboard/
│ # is the zero-build ES-module dashboard (reactive
│ # store, WS client, hash router, per-view modules;
│ # styles/ is the crewlet.io panel language — see
│ # reference/dashboard-design.md;
│ # llm.js renders LLM invocations with collapsible,
│ # height-capped prompt messages + a Source block
│ # naming the event that triggered the turn
│ # (notification triggers show a branded
│ # integration badge + sender); eventDetail.js
│ # renders inbound notifications as a readable
│ # integration-branded view)
└── extensions/ # Extension system

Generated from crewlet/crewlet v0.1.0 at b40ea18.