Skip to content

Architecture — overview

Guide for developers who want to understand arka-deck’s code organization, dependency rules and functional clusters.


arka-deck is a hexagonal TypeScript/Node.js application. The core depends on nothing external — all dependencies point towards it, never the other way around.

┌─────────────────────────────────────────────────────────────┐
│ adapters/ │
│ ┌──────────────────┐ ┌──────────────────────────┐ │
│ │ inbound/ │ │ outbound/ │ │
│ │ cli/ │ │ filesystem/ │ │
│ │ web/ │ │ http/ │ │
│ │ server/ │ │ chat/ │ │
│ │ ui/ (React) │ │ providers/ │ │
│ └──────────────────┘ │ events/ │ │
│ │ process/ │ │
│ │ secrets/ │ │
│ │ system/ │ │
│ └──────────────────────────┘ │
│ ┌─────────────────────────────────────────┐ │
│ │ composition/ │ │
│ │ core-container.ts web-container.ts │ │
│ └─────────────────────────────────────────┘ │
│ ┌─────────────────────────────────────────┐ │
│ │ core/ │ │
│ │ domain/ ports/ use-cases/ │ │
│ │ orchestration/ │ │
│ └─────────────────────────────────────────┘ │
│ ┌─────────────┐ ┌──────────────────────┐ │
│ │ addons/ │ │ providers/ │ │
│ │ workers/ │ │ claude_code/ │ │
│ └─────────────┘ │ codex_cli/ │ │
│ │ google-genai/ │ │
│ └──────────────────────┘ │
└─────────────────────────────────────────────────────────────┘

FolderRole
core/Pure domain — ports, use-cases, domain types, orchestration
adapters/inbound/Entry points — CLI and web server (Fastify + React/Vite UI)
adapters/outbound/I/O implementations — filesystem, HTTP, chat, providers, secrets, system
composition/Dependency wiring — manual DI, containers
addons/Self-contained event-driven modules — pre-installed first-party addons
providers/LLM runtime implementations — claude_code, codex_cli, google-genai
workers/1-shot headless workers — manifest + prompt
bin/arka-deck CLI binary
scripts/Utility scripts (release, prepare-dist, CI gates)
e2e/Playwright end-to-end tests

The hexagonal core. No dependency to adapters/, addons/, composition/ or providers/.

core/
├── domain/ # Pure types and business rules
│ ├── chat/ # Transcript, StreamEvent, sessions
│ ├── catalogue/ # Profiles, installs, agents
│ ├── events/ # ArkaEvent — discriminated union of all bus events
│ ├── project/ # Project, workspace
│ ├── providers/ # Provider manifests
│ ├── squad/ # Squad composition
│ └── workers/ # Worker manifests
├── ports/
│ ├── inbound/ # Use-case contracts (for-chat.ts, for-projects.ts…)
│ └── outbound/ # I/O contracts (event-bus.ts, filesystem.ts, clock.ts…)
├── use-cases/ # Application logic — implements inbound ports
└── orchestration/ # Multi-agent orchestration (LangGraph)

Inbound ports (core/ports/inbound/for-*.ts) define the contracts that use-cases expose to adapters:

  • for-chat.ts — start, resume, send a turn
  • for-projects.ts — projects and workspaces CRUD
  • for-catalogue.ts — load and install agents from Cortex
  • for-workers.ts — trigger a worker
  • for-preferences.ts — read and write preferences
  • and several more — the exhaustive list is in ports-inbound.md (coming).

Outbound ports (core/ports/outbound/*.ts) define the contracts that adapters implement:

  • event-bus.ts — typed in-process event bus
  • filesystem.ts — file read/write (with allowlist)
  • chat-runtime.ts — LLM turn execution
  • before-turn-augmenter.ts — context injection before a turn
  • clock.ts, id-generator.ts — utilities

Boundaries are enforced mechanically by ESLint on each lint:

SourceForbidden to import
core/**adapters/**, composition/**, addons/**, providers/**
core/domain/**core/use-cases/**
adapters/inbound/**adapters/outbound/**
adapters/**addons/**

Composition (composition/) is the only place allowed to wire everything together.


The composition container instantiates all adapters, use-cases and addons, then wires them via manual dependency injection.

  • core-container.ts — instantiates the core (event bus, stores, use-cases)
  • web-container.ts — adds the Fastify server, routes and UI

It is the only file allowed to import from addons/, providers/ and adapters/ simultaneously.


The bus is a core module (core/ports/outbound/event-bus.ts), instantiated once at boot in core-container.ts. It carries typed in-process events across all modules.

interface EventBus {
publish(event: ArkaEvent): Promise<void>; // awaits all handlers
publishAsync(event: ArkaEvent): void; // non-blocking
subscribe<T extends ArkaEventType>(
type: T,
handler: EventHandler<T>,
): Unsubscribe;
}

The full event list is in core/domain/events/arka-event.ts (source of truth). Naming convention: <domain>.<entity>.<action> kebab-case. See event-bus for full documentation.


Each addon is a self-contained module in its own subfolder:

addons/<name>/
├── manifest.json # declarative metadata
└── src/
├── index.ts # register function + public re-exports
├── domain/ # addon-local domain types
├── ports/
│ ├── for-<name>.ts # addon-owned inbound port
│ └── *.ts # outbound ports (stores, HTTP clients)
├── adapters/ # outbound port implementations
└── use-cases/ # addon application logic

Addons can import from core/ (outbound ports, domain types) but not from adapters/ nor from other addons.

To create an addon, see the ../extension/ecrire-un-addon.md tutorial (coming).


Providers implement the chat-runtime.ts outbound port for a given LLM:

providers/
├── claude_code/ # @anthropic-ai/claude-agent-sdk SDK
├── codex_cli/ # Codex CLI
└── google-genai/ # Google Gemini

Each provider exposes a runtime.ts that satisfies the ChatRuntime contract and a manifest.json or equivalent declaring its metadata (available models, capabilities).


Workers are headless LLM processes declared by manifest.json. They are not executed directly — arka-deck’s worker system invokes them through the for-workers.ts port.

workers/<name>/
├── manifest.json # declaration (input, output, runtime, prompt_source)
├── prompt.json # local prompt (optional — otherwise via Cortex)
└──

To design a worker, see ../extension/ecrire-un-worker.md (coming).


Structural decisions are documented in ../../adr/:

ADRTopic
0001Hexagonal architecture
0002Addon contract
0003Materializer pattern
0004GitHub-only distribution
0005Local AES-GCM secrets
0006Anti-SSRF provider protection

LayerTechnology
RuntimeNode.js ≥ 20.19.0, TypeScript (strict)
HTTP serverFastify 5.8.5
UIReact 18, Vite, Tailwind CSS, React Query
Local storageSQLite (better-sqlite3), JSON files
SecretsAES-256-GCM under ~/.arka-deck/
ValidationZod
Claude SDK@anthropic-ai/claude-agent-sdk 0.2.129
OrchestrationLangChain / LangGraph
TestsVitest (unit), Playwright (E2E)
LintingESLint (max-warnings=0), Prettier