Skip to content

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.


ContainerRole
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.


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
└── SkillWorkspaceMaterializer

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:

  1. Drop the addon module under addons/<name>/ with its manifest and register<Name>Addon(deps) (or a runtime built by composition for RegisteredRuntimeAddon).
  2. Add the manifest to composition/addons/index.tsFIRST_PARTY_RUNTIME_ADDON_MANIFESTS (or to the ADDONS array for a self-registering Addon).
  3. Wire it through the single entry point composition/addons/first-party-runtime.tsregisterFirstPartyRuntimeAddons(deps). The composition root calls this function once and recovers each runtime via addonRegistry.require<TRuntime>('<id>').
  4. Optional HTTP routes go to composition/addons/<name>-routes.ts and are mounted from composition/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.


PhaseAction
BootcreateCoreContainer instantiates the graph, starts bus subscriptions
RunRoutes consume use-cases via the container
ShutdowncoreContainer.dispose() calls addon unsubscribe() and closes resources (SQLite, sidecars)