Give AI coding agents mechanical architecture enforcement — not just prompt templates.
Typed port contracts | Static boundary analysis | Multi-agent swarm coordination | Token-efficient AST summaries
When AI agents generate code autonomously, they produce spaghetti. Adapters import other adapters. Domain logic leaks into HTTP handlers. Database queries appear in UI components. No amount of prompt engineering prevents this at scale.
Traditional AI coding tools improve the conversation with AI. hex improves the output.
# Scaffold a new hexagonal project
npx @anthropic-hex/hex scaffold my-app --lang typescript
# Initialize hex in an existing project (works on 100K+ LOC codebases)
npx @anthropic-hex/hex init --lang ts
# Analyze architecture health
npx @anthropic-hex/hex analyze .
# Generate token-efficient summaries for AI context
npx @anthropic-hex/hex summarize src/ --level L1How the layers work
| Layer | May Import From | Purpose |
|---|---|---|
domain/ |
domain/ only |
Pure business logic, zero external deps |
ports/ |
domain/ only |
Typed interfaces — contracts between layers |
usecases/ |
domain/ + ports/ |
Application logic composing ports |
adapters/primary/ |
ports/ only |
Driving: CLI, HTTP, MCP, Dashboard |
adapters/secondary/ |
ports/ only |
Driven: FS, Git, LLM, TreeSitter, Ruflo, Secrets |
composition-root.ts |
Everything | The ONLY file that imports adapters |
The golden rule: Adapters NEVER import other adapters. This is the most common mistake AI agents make, and hex analyze catches it every time.
Ports are typed interfaces. When an AI agent is told "implement this adapter against this port," it has clear input/output contracts — not prose descriptions:
// Port: the contract (what we need)
export interface IFileSystemPort {
read(filePath: string): Promise<string>;
write(filePath: string, content: string): Promise<void>;
exists(filePath: string): Promise<boolean>;
glob(pattern: string): Promise<string[]>;
streamFiles(pattern: string, options?: StreamOptions): AsyncGenerator<string>;
}
// Adapter: the implementation (how we do it)
// AI generates this within its boundary — can't leak into other adapters
export class S3Adapter implements IFileSystemPort {
async read(filePath: string): Promise<string> { /* S3 GetObject */ }
async write(filePath: string, content: string): Promise<void> { /* S3 PutObject */ }
async exists(filePath: string): Promise<boolean> { /* S3 HeadObject */ }
async glob(pattern: string): Promise<string[]> { /* S3 ListObjectsV2 */ }
}$ hex analyze .
Architecture Analysis
=====================
Dead exports: 0 found
Hex violations: 0 found
Circular deps: 0 found
✓ All hexagonal boundary rules passWhen an adapter imports another adapter:
- Hex violations: 1 found
- ✗ src/adapters/secondary/cache-adapter.ts imports from
- src/adapters/secondary/filesystem-adapter.ts
- Rule: adapters must NEVER import other adapters
hex plan "JWT auth with rate limiting"Decomposes into adapter-bounded steps: steps:
- adapter: secondary/auth
port: IAuthPort
task: "JWT generation + validation"
tokenBudget: 4000
- adapter: secondary/rate-limiter
port: IRateLimitPort
task: "Sliding window limiter"
tokenBudget: 3000 |
Each step generates code within its boundary. The AI agent receives:
hex generate \
--adapter secondary/auth \
--port IAuthPort \
--lang typescript |
|
Three levels, integrated into the workflow: # Unit tests (mock ports, test logic)
bun test
# Property tests (fuzz inputs)
bun test --property
# Smoke tests (can it start?)
hex validate . |
Validation is a blocking gate:
Only then: bun run build && git commit |
During feature development, hex shows a persistent status view that eliminates agent console noise:
hex feature: webhook-notifications
────────────────────────────────────────────────────────────────────────
Phase 1/7: SPECS ✓ Complete (5 specs, 1 negative)
Phase 2/7: PLAN ✓ Complete (8 tasks, 3 tiers)
Phase 3/7: WORKTREES ✓ Created 8 worktrees
Phase 4/7: CODE ⟳ In Progress (3/8 done, 5 running)
Workplan:
Tier 0 (domain/ports)
✓ domain-changes (feat/webhook-notifications/domain)
✓ port-changes (feat/webhook-notifications/ports)
Tier 1 (adapters - parallel)
✓ git-adapter Q:95 [===========] test
⟳ webhook-adapter Q:82 [========---] lint
⟳ cli-adapter Q:78 [=======----] test
⏳ mcp-adapter [ ] queued
⏳ fs-adapter [ ] queued
Tier 2 (integration)
⏳ composition-root [ ] queued
⏳ integration-tests [ ] queued
Overall: 38% │ Tokens: 124k/500k │ Time: 3m42s │ Blockers: 0
────────────────────────────────────────────────────────────────────────
[Press 'd' for details | 'q' to abort | 'h' for help]
Agent logs are redirected to .hex/logs/agent-<name>.log to keep the console clean. Press 'd' to view detailed logs, 'q' to abort cleanly, or 'h' for help. This is powered by IFeatureProgressPort with structured event streaming via the event bus.
hex coordinates multiple AI agents working in parallel via ruflo (@claude-flow/cli), with lock-based coordination to prevent duplicate work and file conflicts across instances (ADR-022).
|
interface SwarmConfig {
topology: 'hierarchical' | 'mesh'
| 'hierarchical-mesh';
maxAgents: number; // default: 4
strategy: 'specialized' | 'generalist'
| 'adaptive';
consensus: 'raft' | 'pbft';
memoryNamespace: string;
} |
Swarm Port Interface (full)
interface ISwarmPort {
// Lifecycle
init(config: SwarmConfig): Promise<SwarmStatus>;
createTask(task: SwarmTask): Promise<SwarmTask>;
completeTask(taskId: string, result: string, commitHash?: string): Promise<void>;
spawnAgent(name: string, role: AgentRole, taskId?: string): Promise<SwarmAgent>;
// Pattern learning — agents get smarter over time
patternStore(pattern: AgentDBPattern): Promise<AgentDBPattern>;
patternSearch(query: string, category?: string): Promise<AgentDBPattern[]>;
patternFeedback(feedback: AgentDBFeedback): Promise<void>;
// Persistent memory across sessions
memoryStore(entry: SwarmMemoryEntry): Promise<void>;
memoryRetrieve(key: string, namespace: string): Promise<string | null>;
// Hierarchical memory (layer > namespace > key)
hierarchicalStore(layer: string, namespace: string, key: string, value: string): Promise<void>;
hierarchicalRecall(layer: string, namespace?: string): Promise<SwarmMemoryEntry[]>;
// Intelligence
consolidate(): Promise<{ merged: number; removed: number }>;
contextSynthesize(query: string, sources?: string[]): Promise<string>;
getProgressReport(): Promise<AgentDBProgressReport>;
}hex-hub is a Rust-native dashboard service that runs as a system-wide daemon on port 5555. It provides real-time visibility into architecture health, token efficiency across summary levels, interactive dependency graphs with violation detection, and swarm agent/task status — all streamed via WebSocket.
# Auto-starts on any hex command (if binary installed)
hex analyze .
# Manual control
hex daemon start # Start daemon
hex daemon stop # Stop daemon
hex hub status # Show status + connected projects
# Open in browser
open http://localhost:5555Dashboard features:
- Multi-project tabs — switch between projects with live freshness indicators
- Architecture health ring — real-time score with violation/dead-export breakdown
- Token efficiency panel — L0–L3 compression bars per file
- Swarm status — agent list, task progress, status badges with pulse animations
- Dependency graph — interactive canvas with hexagonal ring layout, zoom/pan, click-to-trace transitive deps, violation highlighting
- Command chat — send commands (
hex analyze,hex claude <prompt>,spawn-agent) directly from the browser - Event log — filterable real-time stream (errors, decisions, milestones)
- Decision modal — interactive prompts for agent decisions requiring human input
1.5MB binary, zero runtime dependencies. Modern dark theme optimized for extended developer use.
Persistence & coordination:
- Swarm state (agents, tasks, events) is persisted in SQLite (
hex_hub.db) — survives daemon restarts (ADR-015) - Hub binary embeds a compile-time build hash for version verification against the TypeScript CLI (ADR-016)
- Multi-instance coordination uses
ICoordinationPortwith worktree locks, task claiming, and activity broadcasting to prevent duplicate work and file conflicts (ADR-011, ADR-022) SwarmOrchestratoracquires locks before worktree creation;WorkplanExecutorclaims tasks before agent spawn — conflicts throw typedWorktreeConflictError/TaskConflictError
See hex-hub/README.md for full API reference.
hex provides real-time feedback through four notification channels behind a single INotificationEmitPort interface:
| Channel | Adapter | Purpose |
|---|---|---|
| Terminal | TerminalNotifier |
Color-coded messages, persistent status bar, interactive decision prompts |
| File Log | FileLogNotifier |
Structured JSONL audit trail in .hex/activity.log, rotated at 10 MB |
| Webhook | WebhookNotifier |
External integration (Slack, CI); batched delivery with exponential-backoff retry |
| Event Bus | EventBusNotifier |
In-memory pub/sub for agent-to-agent coordination; wildcard subscriptions |
When agents encounter ambiguous choices (e.g., two valid architectural approaches), they emit a DecisionRequest with:
- Numbered options with risk ratings
- Configurable deadline (default: 5 minutes)
- Auto-select default option if no human response (prevents agent stalls)
[execute] coder-1: generating tests | quality: 85 | 3/6 adapters | ████░░ 50%
Fields: [phase], active agent and step, quality score from QualityScore.score, adapter completion ratio, and a Unicode progress bar. All channels are independently testable via constructor-injected dependencies (no global state).
A 500-line adapter becomes a 30-line L1 summary. This is how AI agents understand your codebase without blowing their context window.
| Level | What's Included | Tokens | Use Case |
|---|---|---|---|
L0 |
File list only | ~2% | Project overview, file discovery |
L1 |
Exports + function signatures | ~6% | Ideal for AI context — the sweet spot |
L2 |
L1 + function bodies | ~40% | Detailed understanding of logic |
L3 |
Full source code | 100% | Complete file contents |
# Generate L1 summaries for the whole project
hex summarize src/ --level L1Powered by tree-sitter (WASM) for language-agnostic AST extraction.
Why architecture-first beats spec-first
SPECKit gives AI agents prose descriptions. The agent decides how to structure the code. Works for small features, produces spaghetti at scale. Known issues: duplicative documentation, incomplete implementations that "look done" in specs.
BMAD simulates an agile team with 12+ markdown personas. No real multi-agent orchestration — users manually invoke each persona. Architecture decisions are in documents, not enforced in code. Complexity grows with every persona added.
hex gives AI agents typed port interfaces. The agent knows exactly what methods to implement, what types to accept, and what boundary it's working within. Architecture is enforced mechanically.
The difference compounds:
- At 10 files, any approach works
- At 100 files, only enforced boundaries prevent collapse
- At 1000 files, hex's static analysis is the difference between a maintainable codebase and a rewrite
# Global install
npm install -g @anthropic-hex/hex
# Or use npx
npx @anthropic-hex/hex --helpRequirements: Node.js >= 20, Bun (for build/test)
| Command | Description |
|---|---|
hex build <requirements> |
Single entry point — auto-plans, orchestrates agents, generates code, analyzes, validates |
hex scaffold <name> |
Create a new hex project with full structure |
hex analyze <path> |
Architecture health check (dead code, violations, cycles) |
hex summarize <path> --level <L0-L3> |
Token-efficient AST summaries via tree-sitter |
hex generate |
Generate code within an adapter boundary |
hex plan <requirements> |
Decompose requirements into workplan steps |
hex validate <path> |
Post-build semantic validation (blocking gate) |
hex orchestrate |
Execute workplan steps via swarm agents |
hex status |
Swarm progress report |
hex daemon [start|stop|status] |
Manage hex-hub daemon (Rust dashboard service, port 5555) |
hex hub [start|stop|status] |
Alias for hex daemon |
hex dashboard |
Legacy Node.js dashboard (fallback if hex-hub binary not installed) |
hex mcp |
Start MCP stdio server for Claude Code / IDE integration |
hex setup |
Install tree-sitter grammars + skills + agents + hex-hub binary |
hex init [--fast|--minimal|--include|--large-project] |
Initialize project (supports large codebases via streaming scanner) |
hex adr list |
List all ADRs with status |
hex adr status |
Show ADR lifecycle summary |
hex adr search <query> |
Search ADRs by keyword |
hex adr abandoned |
Detect stale/abandoned ADRs |
hex help |
Show all commands and usage |
hex version |
Print current version |
Architecture Decision Records follow a tracked lifecycle with automated staleness detection:
proposed → accepted → (deprecated | superseded | rejected)
↓ ↓
stale abandoned (no git activity in 90 days)
Commands:
hex adr list [--status accepted]— Filter by lifecycle status (proposed, accepted, deprecated, superseded, rejected)hex adr status— Show status distribution (e.g., "15 accepted, 3 proposed, 2 deprecated")hex adr search <query>— Full-text search across all ADR content and metadatahex adr abandoned— Detect stale ADRs with no related commits in 90 days (indicates forgotten decisions)
ADRs are stored in docs/adrs/ and tracked via frontmatter (status, date, supersedes). The lifecycle system helps teams identify which architectural decisions are active vs. historical.
|
Available via
|
Pre-built YAML agents for swarm orchestration:
planner |
hex-coder |
integrator |
swarm-coordinator |
dependency-analyst |
dead-code-analyzer |
validation-judge |
behavioral-spec-writer |
scaffold-validator |
status-monitor |
dev-tracker |
||||
Powered by tree-sitter WASM for language-agnostic AST extraction:
| Capability | TypeScript | Go | Rust |
|---|---|---|---|
| AST Summarize (L0–L3) | Full | Full | Full |
| Export extraction | export keyword |
Capitalized names | pub visibility |
| Import extraction | import statements |
import declarations |
use declarations |
| Boundary validation | Full | Full | Full |
| Code generation | Full (TS rules) | Full (Go rules) | Full (Rust rules) |
| Path resolution | .js → .ts |
Module paths | crate:: paths |
| Scaffold | package.json + tsconfig.json |
go.mod |
Cargo.toml |
| Example project | 4 apps | 1 (weather) | 1 (rust-api) |
hex enforces compile/lint/test checks for all languages via IBuildPort:
| Method | TypeScript | Go | Rust |
|---|---|---|---|
| compile() | tsc --noEmit |
go build ./... |
cargo check |
| lint() | eslint --format json |
golangci-lint run --out-format json |
cargo clippy -- -D warnings |
| test() | bun test |
go test ./... -json |
cargo test |
The pre-commit hook automatically detects staged file languages and runs the corresponding toolchain checks. CI includes a rust-check job for hex-hub validation (compiles, lints, tests, and verifies --build-hash works).
Example: Go Backend (Weather API)
The examples/weather/ directory shows hex applied to a Go project:
examples/weather/backend/src/
core/
domain/ # Weather types, F1 race data
ports/ # IWeatherPort, ICachePort
usecases/ # F1Service (composes ports)
adapters/
primary/
http_adapter.go # HTTP handlers + HTML templates
secondary/
jolpica_adapter.go # External F1 API client
cache_adapter.go # In-memory cache with TTL
composition-root.go # Wires adapters to ports
Same hexagonal rules, different language. The architecture transfers.
src/
core/
domain/ # Value objects, entities, domain events
ports/ # Typed interfaces (input + output)
usecases/ # Application logic
adapters/
primary/ # CLI, MCP, Dashboard, Notifications
secondary/ # FS, Git, TreeSitter, LLM, Ruflo, Build, Registry, Secrets
infrastructure/ # Tree-sitter query definitions
composition-root.ts # Single DI wiring point
cli.ts # CLI entry point
index.ts # Library public API
tests/
unit/ # London-school mock-first tests
integration/ # Real adapter tests
examples/ # Reference apps (weather, rust-api, flappy-bird, todo-app, test-app, summaries)
hex-hub/ # Rust dashboard daemon (axum, system-wide on port 5555)
agents/ # Agent definitions (YAML)
skills/ # Skill definitions (Markdown)
config/ # Language configs, tree-sitter settings
docs/
adrs/ # Architecture Decision Records
analysis/ # Adversarial review reports
hex includes a status line script that shows real-time swarm and project health directly in your Claude Code terminal:
⬡ hex │ my-app │ ⎇main │ ●swarm 2⚡ [3/5] │ ●db │ ◉localhost:3456 │ ◉mcp │ 87/100
Indicators:
- Swarm —
●green (agents active) /●yellow (available, idle) /○dim (not configured) - Agent counts —
2⚡active,1💤idle,[3/5]tasks completed - AgentDB — pattern store connectivity
- Dashboard — clickable URL when running (auto-starts on project load)
- MCP — hex MCP server status
- Score — last architecture health score
Three-tier detection: .hex/status.json (written by hooks) → ~/.claude-flow/metrics (daemon) → ruflo MCP config (fallback). Auto-configured during hex init.
bun run build # Bundle CLI + library to dist/
bun test # Run all tests (unit + property + smoke)
bun run check # TypeScript type check (no emit)
hex analyze . # Architecture validation
hex setup # Install grammars + skills + agents + hex-hub binary
cargo build --release -p hex-hub # Build dashboard binary manuallyWhy these choices?
| Decision | Rationale |
|---|---|
| Tree-sitter over regex | WASM-based AST extraction works across languages; regex breaks on edge cases |
| Hybrid TS+Rust via NAPI | Tree-sitter hot path in native Rust (5-10x faster than WASM); falls back to WASM if binary unavailable (ADR-010) |
| Ruflo as required dep | Swarm coordination is not optional; even solo workflows benefit from task tracking |
| Single composition root | Only one file imports adapters; adapter swaps are one-line changes |
| L0-L3 summary levels | AI agents need different detail at different phases; L1 is the sweet spot |
| Worktree isolation | Each agent gets a git worktree, not just a branch; prevents merge conflicts |
safePath() protection |
FileSystemAdapter prevents path traversal outside project root |
execFile not exec |
RufloAdapter prevents shell injection from untrusted inputs |
| London-school testing | Mock ports, test logic; hexagonal architecture makes this natural |
No mock.module() |
Tests use dependency injection (Deps pattern), never mock.module() — prevents mock/prod divergence (ADR-014) |
| Streaming filesystem scanner | streamFiles() uses an async generator with BFS queue and inode tracking — bounded memory regardless of project size (ADR-021) |
.hexignore defaults |
Smart exclusions (target/, node_modules/, build/) prevent OOM on large projects; falls back to .gitignore |
| Lock-before-worktree | SwarmOrchestrator acquires coordination lock before creating worktree; prevents parallel agents from stomping each other (ADR-022) |
| Claim-before-spawn | WorkplanExecutor claims tasks via hub before spawning agents; prevents duplicate work across instances |
hex_build single entry point |
Users describe what to build; hex handles plan → orchestrate → analyze → validate internally |
| Pluggable secrets chain | ISecretsPort adapters stack: Infisical → LocalVault → env-var; composition root selects |
| Dashboard auto-start | Dashboard HTTP server launches on project load; port conflicts and stale locks self-heal |
| Rust hub over Node hub | 1.5MB binary vs Node.js process; zero runtime deps; system-wide daemon serves all projects |
| WebSocket over SSE | Unified bidirectional channel for events, commands, and chat; 25s keepalive pings detect dead connections |
run-claude hub command |
Invoke Claude CLI from dashboard chat; enables browser-based agent interaction without terminal |
hex includes ISecretsPort with a pluggable adapter chain for secret resolution:
| Adapter | When It's Used |
|---|---|
InfisicalAdapter |
Production — fetches from Infisical vault |
LocalVaultAdapter |
Development — encrypted local file (~/.hex/vault.json) |
EnvSecretsAdapter |
Fallback — reads from environment variables |
CachingSecretsAdapter |
Wraps any adapter with TTL-based in-memory cache |
The composition root selects the adapter chain based on environment. Secrets never leak into domain or adapter code — only the composition root calls ISecretsPort.
| Protection | Implementation |
|---|---|
| Path traversal | FileSystemAdapter.safePath() blocks ../ escapes |
| Shell injection | RufloAdapter uses execFile (not exec) |
| Secret management | ISecretsPort — Infisical / LocalVault / env-var adapter chain |
| XSS prevention | Primary adapters must not use innerHTML with external data |
| Credential safety | .env files are gitignored; .env.example provided |
| Dashboard auth | Bearer token authentication for HTTP endpoints |
| Pre-commit gate | Security audit hook blocks commits with violations |
hex builds on the Hexagonal Architecture pattern (also known as Ports and Adapters), originally conceived by Alistair Cockburn in 2005.
"Allow an application to equally be driven by users, programs, automated test or batch scripts, and to be developed and tested in isolation from its eventual run-time devices and databases." — Alistair Cockburn
- Hexagonal Architecture — Alistair Cockburn's original article defining the Ports and Adapters pattern
- Growing Object-Oriented Software, Guided by Tests — Steve Freeman & Nat Pryce. The London-school TDD approach that hex's test strategy follows
- Clean Architecture — Robert C. Martin. Concentric dependency rule that hex enforces via static analysis
- tree-sitter — Max Brunsfeld et al. Language-agnostic parsing framework powering hex's L0-L3 AST summaries
- ruflo / claude-flow — Reuven Cohen (@ruvnet). Multi-agent swarm coordination framework
- Infisical — Open-source secrets management platform integrated via
ISecretsPort
| Contributor | Role |
|---|---|
| Gary (@gaberger) | Creator, architect, primary developer |
| Claude (Anthropic) | AI pair programmer — code generation, testing, documentation |
MIT — Use it, fork it, build on it.
Built for AI agents that write code, not just chat about it.
Quick Start • Architecture • Workflow • Swarm • CLI • Claude Code
