Composition root
The composition root assembles all arka-deck dependencies into a living graph. It instantiates adapters, use-cases, addons, and wires them via manual dependency injection.
Source of truth: composition/core-container.ts, composition/web-container.ts.
Two containers
Section titled “Two containers”| Container | Role |
|---|---|
createCoreContainer(deps) | Instantiates the core: event bus, outbound adapters (stores, clients), inbound use-cases, first-party addons |
createWebContainer(coreContainer, deps) | Instantiates the web server layer: Fastify, routes, current UI state (workspace + project) |
The coreContainer is usable without web server (e.g. in CLI or tests). The webContainer adds the HTTP transport layer.
Instantiation order (simplified)
Section titled “Instantiation order (simplified)”1. Infrastructure adapters ├── Clock (FsClock) ├── IdGenerator (UuidIdGenerator) ├── Filesystem (FsFilesystem with allowlist) ├── ArkaHome (~/.arka-deck/ or ARKA_DECK_HOME) └── SecretCipher (AesSecretCipher)
2. Event bus └── InMemoryEventBus
3. Production stores ├── FsProjectStore, FsProjectIndexStore, FsWorkspaceStore ├── SqliteChatSessionStore ├── SqliteArkadocStore, SqliteArkadocManifestStore └── SqliteConnectorInstallationStore, etc.
4. Cortex HTTP clients ├── HttpCatalogueClient, HttpCatalogueBlocsClient ├── HttpArkadocCortexClient, HttpAtomsClient └── HttpCortexLiteRuntimeContextClient
5. Provider hub └── HttpProviderHub (registry + LLM provider lifecycle)
6. Inbound use-cases ├── buildForProjects, buildForWorkspaces ├── buildForChat, buildForCatalogue ├── buildForArkadoc, buildForSquads, buildForMissionGuardian └── ... (all implemented for-*.ts)
7. First-party addons (canonical path: `composition/addons/first-party-runtime.ts`) ├── registerFirstPartyRuntimeAddons (single entry point) │ ├── cortex-actions runtime (currently still built directly by core-container — SPEC-05 deviation) │ ├── memory-local runtime (currently still built directly by core-container — SPEC-05 deviation) │ └── squad-orchestration (Addon, built by the registry from deps) ├── Cortex Lite (sidecar HTTP, not in-process — see addons-firstparty/cortex-lite/) ├── Mission Guardian runtime wiring (core runtime, not an in-process addon) ├── registerSquadLeaderAddon └── registerNotionConnectAddon
8. Materializers ├── ClaudeAgentWorkspaceMaterializer ├── HookWorkspaceMaterializer └── SkillWorkspaceMaterializerComposition root import rules
Section titled “Composition root import rules”composition/ is the only folder allowed to import simultaneously from:
core/(ports, use-cases, domain)adapters/(outbound + inbound)addons/(registers)providers/(runtimes)
Other folders strictly respect the boundaries (see ADR 0001).
Wiring an addon (canonical path — SPEC-05, decision B)
Section titled “Wiring an addon (canonical path — SPEC-05, decision B)”The canonical path to add a first-party addon does not require editing composition/core-container.ts:
- Drop the addon module under
addons/<name>/with its manifest andregister<Name>Addon(deps)(or a runtime built by composition forRegisteredRuntimeAddon). - Add the manifest to
composition/addons/index.ts→FIRST_PARTY_RUNTIME_ADDON_MANIFESTS(or to theADDONSarray for a self-registeringAddon). - Wire it through the single entry point
composition/addons/first-party-runtime.ts→registerFirstPartyRuntimeAddons(deps). The composition root calls this function once and recovers each runtime viaaddonRegistry.require<TRuntime>('<id>'). - Optional HTTP routes go to
composition/addons/<name>-routes.tsand are mounted fromcomposition/web-container.ts.
// composition/addons/first-party-runtime.ts (canonical path)
import { squadOrchestrationAddon } from '../../addons/squad-orchestration/src/index.js';import type { CortexActionsAddonRuntime } from '../../addons/cortex-actions/src/index.js';import type { LocalMemoryAddonRuntime } from '../../addons/memory-local/src/index.js';// ...
export function registerFirstPartyRuntimeAddons(deps: { // ... shared ports + already-built runtimes for RegisteredRuntimeAddon entries}): { addonRegistry: AddonRegistry; squadOrchestrationRuntime: SquadOrchestrationRuntime } { const addonRegistry = new AddonRegistry(); addonRegistry.registerAll( [ registeredRuntimeAddon('cortex-actions', deps.cortexActionsRuntime), registeredRuntimeAddon('memory-local', deps.memoryRuntime), squadOrchestrationAddon, ], deps, ); return { addonRegistry, squadOrchestrationRuntime: addonRegistry.require('squad-orchestration') };}Current deviations (tracked in ROADMAP.md → Chantiers de split tracés SPEC-05): core-container.ts still calls registerCortexActionsAddon and registerLocalMemoryAddon directly because their runtime builders depend on container-local stores/clients. The target is to push these into dedicated composition/addons/<name>-factory.ts helpers so the composition root only invokes registerFirstPartyRuntimeAddons.
Lifecycle
Section titled “Lifecycle”| Phase | Action |
|---|---|
| Boot | createCoreContainer instantiates the graph, starts bus subscriptions |
| Run | Routes consume use-cases via the container |
| Shutdown | coreContainer.dispose() calls addon unsubscribe() and closes resources (SQLite, sidecars) |
See also
Section titled “See also”- Overview: ./overview
- Inbound ports: ./ports-inbound
- Outbound ports: ./ports-outbound
- Materializers: ./materializers