Skip to content
You are reading documentation for unreleased main. Read the 0.1 version.

Overview

Crewlet is an open-source engine for orchestrating hierarchically organized AI agent companies. It provides the runtime, event system, seat ownership, and knowledge and memory infrastructure needed to operate a network of AI agents modeled after a real corporate structure. Task state lives in a work-item tracker, the engine’s own by default or an external one the engine deliberately mirrors none of (see The Tracker).

Crewlet ships as an engine plus a thin operational surface: the engine does the work, and a REST API + a web dashboard (served inside the engine’s own process, by every node given an API port) provide configuration, webhooks, and observability. Anything beyond that (custom UIs, metrics exporters, bespoke automations) is built outside the process: the engine loads no plugins, and its surfaces to the outside are the REST API, the /ws/stream socket, OTLP, and MCP for anything an agent should be able to call.


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 or self-hosted Mattermost, the work-item tracker, the code host)
  • Task management — the engine’s own work tracker by default (items, threads, hand-offs, a board and an MCP surface), or an external PM tool it deliberately mirrors none of (Jira, GitHub/GitLab issues) — see The Tracker
  • Code hosting — agents read, review, and track code via GitHub or GitLab MCP tools, and author code through the code sandbox
  • Knowledge — a shared knowledge base behind one seam, either the engine’s own pages (keyword over a BM25 index this node holds, semantic over embeddings the fleet derives once, or the two fused by reciprocal rank fusion — hybrid is the default) or a live Confluence search, plus a per-agent private diary (vector similarity computed by the database — 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 — the engine’s own, or a vendor’s searched live — plus a per-agent private diary
Decision modelThe DACI framework (Driver / Approver / Contributor / Informed)
LifetimeA long-running, persistent company
Config styleA YAML org chart, versioned in the store and edited live
ExtensibilityOut of process: MCP servers for anything an agent calls, and the REST API, the /ws/stream socket and OTLP for anything built around the engine. The binary loads no plugins

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 (a chat mention, a work-item 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 the seat’s last_error on the dashboard — an unreachable provider as the seat state stopped/provider — see Turn Engine and The Tracker.


  • Event-driven — agents are reactive; events trigger agent work, agent work produces events
  • Concurrent by default — a seat’s turn, its MCP children and the node’s own duties run in parallel, and every shared structure is checked under the race detector
  • Provider-agnostic: pluggable LLM, storage, tracker, knowledge and embedding backends, each behind a small contract; external tools via MCP. Only the LLM is required: the tracker and the knowledge base ship with the engine, so a company with an API key and nothing else runs
  • Config-driven — a company is a YAML document, validated against a schema generated from the same types the engine runs on
  • Extension-oriented: anything beyond running the company is built outside the process, not added to the core
  • Observable: structured logging, OpenTelemetry tracing, and an event store the dashboard reads, from day one

webhooks / websocket

verify · claim once per fleet · publish

resolve the seat, wake it

one durable consumer per seat

tool calls, made as each agent

the agent's own credentials

events, and the next wake

ownership, budgets, ledgers

audit rows, memory, revisions

External surfaces (all optional)Slack · Mattermost · Jira · Confluence · GitHub · GitLab

What a turn consumesan LLM API, or a coding CLI on your own subscriptionembeddings · MCP servers · a code sandbox

ingress — API + dashboardwebhook routes · REST · /config · /secrets/ws/stream · OTLP ingest · /health · /ready

seats — the agentsSeat host: leases, mailboxes, MCP childrenTurn engine: executor → reviewer, one per running turnTool registry: builtins · per-role MCP · a2a_askProvider chain: fallback models over a credential pool

workers — company-wide singletonsscheduler · sandbox waiter · retention sweep · skill curator

always on, whatever the rolesnotification service — parse, resolve, wakeconfig reconciler · node presence · reflection · observability edge

Event streamembedded NATS JetStream by defaultcrewlet.agent.HANDLE.inbox · .control · .reflectcrewlet.notifications.inbound · crewlet.events.*crewlet.config.* · crewlet.memory.* · dlq.*

Coordination KVrides the stream's own connectionseat · node · worker leases with a fencing epochactivation pointer · per-node statusledgers · counters · the company's secrets

Storetwo local files, owned exclusivelythis node's: crewlet_events · agent_diary · episodesreplicated: the tracker, the pages, the vectors

crewlet run — one process by default; node.roles picks the groups

That is the whole engine at a glance; Architecture draws it at six zoom levels — what sits outside the boundary, what runs inside one node, the hop-by-hop path a trigger takes across a fleet, what a turn does, where every table and bucket lives, and what changes when a second node appears.

Infrastructure: none to operate. The stream is a NATS JetStream server the process embeds, and the store is a local file it creates. Outgrowing one node moves only the stream: the embedded servers join one cluster, or every node dials one external NATS (see Running a Fleet). It is the same client code either way — embedded versus external is a connection choice, not a second backend — and the store stays each node’s own file. OpenTelemetry for distributed tracing.

One node type: crewlet run is the node, and what it does is a config value: node.roles picks from data (hold the company’s durable state), ingress (serve the HTTP API and its webhooks), seats (run agents), and workers (the company-wide singleton duties). The default is all four: one process holding the company’s records, serving the API and the dashboard and running every agent, which is the whole stack. Giving the API nodes of its own is the same command with -roles data,ingress: an engine that serves the API and claims no seats, so both topologies are the same code path rather than two wirings that have to be kept in step. And an agent host can hold no data at all — -roles seats on a scratch store, joined to the fleet as a leaf — which keeps it small and disposable while its seats read and write through a node that holds the data (ADR-0025).

One node is enough, and more than one is supported. Agents are stateful seats, not interchangeable workers, so which node runs which seat is decided by a lease — see Seat ownership. Scale up (a bigger host, a higher node.max_concurrent) before scaling out: a single engine handles many concurrent turns, and one node is the design’s degenerate case rather than a lesser path. Run a fleet when a node’s failure is not acceptable downtime, when traffic has to terminate separately from the agents, or when some seats must run somewhere specific — Running a Fleet covers all three, and Scaling Out is the model underneath them.


ComponentTechnologyRationale
LanguageGo 1.27+One self-contained binary, real parallelism, a standard library that covers most of this table
DistributionA single CGO_ENABLED=0 binaryNothing to install alongside it. The matrix is linux and macOS on amd64 and arm64 — bounded by the platforms the store driver embeds its database engine for, not by the compiler. The linux binaries need glibc: that engine is loaded with dlopen, which no pure-Go build avoids
Event streamEmbedded NATS JetStreamPersistent pub/sub inside the process — a company runs with no broker to operate. An external NATS cluster takes the same slot for a fleet, on the same client code. A seat’s mailbox is a durable consumer created with nothing attached, which here is an ordinary API call (~1.7 ms) rather than an admin endpoint
StoreTursoTwo local files this process owns exclusively — this node’s own estate and the replicated one beside it; pure Go, SQLite file format, and the vector functions the learning subsystem’s recall is written against
Vector searchThe store’s vector distance functionsThe per-agent diary and the episodic store, in the same file as everything else. The arithmetic is the database’s; there is no ANN index reachable from the Go driver yet, so recall is a scan behind the per-agent index
Event storeA table in that fileLLM-invocation observability and the event dashboards, written inline by a publish listener
CoordinationTTL leases with a fencing epochSeat ownership and the fleet’s shared counters, in a KV riding the stream’s own NATS connection — never the store file, and never a second connection that could fail on its own
ConfigYAML → typed structs → generated JSON SchemaOne definition drives validation, the schema editors read, and the docs
TracingOpenTelemetryW3C Trace Context, automatic propagation, OTLP export to Jaeger/Tempo
LLM clientsOfficial vendor SDKsAnthropic and OpenAI, plus any OpenAI-compatible endpoint
Structured logginglog/slogStandard library, machine-parsable, one component-bound logger per subsystem
TestingtestingStandard library, no assertion framework; one shared conformance suite per multi-backend contract

External dependencies sit behind small Go interfaces, each declared by the package that consumes it, so a backend is swappable and a test runs against a fake rather than a real API.

llm.Provider (internal/providers/llm) has two methods: Complete(ctx, Request), one model call with the messages and optional tool definitions — offered, never forced: there is no tool choice to set, so the model decides — and Model(), the entry’s configured model id for log lines and config display. A request may also carry an Effort — the most effort the call is worth, a ceiling the backend lowers its entry’s own level to and never raises it past: the classifier and extraction calls (the extension judge, the knowledge answer, the turn-start memory filter, knowledge query and summary, memory compaction and every learning pass) ask for low, and the phases of a turn name none and run at the entry’s level. The cli-agent backend ignores it, because a CLI has no per-call effort flag. There is no separate streaming method: a caller that sets Request.OnDelta asks the backend to stream, and the backend calls it as text arrives while still returning the whole Completion. The Anthropic backend streams every call, listened to or not, because it sends the model’s own output cap (128K on the current models) and the vendor requires a stream for a response that large; a stream that ends before its message_stop is a server failure the chain retries, never a short answer. The OpenAI backend holds its streams to the same rule: one is an answer only once a choice carries a finish_reason or the [DONE] sentinel arrives, and a body that ends cleanly before either is a server failure. An entry’s timeout_seconds bounds a unary call in total but a streamed one by its silence — the longest wait with no byte arriving — because a round thinking at a high effort writes for many minutes, and a total bound sized for an ordinary answer cut exactly those rounds off half-way. The model that actually served a call is Completion.Model, and the per-model token breakdown is built from that field rather than from Model(), because a fallback chain shared by concurrent callers has no single answer a method with no arguments could return. Completion.ProviderKey is the same fact in the operator’s vocabulary — which providers.llm entry answered — and a chain fills it in from the member that served, since a backend is never told the key it was configured under.

A provider does not retry and does not decide what a failure means beyond a coarse llm.ErrorKind (rate_limit, auth, timeout, server, or fatal). The HTTP backends classify by status, except for an error that ends a response already streaming: it arrives after the 200, so the status says nothing about it. The Anthropic backend reads the error type the API names in every error body first — an overloaded_error half-way through a round is server and the chain moves on, where its status alone would have made it fatal — and the OpenAI backend treats an error chunk mid-stream as server unless the host names an HTTP status in its code. Rotation across keys belongs to the credential pool (internal/providers/credential), and falling back to the next model belongs to the seat’s chain (internal/providers/llm/chain), which tries its members in order and stops at a fatal error.

The request shape is the model’s. A request field a model does not accept is a 400, and a 400 is fatal — the chain does not try the next member on it — so the Anthropic provider sends nothing the model in front of it would refuse, read from one capability table (internal/providers/llm/anthropic/claudemodel) under the model’s normalized id. Thinking is adaptive with a summarized display on every call to a model that has that mode (there is no off: several models refuse it, and depth is the effort level), and a budget only on the budget-era models that predate it, only when the entry gives one; output_config.effort is the entry’s level (high when unset) lowered to the call’s ceiling, only where the model takes effort; a request’s temperature reaches the wire only on a model that takes sampling and a call that is not thinking; max_tokens is the model’s own output cap, a call’s smaller cap honoured only on a call that is not thinking; and neither a forced tool choice nor a prefill is ever sent. An id the table does not know gets the current generation’s shape, which an entry’s claude_model corrects for a gateway alias hiding an older model. The rules are checked at crewlet validate and again when the provider is built; Claude models has the table. The conversation goes back the same way it came: every assistant turn is replayed as the vendor’s own content blocks, verbatim and in the order the model wrote them, because the current models bind each thinking block to the conversation before it and refuse one whose earlier turns were rebuilt. The one thing that does not only grow is the tool list (activate_tool adds to it, and a resume renders it again), so on a model that runs that check the reasoning written under an earlier tool set is shed, oldest first, rather than replayed into a 400 — see the conversation only grows.

Built-in providers: OpenAI, Anthropic (using their official SDKs), any OpenAI-compatible endpoint, and cli-agent: a locally installed coding CLI (claude, codex, gemini, opencode, and others) driven on the operator’s subscription rather than a metered API key. Different roles can use different providers and models (for example, executives on Claude and junior agents on a smaller, cheaper model).

Subscription backends. The cli-agent provider is the one built-in that is not an HTTP client: it spawns a local process, so it needs per-seat filesystem isolation (a coding CLI keeps sessions, history and project memory under one home, and one provider instance serves every seat), an in-prompt JSON envelope in place of a native tool-call channel, and its own auth story. All three are covered in Subscription LLM Backends. A spent subscription window classifies as rate_limit, so the ordinary fallback chain carries a role onto a metered key until it resets.

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 within an epoch. Both built-in HTTP 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 breakpoints on the system block and the final tool definition (caching the whole tools and system prefix) and, on a call that offers tools, one more on the last block of the conversation, which moves forward with it — so each round of a tool loop reads the history the previous round wrote instead of re-billing it, which by the twentieth round is most of what a round sends. That third one is an explicit marker on the block rather than the request’s top-level cache_control (the API’s “automatic” breakpoint, which lands in the same place), because the legacy Bedrock integration (Opus 4.6 and earlier) answers the top-level field with a 400, and an entry reaches Bedrock through any gateway that forwards the body. A call offering no tools (a judge, a knowledge or learning pass) is asked once, so its tail is not cached: the write would cost a premium for a read that never comes. Three breakpoints, inside the API’s four, all on the 5-minute TTL. The OpenAI provider relies on the platform’s automatic prefix caching, which works because the static system prompt is already first in the message array. This is why the per-phase prompts can carry their full incident-hardened guidance (see Turn Engine) without the repetition dominating cost.

Completion.InputTokens always reports the full prompt-token count regardless of cache state, so the token budget stays correct: Anthropic reports cache reads and writes separately from its raw input tokens, so the provider sums all three; OpenAI’s prompt tokens already include the cached portion. Completion.CacheRead and Completion.CacheWrite break that total down for cost reporting only, and each tool-loop round records the model, the input and output tokens and the cache reads on its trace span.

embeddings.Embedder (internal/providers/embeddings) embeds one text (Embed(ctx, text)) and reports the space its vectors are in — the configured Width() and the Model() they come from — and the model’s Limits(): how many bytes one input may hold, and how many inputs and bytes one request may carry, resolved from what the model’s vendor documents (or what the operator states for a model this build does not know). embeddings.BatchEmbedder adds EmbedBatch, which sends a batch in as many requests as those limits need. An input past the bound is refused before anything is sent, and a caller decides what a longer text becomes: the knowledge corpus embeds its opening, a turn’s ask is split into pieces whose vectors are pooled into one (embeddings.EmbedWhole). A failure is classified — refused (this request will be refused again unchanged), transient, or a configuration fault — and nothing in the provider retries: for a turn starting it means “no similarity search”, never a failed turn, and the corpus duty asks again on its next tick. It is 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), and by the knowledge system for the semantic half of a native search: the company’s own pages and work items are embedded once by a fleet-singleton duty and applied on every node, so the bill is paid once however many nodes run. A Confluence knowledge base is searched live and embeds nothing. Built-in provider: OpenAI (works with any OpenAI-compatible endpoint via base_url). Configured under providers.embeddings in YAML.

Two local files, each a Turso database over the SQLite file format built from its own forward-only migration sequence: this node’s own estate, opened when the node starts (store.OpenNode), and the replicated estate, opened on that node’s handle (OpenReplicated). Every node with the data role holds the whole of it from boot for as long as it runs, in its own file beside the node’s (crewlet-replicated.db, or wherever store.replicated_path puts it); a node without data holds none. Two rather than one because a snapshot is a copy of ONE of them: a node too far behind to replay the log installs a peer’s replicated file wholesale, and that file must not carry the donor’s audit log or its agents’ memory. No transaction spans the two, no read joins across them and nothing attaches one to another, which a static walk enforces (see Architecture § Where state lives). There was a second certified driver (mainline SQLite) as an escape hatch, and it is retired: it could not serve a database with rows in it, because it has no vector functions and recall degraded to nothing without saying so. The engine owns every file exclusively — a second process pointed at the same path is corruption waiting for a schedule to collide, which is why everything the company has to agree on now lives in the coordination KV instead. The load-bearing tables of the node’s own estate:

  • agent_diary (embedding column): 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-50 ∪ recency top-50, deduped by row id) handed to an aux-LLM relevance filter. Shared knowledge is a separate read: on the native backend it is the company’s pages as the replicated estate holds them (the pages log, applied on every data node), indexed for BM25 search in each data node’s own estate; on Confluence it is a live query with no local copy at all (see knowledge system).
  • episodes (embedding column): one row per completed turn; raw and 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_markers — mark_onboarded bookkeeping (one row per agent, UPSERT-keyed).
  • conversation_sessions — the conversation ledger: one row per completed turn, keyed on the seat and the conversation it served, rendered back into that conversation’s next turn. Deduped on the work key, trimmed on write, swept on a retention horizon.
  • secret_values — the local half of the secret store, and now only its bootstrap path: the company’s credentials live on the coordination KV where every node reads them, and rows written here while the engine was stopped are migrated there at its next start. Sealed with the Tier A keyring either way; no plaintext mode.

