ADR-0169shipped

CLI Capability Interface Contracts

Status: shipped
Date: 2026-02-28
Updated: 2026-02-28
Deciders: Joel Hooks
Supersedes: None
Related: ADR-0144 (hexagonal architecture), ADR-0163 (adaptive prompt architecture), ADR-0172 (agent mail adapter), ADR-0181 (AT Proto adapter track)

Context

SYSTEM.md defines platform capabilities as joelclaw CLI commands (otel, recall, mail, secrets, deploy, notify, heal, log). These are the agent-facing operational surface.

Today, capability behavior is split across direct command implementations, shell wrappers, and external tools. Some capabilities are fully implemented, some are missing, and some are spread across multiple commands. There is no single typed contract for capability inputs/outputs, no adapter registry, and no shared configuration loader for capability backends.

Current State (2026-02-28)

CapabilityIn SYSTEM.mdCurrent CLI SurfaceStateCurrent Backend RealityGap to Contract Target
oteljoelclaw otel {list,search,stats}Implemented (phase 4)Capability adapter typesense-otel via registry/runtimeMaintain output parity while adapter internals evolve
recalljoelclaw recallImplemented (phase 4)Capability adapter typesense-recall via registry/runtimeMaintain retrieval quality and rewrite observability under adapter path
secretsjoelclaw secrets {status,lease,revoke,audit,env}Implemented (phase 1)Capability adapter agent-secrets-cli wrapping existing backendExpand adapter breadth as backend evolves
mailjoelclaw mail {status,register,send,inbox,reserve,release,...}Implemented (phase 4)Capability adapter mcp-agent-mail under registry/runtime (MailPort)Keep adapter health/diagnostics explicit and deterministic
deployjoelclaw deploy workerImplemented (phase 3)Capability adapter scripted-deploy orchestrating deterministic worker sync pathExpand adapter coverage beyond worker sync only if needed
notifyjoelclaw notify sendImplemented (phase 1)Capability adapter gateway-redis on canonical gateway queue pathAdd more notify verbs only if needed
healjoelclaw heal {list,run}Implemented (phase 3)Capability adapter runbook-heal over deterministic runbook engineKeep legacy recover surface as compatibility path during migration
logjoelclaw log writeImplemented (phase 1)Capability adapter slog-cli with structured envelope responsesAdd read-side parity only if useful (logs remains read path)
subscribe❌ (not listed)joelclaw subscribe {list,add,remove,check,summary}Implemented (phase 4)Capability adapter redis-subscriptions under registry/runtimeKeep as non-SYSTEM extension capability and preserve existing UX

Additional drift to address

  • joelclaw capabilities already exists as a navigation catalog command. This ADR uses “capability” as an architectural contract concept; both must be tied to the same registry to avoid drift.
  • Existing command implementations are not consistently aligned with ADR-0144 extraction boundaries (thin CLI composition root + heavy logic in reusable adapters/packages).
  • CLI runtime config is currently centered on ~/.config/system-bus.env (packages/cli/src/config.ts), not capability TOML.

Decision

1) Treat each capability as a typed port with named adapters

A capability is not only a command name; it is a stable contract that can be implemented by interchangeable adapters.

import { Effect, Schema } from "effect"
 
export interface CapabilityContext {
  readonly cwd: string
  readonly now: Date
  readonly config: JoelclawCapabilitiesConfig
}
 
export interface CapabilityCommandSpec<TArgs, TResult> {
  readonly summary: string
  readonly argsSchema: Schema.Schema<TArgs>
  readonly resultSchema: Schema.Schema<TResult>
}
 
export interface CapabilityPort<
  TCommands extends Record<string, CapabilityCommandSpec<any, any>>,
> {
  readonly capability: string
  readonly adapter: string
  readonly commands: TCommands
  execute<K extends keyof TCommands>(
    subcommand: K,
    args: Schema.Schema.Type<TCommands[K]["argsSchema"]>,
    context: CapabilityContext,
  ): Effect.Effect<
    Schema.Schema.Type<TCommands[K]["resultSchema"]>,
    CapabilityError
  >
}
 
export interface CapabilityError {
  readonly code: string
  readonly message: string
  readonly retriable: boolean
  readonly fix?: string
}

Contract implications:

  • Subcommands are typed (no free-form string dispatch).
  • Args/results have runtime validation via schemas.
  • Adapters return typed results; CLI envelope wrapping stays centralized.

