π reference β overview/guide, not tied to a code symbol
Validation by Beadloom
doc_syncβ same source assync-check.
Beadloom Architecture β
π reference β overview/deep-dive, not tied to a single code symbol. It aligns with (does not replace) the generated
/architectureC4 overview on the published portal.
Beadloom turns Architecture as Code into Architectural Intelligence β structured, queryable knowledge about your system that humans and AI agents consume in <20ms.
System Design β
The system is organized into six DDD domain packages, an application (use-case orchestration) layer, and two interface layers:
Domains:
- Context Oracle (
context_oracle/) β BFS graph traversal, context bundle assembly, code indexing, two-tier caching, FTS5 search,whyimpact analysis - Doc Sync (
doc_sync/) β docβcode synchronization tracking, stale detection, symbol-level hashing, docs audit - Graph (
graph/) β YAML graph loader, diff engine, rule engine, import resolver (9 languages), architecture linter, C4 diagram emitter, federation - Onboarding (
onboarding/) β project bootstrap, doc generation/polishing, architecture-aware presets, AGENTS.md / IDE-rules generation, config sync, and the agentic-flow role configurator (flow_config.py,role_composer.py,role_adapters.py) - Infrastructure (
infrastructure/) β domain-agnostic SQLite database layer, health metrics, git-activity tracking - AI Agents (
ai_agents/) β governed AI-agent harnesses that ship inside the wheel; hosts the deterministic, seam-isolated AI tech-writer (ai_agents/ai_techwriter/, run viapython -m beadloom.ai_agents.ai_techwriter). A leaf consumer: it may readapplication/context_oracle/graph/doc_syncAPIs but must never be imported by the core domains or services (enforced by thecore-no-import-ai-agents/application-no-import-ai-agentsforbid_importrules).
Application (use-case orchestration) (application/) β composes the domains into end-to-end use cases. It owns:
reindex/β the full + incremental reindex pipeline (drop β recreate β reload graph/docs/code/sync; SHA-256file_indexfor incremental runs), a cohesion-split packagedoctor.pyβ graph + data integrity checksstatus.pyβ the read-side ofbeadloom status(index/coverage/health/trend counts + context-bundle metrics)debt_report/β architecture-debt aggregation, scoring, trend tracking, CI gating, a cohesion-split packagewatcher.pyβ file watcher for auto-reindex on changegate.pyβ the unifiedbeadloom cigate (reindex β lint β sync-check β config-check β doctor β optional federate)- the VitePress site generators β
site.py(orchestrator),site_pages.py,site_nav.py,site_about.py,site_dashboard/(a cohesion-split package),site_landscape.py,site_published.py,site_mermaid_guard.py,site_metrics_history.py
Interface layers:
- Services (
services/) β CLI (services/cli.pyis a thin Click registration shell; the command implementations live in theservices/commands/package, one cohesive module per command group) and MCP Server (services/mcp_server.py, stdio server with 18 tools for AI agents β 14 graph read/write tools + four BDL-048 process-tools). Both call into the application layer and Context Oracle; the CLI never reaches past those layers. - TUI (
tui/) β interactive terminal architecture workstation (Textual): dashboard, explorer, doc-status screens.
A layers rule in .beadloom/_graph/rules.yml enforces the direction services / tui β application β domains (the interface layers depend inward; domains never depend on the application or service layers).
Node Kinds β
The graph distinguishes the kinds of node it tracks:
| Kind | Doc | Annotation | Description |
|---|---|---|---|
service | services/<name>.md | # beadloom:service=<id> | An interface/process boundary (CLI, MCP server, TUI) |
domain | domains/<name>/README.md | # beadloom:domain=<id> | A DDD domain package |
feature | features/<name>/SPEC.md | # beadloom:feature=<id> | A user-facing capability inside a domain |
component | <name>/DOC.md | # beadloom:component=<id> | An internal/infra building block β the mirror of a feature for code that is not user-facing |
entity / adr | β | β | Domain entities and architecture decisions |
The component kind (BDL-051) and the module-coverage lint (promoted to severity: error) together close the no-shadow-code gap: every src module with at least one symbol must be a tracked node (feature or component, or covered by a node's source β including a directory source like tui/) or named on a small, visible exempt: list in rules.yml. A new untracked module therefore fails beadloom lint --strict / beadloom ci.
Specification β
Data Flow β
The application/reindex/ orchestrator drives the indexing pipeline, calling each domain in order; the resulting SQLite index is then read back by Context Oracle for sub-20ms context bundles.
YAML Graph Files (.beadloom/_graph/*.yml)
β
application/reindex/ (orchestrates the pipeline below)
β
ββ graph/loader.py β SQLite (nodes, edges, rules)
ββ graph/import_resolver.py β SQLite (code_imports)
ββ doc_sync/doc_indexer.py β SQLite (docs, chunks, search_index)
ββ context_oracle/code_indexer.py β SQLite (code_symbols)
ββ (writes file_index, health_snapshots)
β
context_oracle/builder.py β BFS traversal β context bundle (JSON)
β β L1 memory / L2 SQLite cache
services/cli.py / services/mcp_server.py / tui/ β user / AI agentSQLite Schema β
The database is stored in .beadloom/beadloom.db and uses WAL mode for concurrent access.
Core tables (7):
| Table | Key columns | Description |
|---|---|---|
nodes | ref_id (PK), kind, summary, source, extra | Graph nodes (domain, feature, service, entity, adr) |
edges | src_ref_id, dst_ref_id, kind (composite PK), extra | Graph edges (part_of, depends_on, uses, implements, touches_entity, touches_code) |
docs | id (PK), path (UNIQUE), kind, ref_id (FKβnodes), hash, metadata | Document index |
chunks | id (PK), doc_id (FKβdocs), chunk_index, heading, section, content, node_ref_id | Document chunks (max 2000 chars) |
code_symbols | id (PK), file_path, symbol_name, kind, line_start, line_end, annotations, file_hash | Code symbols (function, class, type, route, component) |
sync_state | id (PK), doc_path, code_path, ref_id (FKβnodes), code_hash_at_sync, doc_hash_at_sync, synced_at, status, symbols_hash | Docβcode sync state (ok, stale) |
meta | key (PK), value | Index metadata (key-value) |
Infrastructure tables (7):
| Table | Key columns | Description |
|---|---|---|
health_snapshots | id (PK), taken_at, nodes_count, edges_count, docs_count, coverage_pct, stale_count, isolated_count, extra | Trend tracking across reindexes |
file_index | path (PK), hash (SHA-256), kind (graph/doc/code), indexed_at | Incremental reindex support |
bundle_cache | cache_key (PK), bundle_json, etag, graph_mtime, docs_mtime, created_at | L2 persistent context cache |
search_index | ref_id, kind, summary, content | FTS5 virtual table for full-text search |
code_imports | id (PK), file_path, line_number, import_path, resolved_ref_id, file_hash | Import relationships between files |
rules | id (PK), name (UNIQUE), description, rule_type (deny/require/forbid_edge/layer/cycle_detection/import_boundary/cardinality), rule_json, enabled | Architecture rules from rules.yml |
graph_snapshots | id (PK), label, created_at, nodes_json, edges_json | Point-in-time architecture graph captures for drift detection |
BFS Algorithm β
Context Oracle uses BFS with edge prioritization:
| Priority | Edge type | Description |
|---|---|---|
| 1 | part_of | Component is part of |
| 2 | touches_entity | Touches entity |
| 3 | uses / implements | Uses / implements |
| 4 | depends_on | Depends on |
| 5 | touches_code | Touches code |
BFS traverses the graph bidirectionally (outgoing + incoming edges), sorting neighbors by priority.
Default parameters:
depth= 2 β graph traversal depthmax_nodes= 20 β node limit per bundlemax_chunks= 10 β text chunk limit per bundle
Rules Engine β
Architecture rules are defined in .beadloom/_graph/rules.yml (schema version 3) and enforce boundaries between graph nodes. The YAML key on each rule selects its type.
Rule types (7, parsed and evaluated by the graph/rules/ package, orchestrated by graph/linter.py):
| YAML key | Semantics | Example |
|---|---|---|
deny | Forbid depends_on/import relationships between matched nodes | domain:* β service:* β domains must not depend on services |
require | Require edges from matched nodes to targets | Every domain:* must have a part_of edge to the beadloom service |
forbid | Forbid specific edge patterns between tagged node groups | Nodes tagged ui-layer must not have uses edges to native-layer |
layers | Enforce layered architecture direction | Top-down: services β domains β infrastructure |
forbid_cycles | Detect circular dependencies in the graph | No cycles on uses/depends_on edges |
forbid_import | Control file-level import boundaries | Files in src/beadloom/tui/** must not import from src/beadloom/infrastructure/** |
check | Enforce complexity / coverage limits per node | max_symbols: 280 per domain (Beadloom's domain-size-limit; see the recalibration note below); module-coverage (every src module tracked) |
Internally each parsed rule carries a
rule_typestring (deny/require/forbid/layer/forbid_import/cardinality/ β¦) used by the evaluators; the authoring key inrules.ymlis the column above.
Evaluation:
denyrules are checked against thecode_importstable: resolved import ref_ids are matched against rule patternsrequirerules are checked against theedgestable: nodes matching thefor/frompattern must have the specified edge kind to the target- Node matchers support an optional
excludefield (list of ref_ids) to exempt specific nodes from rule matching unless_edgeexemptions allow otherwise-forbidden imports when a specific edge kind exists between the nodesforbidrules check edge patterns between nodes matching tag selectorslayersrules verify dependency direction across ordered architectural layersforbid_cyclesuses an iterative WHITE/GREY/BLACK colored DFS (ingraph/rules/cycles.py) to find circular dependency paths, reporting each unique cycle onceforbid_importrules query thecode_importstable for forbidden cross-boundary importscheckrules count symbols/files per node (cardinality) and verify module coverage;module-coverageisseverity: error
Output formats:
- Rich β human-readable with Unicode indicators (β, β, β², βΌ)
- JSON β structured violations array + summary
- Porcelain β machine-readable, one TAB-separated line per violation
CLI: beadloom lint [--strict] [--format rich|json|porcelain] [--no-reindex]
The --strict flag exits with code 1 on error-severity violations (for CI/CD). Rules support error and warn severity levels.
domain-size-limitrecalibrated 200 β 280 (BDL-059 S3). Cohesion-driven decomposition split the monster files (rule_engine.py,federation.py) into cohesive same-domain packages, but an in-domain split does not change a domain's symbol count βgraphandapplicationare legitimately large bounded contexts. Dodging the count by reclassifying nodes would be metric-gaming; recalibrating the threshold for this codebase's largest contexts is the honest, documented alternative. The limit stays awarnsignal for genuine re-scoping (a domain over 280), never a target.
Node Tags β
Nodes can be assigned tags for use in rule matching:
# In services.yml
nodes:
- ref_id: my-feature
kind: feature
tags: [ui-layer, presentation]Tags are arbitrary strings. Rules reference them via { tag: <tag-name> } selectors in forbid_edge and layer rules.
Cache Architecture β
Context bundles use a two-tier cache to achieve <20ms response times:
L1 β In-memory (ContextCache):
- Key:
(ref_id, depth, max_nodes, max_chunks) - Invalidation: mtime comparison against
.beadloom/_graph/and docs directories - Cleared on reindex
L2 β SQLite (SqliteCache):
- Table:
bundle_cache - Key:
"<ref_id>:<depth>:<max_nodes>:<max_chunks>" - Survives MCP server restarts
- Invalidation: mtime-based, same as L1
ETag validation:
- Format:
"sha256:<first-16-hex-chars>"of sorted bundle JSON - Returned on cache hit with
cached: trueandunchanged_sincetimestamp - Clients skip re-fetching if ETag is unchanged
Incremental Reindex β
The file_index table tracks SHA-256 hashes of all indexed files (graph YAML, docs, source code).
Process:
- Scan relevant files across graph, docs, and source directories
- Compute SHA-256 hash per file
- Compare with stored
file_index.hash - Only re-parse files with changed hashes; skip unchanged
- Update
file_indexwith new hash and timestamp - Return
ReindexResultwithnothing_changedflag
When nothing changed, the CLI displays current DB counts instead of "0 indexed".
Health Snapshots β
Each reindex captures a health snapshot:
| Metric | Description |
|---|---|
nodes_count | Total graph nodes |
edges_count | Total graph edges |
docs_count | Total indexed documents |
coverage_pct | % of nodes with linked docs |
stale_count | Stale sync_state records |
isolated_count | Nodes with zero edges |
Trends: compared against the previous snapshot, displayed as β² +8%, βΌ +2, etc. Arrows are inverted for "bad increase" metrics (stale, isolated). Snapshots persist across reindexes.
Architecture Snapshots β
beadloom snapshot manages point-in-time captures of the architecture graph for historical comparison.
Commands:
beadloom snapshot save [--name NAME]β save current graph statebeadloom snapshot listβ list saved snapshotsbeadloom snapshot compare [SNAP_ID]β compare current graph with a snapshot
Snapshots are stored in SQLite and enable architecture drift detection across releases.
Agent Prime β
beadloom prime outputs a compact project context (target: β€2000 tokens) for AI agent session initialization.
Sections:
- Project metadata (name, version)
- Architecture summary (node counts by kind, symbol count)
- Health metrics (stale docs, lint violations, last reindex)
- Architecture rules (from rules.yml)
- Domain list (all domain nodes with summaries)
- Stale docs (doc/code path pairs)
- Lint violations (evaluated without reindex)
- Key CLI commands (reference table)
- Agent instructions (workflow guidance)
Output formats: Markdown (default), JSON.
Graceful degradation: works without DB (static-only mode with warning).
CI Gate β
application/gate.py powers beadloom ci β the unified gate that composes the existing checkers into one verdict with a single exit code: reindex β lint --strict β sync-check β config-check β doctor β (optional) federate landscape gate. Every step's honest result is printed (PASS / FAIL / SKIP) β never a green that silently skipped a step. --format rich|json|github applies uniformly; --hub <export> arms the cross-service landscape gate. The same gate runs as the pre-push Beadloom Gate hook (install-hooks --pre-push) and in CI (.github/workflows/ci.yml).
Agentic Flow Configurator β
The onboarding domain composes the packaged multi-agent dev flow from a declaration in .beadloom/flow.yml:
.beadloom/flow.yml ββloadβββΆ flow_config.py (FlowConfig: tools, architecture, stack, quality)
β
βΌ
role_composer.py compose_role(role, architecture=, stack=)
β = CORE protocol + ONE architecture overlay (ddd|fsd)
β + sorted stack overlays (byte-deterministic)
βΌ
role_adapters.py generate_adapters(config, project_root)
β
βββββββββββββ΄ββββββββββββ
βΌ βΌ
.claude/agents/* (Claude Code) .cursor/agents/* (Cursor)flow_config.pyβFlowConfig(frozen) +resolve_flow_config(flag βflow.ymlβ default) +detect_stack; strict validation. Supported: toolsclaude/cursor; architectureddd/fsd(exactly one); stackpython/fastapi/javascript/typescript/vuejs.role_composer.pyβcompose_role(role, *, architecture, stack)= CORE + one architecture overlay + sorted stack overlays; FSD at parity with DDD. Overlay templates live underonboarding/templates/roles/{core,architecture/<arch>,stack/<stack>}/.role_adapters.pyβgenerate_adapters(config, project_root)writes the per-tool adapter set(s).beadloom setup-agentic-flow --tool/--architecture/--stackis the CLI entrypoint;config-check/--fixbyte-guards each composed adapter againstcompose_role(...)and recomposes drift.
The AI tech-writer harness (ai_agents/ai_techwriter/) is the runtime half of the flow: a PR-triggered, symbol-scoped, bounded-parallel doc-refresh harness β see the ai_agents domain README + the ai-techwriter feature SPEC.
Invariants β
- All
ref_idvalues are unique within the graph - Edges reference only existing nodes (FK with ON DELETE CASCADE)
- A document is linked to at most one node via
ref_id - On full reindex all tables except
health_snapshotsare recreated (drop + create) - WAL mode is enabled on every connection open
- Foreign keys are enabled per-connection
Constraints β
- Code indexer supports
.py,.js,.jsx,.ts,.tsx,.go,.rs(tree-sitter) - Import analysis supports 9 languages: Python, TypeScript, JavaScript, Go, Rust, Kotlin, Java, Swift, Objective-C, C/C++ (16 file extensions total)
- Documentation root is configurable via
docs_dirin.beadloom/config.yml(default:docs/) - Source scan paths are configurable via
scan_pathsin.beadloom/config.yml(default:src,lib,app) - Graph is read only from
.beadloom/_graph/*.yml - Rules are read from
.beadloom/_graph/rules.yml - Rules support 7 authoring keys:
deny,require,forbid,layers,forbid_cycles,forbid_import,check ai_agentsis a leaf consumer β never imported by core domains/services (forbid_importenforced)- Maximum chunk size: 2000 characters
- Levenshtein suggestions: maximum 5, distance threshold = max(len/2, 3)
Configuration β
.beadloom/config.yml:
| Key | Default | Description |
|---|---|---|
languages | all supported | File extensions to parse (e.g. [".py", ".ts"]) |
scan_paths | ["src", "lib", "app"] | Source directories to scan |
docs_dir | docs/ | Documentation root directory |
sync.hook_mode | warn | Pre-commit hook mode: warn or block |