Alongside them sit the durable runtime tables a turn leaves behind — crewlet_events, scheduled_runs — and the config plane’s company_config payloads. The full migration list is in internal/store/schema/.

What is not here is as deliberate: the completion ledger, the delivery dedupe, the notification valve, the credential cooldowns, the token counter, the activation pointer and each node’s apply status all answer a question the whole company has to agree on, so they live in the fleet’s coordination store rather than in any one node’s file.

Everything else is YAML config, in-memory state, or an external tool.


adr/ # Architecture decision records — a decision that
# binds more than one package, with the gate that
# anchors each one to its authority
cmd/crewlet/ # The one binary: run, validate, schema, migrate,
# budgets, backup, retention, work, objects,
# fleet, seats, secrets, config, llm, search, and
# the six integration CLIs — gitlab/github/
# jira/slack
# `provision`, confluence `import|resync`,
# mattermost `provision|doctor`
internal/
├── engine/ # The wiring: which concrete thing satisfies which seam
├── config/ # The two config tiers → typed structs → JSON Schema
├── org/ # Organization model (hierarchy, roles, seat identity)
├── agent/ # The agent runtime, one package per hard part:
│ # turn/ (the two-stage loop), toolloop/ (the
│ # model↔tool round-trip and its suspend),
│ # inbox/ (what wakes a seat, and what must not
│ # wake it twice), ledger/ (iteration, conversation
│ # and budget ledgers), structured/ (how a phase
│ # gives a typed answer), prefetch/, prompts/,
│ # skills/, skillsync/ (keeps every node's
│ # skill registry current), builtin/,
│ # subagent/ (workers), steer/ (a person's note
│ # to a running turn)
├── queue/ # The EventQueue contract + the jetstream backend
│ # and the in-memory twin, both certified by one
│ # suite
├── coord/ # TTL leases with a fencing epoch + the shared KV
├── statelog/ # The durable-state framework: one ordered stream per
│ # domain is the write-ahead log, N identical SQL
│ # copies are the state, and the checkpoint commits
│ # in the same transaction as the rows
├── tracker/ # The engine's own work tracker — statelog's first
│ # domain: records, subjects, ranks, custom fields
├── pages/ # The engine's own knowledge base — statelog's third
│ # domain: containers, pages, revisions, comments
├── usage/ # Each node's day, replicated — statelog's fourth
│ # domain: spend, turns and reads that outlive the
│ # node that recorded them
├── eventfan/ # The fleet's turn-level history, read from every
│ # node at query time, with coverage on every answer
├── search/ # Both halves of knowledge search — the BM25 index
│ # and the two-stage semantic retrieval — plus the
│ # embedding domain and the fleet's bucket fan-out
├── textindex/ # The analyzer and the BM25 arithmetic, as pure
│ # functions: Turso has no fts5
├── changefeed/ # How a committed record becomes a WAKE, derived by
│ # something that outlives the writer
├── seat/ # Which seats this node runs, and the watchdog
├── store/ # The two local files: this node's own estate, and
│ # the replicated one a state log's applier writes
├── estate/ # The replicated estate ROUTED: one router on every
│ # node, answering in-process where this node's
│ # copy serves and asking a data node otherwise
├── objstore/ # The object store: one object per upload, under a
│ # key minted for it and never reused, in one store
│ # the fleet shares — natsobj/ (the default) or
│ # s3obj/ — plus collect/ (the collector) and
│ # references/ (the tables that name an object)
├── events/ # The envelope and the typed-payload registry
├── a2a/ # Agent-to-agent channels (one ask, one answer)
├── schedule/ # Role/unit cron-style recurring work
├── learning/ # What a seat remembers, and memsync/ — the changelog
│ # that carries it when a seat moves node
├── knowledge/ # The backend-neutral knowledge-search seam
├── providers/ # llm/ (+ chain, credential rotation), embeddings/
├── sandbox/ # Code work as a suspended Execute phase
├── mcp/ # MCP client and child-process supervision
├── tools/ # The registry, and the per-phase tool surfaces
├── notify/ # The backend-neutral notification spine
├── mattermost/ slack/ # The eight third-party apps: client, parser,
│ jira/ confluence/ # transport, prompt, provisioning reconcile — each
│ gitlab/ github/ # contributing only what is genuinely its own,
│ datadog/ atlassian/ # which is why Jira has no transport and why
│ # Datadog routes on a monitor's TAGS: an alert is
│ # addressed to nobody
├── integration/ # The app-neutral reconcile spine: the finding
│ # vocabulary every pass reports in, the cadence
│ # derived from who has to act, the fleet singleton
│ # that runs it, and integrationtest/ — the one
│ # suite every reconciler passes
├── setup/ # What an integration NEEDS before it works, in one
│ # vocabulary every app and the dashboard share,
│ # plus the guard every writer at a surface takes
├── configplane/ # The activation pointer's cadence and postures
├── node/ # The node's own identity, presence and drain
├── provision/ # The shared provisioning grammar and its sinks
├── backup/ # A verified copy of both estates, taken from inside
│ # the engine — the only place either is reachable
├── maintenance/ # The retention sweep, behind one singleton duty
├── tokens/ # Token accounting shared by the meter and the API
├── hostbox/ procgroup/ # The local sandbox host, and process-tree teardown
├── whsec/ # Webhook signing secrets: the format, minting and the HMAC key
├── jsprovision/ # How long a replicated JetStream create gets, and
│ # what "the cluster is still forming" looks like:
│ # one policy for the streams, the consumers and the
│ # coordination buckets, branching on whether this
│ # node's broker has peers
├── jsapi/ # Which JetStream API a connection speaks: the
│ # embedded fleet's one domain, which a leaf node
│ # reaches across its link, or an external
│ # cluster's account
├── httpx/ textcut/ # The shared HTTP transport; rune-safe shortening
├── api/ # REST + dashboard: webhooks/, stream/, queries/,
│ # livestate/, configapi/, setupapi/, secretsapi/,
│ # auth/, httpjson/, mcpbridge/, operator/ (the
│ # operator catalogue over /operator/mcp and the
│ # dashboard's /operator/act), and pagepolicy/:
│ # the security headers every response carries
├── observe/ # The observability edge (store row + live push)
├── tracing/ # OpenTelemetry: one provider, W3C propagation, and
│ # the bridge to the envelope's trace fields
├── fleetsecrets/ # The company's credential store: this package owns
│ # the key, coordination owns the bytes
├── runtoken/ # The signed, self-describing credential a per-run
│ # endpoint carries in its own URL path
├── secrets/ # The keyring and the sealed envelope: config encryption
│ # at rest and the secret store's values (the ${VAR}
│ # resolver is config.Resolver)
├── skipgate/ solo/ # The suite's own gates: a skip is not a pass, and
│ # which packages need the runner to themselves
├── docsgate/ # Every markdown link, anchor and docs index entry
│ # resolves (test-only)
├── clientsource/ # Holds a declaration the dashboard makes against the
│ # engine's own, found by name and read by syntax;
│ # every one lives in dashboard/src/contract/
├── sourcetree/ # What is this repository's tree: the module root,
│ # a walk that never reads a nested checkout, and
│ # what a gate may skip unread
├── e2e/ # The end-to-end company, and the dashboard replay
├── period/ # The company calendar: the day, ISO week and month a
│ # moment falls in, named by a label every node
│ # computes alike
└── version/ logging/ redact/ envref/ envfile/ workkey/ backoff/ # small shared
# grammars
dashboard/ # The dashboard's SOURCE — React + TypeScript, built
# by Vite. Its output is committed to
# static/dashboard, so `go build ./...` needs no
# Node. See reference/dashboard-design.md
static/dashboard/ # That build output, embedded in the binary — a
# store mirroring the server projection, one
# websocket as the only read channel (writes
# are REST, as the signed-in person), a hash
# router, one file per screen

Every package states its own rationale in its package doc, so go doc ./internal/coord is the authority on coordination rather than this page.

Part of Crewlet. Generated from crewlet/crewlet main at f665f5a. This is not the current version — see the latest docs.