Addon contract — formal reference
This document describes the formal interface an addon must respect to integrate into arka-deck. It complements the write an addon tutorial with exact types and contractual guarantees.
Source of truth for types: core/ports/outbound/, core/domain/events/arka-event.ts.
manifest.json
Section titled “manifest.json”Each addon declares a manifest.json at its root.
{ "name": "my-addon", "version": "1.0.0", "kind": "capability", "description": "Short addon description.", "status": "active", "workers": ["worker-name-1"], "dependencies": [], "entrypoint": "src/index.ts"}| Field | Type | Values | Required |
|---|---|---|---|
name | string | kebab-case | yes |
version | string | semver | yes |
kind | string | "capability" / "orchestration" / "feature" / "convention" | yes |
description | string | max ~200 chars | yes |
status | string | "active" / "draft" | yes |
workers | string[] | attached worker names | yes (empty if none) |
dependencies | string[] | required other addon names | yes (empty if none) |
entrypoint | string | relative path from addon root | yes for capability / orchestration |
label | string | displayable label | no |
runtime | string | "cli" / "web" / "shared" | no |
tier | string | "free" / "standalone" / "cortex" / "squad-business" | no |
exposes | object | declarative metadata | no |
The executable schema is composition/addons/manifest-schema.ts.
kind values in the JSON manifest are aligned with the runtime AddonManifest.category category from the TypeScript registry. LLM providers are not an addon kind: they use the separate ProviderManifestSchema.
First-party loading
Section titled “First-party loading”At startup, composition loads first-party manifests via composition/addons/loader.ts and also checks addon ↔ worker references. Warnings are logged, but declarative loading does not replace explicit runtime wiring in the composition root.
Current public limit
Section titled “Current public limit”The addon registry is an internal first-party brick. It validates manifests and provides a stable contract, but arka-deck does not yet load an arbitrary third-party marketplace in production.
Consequence for public documentation: speak of “addon contract” or “first-party extensible addons”, not of an already-active public marketplace. Runtime additions stay explicitly wired in the composition root.
The registry accepts two distinct forms:
Addon: the registry callsregister(deps)and builds the runtime.RegisteredRuntimeAddon: the composition root has already built the runtime; the registry stores it as-is and does not callregister(deps).
This distinction avoids presenting already-wired addons (cortex-actions, gouvernance-lite, memory-local) as if they were built by the registry.
Register function
Section titled “Register function”The addon entry point is a register<Name>Addon(deps) function exported from src/index.ts.
export interface MyAddonDeps { readonly clock: Clock; readonly filesystem: Filesystem; readonly eventBus: EventBus; readonly idGenerator?: IdGenerator; readonly resolveSession: (sessionId: string) => Promise<SessionContext | null>; readonly cortexBaseUrl?: string;}
export interface MyAddonRuntime { readonly forMyAddon: ForMyAddon; readonly beforeTurnAugmenter: BeforeTurnAugmenter; readonly unsubscribe: () => void;}
export function registerMyAddon(deps: MyAddonDeps): MyAddonRuntime { // 1. Instantiate adapters // 2. Build use-cases // 3. Subscribe to the bus // 4. Build the augmenter (if applicable) return { forMyAddon, beforeTurnAugmenter, unsubscribe };}Rule: unsubscribe() must always be present and idempotent. It is called at shutdown.
Available dependencies
Section titled “Available dependencies”Composition (composition/core-container.ts) provides these dependencies to each addon:
| Dep | Type | Source | Usage |
|---|---|---|---|
clock | Clock | core/ports/outbound/clock.ts | Current timestamp (clock.now()) |
filesystem | Filesystem | core/ports/outbound/filesystem.ts | File read/write (allowlist) |
eventBus | EventBus | core/ports/outbound/event-bus.ts | Subscribe and publish events |
idGenerator | IdGenerator | core/ports/outbound/id-generator.ts | Generate stable IDs |
resolveSession | (sessionId) => Promise<Context|null> | Composition | Resolve projectPath from sessionId |
cortexBaseUrl | string? | Env ARKA_DECK_CORTEX_URL | Cortex URL override for tests |
EventBus
Section titled “EventBus”export type Unsubscribe = () => void;
export interface EventBus { publish(event: ArkaEvent): Promise<void>; publishAsync(event: ArkaEvent): void; subscribe<T extends ArkaEventType>(type: T, handler: EventHandler<T>): Unsubscribe;}Guarantees:
- An error in one handler does not impact others (isolation via
allSettled) - Handler order is not guaranteed
unsubscribeis idempotent- The bus is not persistent — events produced before subscription are not replayable
For the detailed guide, see ../architecture/event-bus.
Event catalogue (ArkaEvent)
Section titled “Event catalogue (ArkaEvent)”Source of truth: core/domain/events/arka-event.ts. Same categories as the French version: workspace, project, provider, catalogue/installs, chat session/turn/blocks/tools/compaction/slash/runtime, memory, settings, system, addons/workers, squad orchestration.
See the French version for the full type list — names are identical.
BeforeTurnAugmenter
Section titled “BeforeTurnAugmenter”Port core/ports/outbound/before-turn-augmenter.ts. Implement this port if your addon must inject context into a chat turn.
export interface BeforeTurnAugmenter { augmentPrompt(input: BeforeTurnAugmentInput): Promise<string | null>;}
export interface BeforeTurnAugmentInput { readonly sessionId: string; readonly turnKind?: 'silent_prepare' | 'visible_user' | 'resume'; readonly visibleTurnIndex?: number;}Expected behavior:
- Return
nullif no augmentation for this turn - If the addon has state (e.g. a selected artifact), consume it in
augmentPrompt— the second call will returnnull - Do not throw — return
nullon non-fatal errors
Storage conventions
Section titled “Storage conventions”Addons write under .arka-deck/addons/<addon-name>/ inside the project folder.
<project-root>/└── .arka-deck/ └── addons/ └── my-addon/ ├── options.json └── logs/Rule: never write outside .arka-deck/addons/<your-addon>/. The Filesystem port enforces an allowlist — writes outside this perimeter are rejected.
For global (non-project) data: ~/.arka-deck/addons/<addon-name>/.
Types importable from core
Section titled “Types importable from core”import type { ArkaEvent, ArkaEventType } from '../../core/domain/events/arka-event.js';import type { EventBus } from '../../core/ports/outbound/event-bus.js';import type { Filesystem } from '../../core/ports/outbound/filesystem.js';import type { Clock } from '../../core/ports/outbound/clock.js';import type { IdGenerator } from '../../core/ports/outbound/id-generator.js';import type { BeforeTurnAugmenter } from '../../core/ports/outbound/before-turn-augmenter.js';Relative path: from addons/<name>/src/, core/ is at ../../../core/.
Public sheet template
Section titled “Public sheet template”To write an addon sheet for marketing/public-site usage, use the bilingual template docs/addons/_TEMPLATE.md (sections: short pitch, hero, what it does, mechanics/flow, compatibility, stay tuned).
This template is distinct from dev sheets (which stay under docs/dev/addons-firstparty/).