2) Keep CLI as composition root (ADR-0144)

  • packages/cli remains thin command wiring + envelope formatting.
  • Capability adapter logic lives in dedicated @joelclaw/* packages/modules.
  • No cross-package ../../src/* imports for adapter logic.

3) Standardize capability config with clear precedence

Capability adapter selection is configured in TOML with deterministic override order:

  1. CLI flags (highest)
  2. Environment variables
  3. Project .joelclaw/config.toml
  4. User ~/.joelclaw/config.toml
  5. Built-in defaults
# ~/.joelclaw/config.toml
 
[capabilities.mail]
enabled = true
adapter = "mcp-agent-mail"
 
[capabilities.mail.adapters.mcp-agent-mail]
endpoint = "http://127.0.0.1:8765"
timeout_ms = 5000
 
[capabilities.secrets]
enabled = true
adapter = "agent-secrets"
 
[capabilities.notify]
enabled = true
adapter = "gateway-redis"

4) Clarify command overlap semantics

  • log = write structured entries (joelclaw log write ...)
  • logs = read/analyze runtime logs (joelclaw logs ...)
  • notify = canonical alerting command; gateway push remains low-level transport/debug surface
  • deploy = canonical deployment trigger; existing specialized deploy commands become adapter-specific implementations

Phased Implementation Plan (value/effort ordered)

Phase 0 — Contract foundation (small, required first)

  • Add capability contract types + adapter registry.
  • Add TOML capability config loader with precedence + schema validation.
  • Keep existing command behavior unchanged.

Exit criteria: registry + config loader exists; no command regressions.

Phase 1 — Fill SYSTEM.md command gaps with thin adapters (high value, low effort)

  • Ship joelclaw secrets (adapter wraps existing secrets CLI behavior).
  • Ship joelclaw log (adapter wraps slog write / canonical event path).
  • Ship joelclaw notify (adapter wraps current gateway push flow).

Exit criteria: secrets, log, notify commands exist and return standard HATEOAS envelopes.

Phase 2 — Ship joelclaw mail (highest value missing capability)

  • Implement MailPort + mcp_agent_mail adapter (ADR-0172).
  • Subcommands: register, send, read, reserve, release.
  • Add capability health check path for adapter reachability.

Exit criteria: role/system workflows can actually execute joelclaw mail commands end-to-end.

Phase 3 — Consolidate fragmented operations

  • Implement canonical joelclaw deploy capability (initial adapter: existing script/CLI workflows).
  • Implement canonical joelclaw heal capability (compose existing recover/nas/inngest heal flows).
  • Keep legacy subcommands as compatibility aliases until migration completes.

Exit criteria: deploy/heal actions available behind single capability commands with explicit adapters.

Phase 4 — Migrate already-implemented capabilities behind registry

  • Move existing otel, recall, subscribe commands onto capability registry interfaces.
  • Keep command UX stable; swap internals to adapter architecture.

Exit criteria: all target capabilities execute through the same port/adapter path.

Phase 5 — Optional AT Protocol mail adapter (deferred)

  • Add atproto-pds adapter only after parity tests with MCP adapter and lexicon stabilization.
  • No impact to CLI contract surface.

Exit criteria: adapter swap via config only, no command changes.

AT Protocol evolution path

Adapter swap remains a valid direction, but is explicitly deferred until prerequisites exist:

  • stable mail lexicon + message/reservation mapping
  • identity/auth strategy for multi-agent writes
  • parity tests proving mcp-agent-mail and atproto-pds adapters satisfy the same MailPort contract

Until then, AT Proto remains future scope, not phase-1 delivery.

Implementation Update (2026-02-28)

  • Phase 0 shipped: capability contract types, registry, and deterministic config precedence loader (flags > env > project > user > defaults).
  • Phase 1 shipped: joelclaw secrets, joelclaw log, and joelclaw notify command groups with HATEOAS envelopes and adapter-backed execution.
  • Phase 2 + 4 shipped with ADR-0172 alignment: joelclaw mail now executes through capability registry/runtime via mcp-agent-mail (MailPort), not bespoke command internals.
  • Phase 3 shipped: canonical joelclaw deploy and joelclaw heal command roots now execute through capability adapters (scripted-deploy, runbook-heal).
  • Phase 4 shipped: otel, recall, and subscribe command internals now execute through capability registry/runtime adapters (typesense-otel, typesense-recall, redis-subscriptions) while preserving command UX/envelopes.
  • Remaining ADR-0169 scope: none required for this ADR. Optional atproto-pds adapter work is now tracked separately in ADR-0181.

Consequences

  • Capability contracts become concrete and testable (typed subcommands + schemas).
  • SYSTEM.md capability claims become executable in prioritized phases.
  • CLI remains a thin composition root aligned with ADR-0144.
  • Adapter swapping becomes practical (mail now, AT Proto later) without command rewrites.
  • Scope becomes deliverable: immediate value first, aspirational items clearly deferred.