Overall: TypeScript plugin + Rust worker process over a session-scoped NDJSON bridge. A unified CLI (packages/aft-cli/) serves setup/doctor across all harnesses; shared transport, binary resolution, and ONNX helpers live in packages/aft-bridge/.
Key Characteristics:
- Use
packages/opencode-plugin/src/index.tsandpackages/pi-plugin/src/index.tsto register harness tools and map them onto Rust commands. - Use
packages/aft-bridge/src/bridge.tsandpackages/aft-bridge/src/pool.tsas the shared transport layer, isolating oneaftprocess per project root. - Use
packages/aft-cli/src/index.tsas the unified setup/doctor CLI across all harnesses. - Use
crates/aft/src/commands/handlers to keep protocol dispatch thin and command logic modular. - Use
crates/aft/src/edit.rs,crates/aft/src/format.rs,crates/aft/src/callgraph.rs,crates/aft/src/semantic_index.rs,crates/aft/src/search_index.rs,crates/aft/src/compress/, andcrates/aft/src/lsp/as shared engines behind multiple commands.
OpenCode integration layer:
- Purpose: Register tools, load config, and attach post-execution metadata.
- Location:
packages/opencode-plugin/src/index.ts - Contains: Plugin bootstrap, tool-surface selection, hoisting logic, disabled-tool filtering, session-directory management, RPC server, auto-update checker hook
- Depends on:
packages/opencode-plugin/src/config.ts,packages/opencode-plugin/src/tools/*.ts,packages/aft-bridge/ - Used by: OpenCode plugin loading through
@cortexkit/aft-opencode
Pi integration layer:
- Purpose: Register tools, load config, and manage Pi host notifications.
- Location:
packages/pi-plugin/src/index.ts - Contains: Plugin bootstrap, tool-surface selection, hoisting logic, LSP auto-install (npm/github/project-relevance probes),
aft-statuscommand - Depends on:
packages/pi-plugin/src/config.ts,packages/pi-plugin/src/tools/*.ts,packages/pi-plugin/src/commands/*.ts,packages/aft-bridge/ - Used by: Pi coding agent through
@cortexkit/aft-pi
Shared bridge layer:
- Purpose: Resolve or download the binary, start worker processes, manage ONNX runtime, format output, and forward requests. All harness adapters share this layer.
- Location:
packages/aft-bridge/src/bridge.ts,packages/aft-bridge/src/pool.ts,packages/aft-bridge/src/resolver.ts,packages/aft-bridge/src/downloader.ts,packages/aft-bridge/src/onnx-runtime.ts,packages/aft-bridge/src/migration.ts,packages/aft-bridge/src/zoom-format.ts - Contains: Session bridge lifecycle, restart handling, version checks, binary discovery, binary download, ONNX runtime detection, storage migration, compact UI formatting, active logger
- Depends on: Node child-process APIs, GitHub releases,
onnxruntime-node - Used by:
packages/opencode-plugin/src/index.ts,packages/pi-plugin/src/index.ts
Unified CLI layer:
- Purpose: Provide a single
npx @cortexkit/aftentry point for setup, doctor, and LSP management across all harnesses. - Location:
packages/aft-cli/src/index.ts,packages/aft-cli/src/commands/ - Contains:
setup,doctor,doctor lsp,doctor --fix,doctor --clear,doctor --issue; harness auto-detection (OpenCode/Pi) with--harnessoverride - Depends on:
packages/aft-bridge/, harness adapter config paths - Used by: End users via
npx @cortexkit/aft
Tool definition layer (OpenCode):
- Purpose: Convert OpenCode tool arguments into protocol requests and permission checks.
- Location:
packages/opencode-plugin/src/tools/ - Contains: Hoisted tools (edit/write/apply_patch), reading tools, import tools, structure tools, navigation tools, refactoring tools, safety tools, bash tools, conflict tools, AST tools, LSP tools, search tools, semantic tools, inspect tools, permissions helpers
- Depends on:
packages/aft-bridge/src/pool.ts,packages/opencode-plugin/src/shared/,packages/opencode-plugin/src/metadata-store.ts - Used by:
packages/opencode-plugin/src/index.ts
Tool definition layer (Pi):
- Purpose: Convert Pi tool arguments into protocol requests and permission checks.
- Location:
packages/pi-plugin/src/tools/ - Contains: Hoisted tools (read/write/edit/grep), reading tools, import tools, structure tools, navigation tools, refactoring tools, safety tools, bash tools, conflict tools, AST tools, inspect tools, semantic tools, render helpers, diff-format helper
- Depends on:
packages/aft-bridge/src/pool.ts,packages/pi-plugin/src/shared/ - Used by:
packages/pi-plugin/src/index.ts
Protocol and command layer:
- Purpose: Accept NDJSON requests and route each command to a focused handler.
- Location:
crates/aft/src/main.rs,crates/aft/src/protocol.rs,crates/aft/src/commands/ - Contains: Request dispatch, response encoding, command handlers for read/write/edit/outline/zoom/bash/batch/grep/glob/search/imports/refactor/LSP/inspect/conflicts/checkpoints/state
- Depends on:
crates/aft/src/context.rs,crates/aft/src/parser.rs,crates/aft/src/callgraph.rs,crates/aft/src/edit.rs,crates/aft/src/semantic_index.rs,crates/aft/src/search_index.rs,crates/aft/src/compress/ - Used by:
packages/aft-bridge/src/bridge.ts
Analysis and mutation engine layer:
- Purpose: Parse code, compute call graphs, apply edits, format files, manage imports, index code semantically, and search with trigram indexes.
- Location:
crates/aft/src/parser.rs,crates/aft/src/callgraph.rs,crates/aft/src/edit.rs,crates/aft/src/format.rs,crates/aft/src/imports/,crates/aft/src/extract.rs,crates/aft/src/semantic_index.rs,crates/aft/src/search_index.rs,crates/aft/src/symbols.rs,crates/aft/src/calls.rs,crates/aft/src/symbol_cache_disk.rs,crates/aft/src/fuzzy_match.rs,crates/aft/src/ast_grep_hints.rs,crates/aft/src/ast_grep_lang.rs,crates/aft/src/query_shape.rs,crates/aft/src/pattern_compile.rs - Contains: Tree-sitter parsing, symbol extraction, diff generation, formatter detection, type-checker integration, import engines (Java, C#, PHP, Kotlin, Scala, Swift, Ruby, Lua, C/C++, Perl, Solidity, Vue), refactor helpers, semantic embedding index, trigram search index, disk-backed symbol cache, AST-grep integration
- Depends on: tree-sitter grammars, ast-grep, external formatter and checker processes, ONNX Runtime (optional), fastembed / OpenAI-compatible / Ollama backends (optional)
- Used by:
crates/aft/src/commands/*.rs
State and diagnostics layer:
- Purpose: Hold per-process mutable state for backups, checkpoints, file watching, call graph cache, LSP state, database storage, bash background tasks, cache freshness tracking, and file-system locking.
- Location:
crates/aft/src/context.rs,crates/aft/src/backup.rs,crates/aft/src/checkpoint.rs,crates/aft/src/lsp/,crates/aft/src/db/,crates/aft/src/cache_freshness.rs,crates/aft/src/fs_lock.rs,crates/aft/src/bash_background/ - Contains:
AppContext, undo history, named checkpoints, watcher receiver, LSP manager, diagnostics store, document store, persistent database tables (backups, bash tasks, compression events, state), cache-freshness tracker, file-system lockfile, background task registry, PTY process pool - Depends on:
notify, LSP transport helpers, RustRefCell, SQLite (viadb/),serde - Used by: All command handlers through
AppContext
Tool invocation flow:
- Register tool definitions and config-driven surface selection --
packages/opencode-plugin/src/index.tsorpackages/pi-plugin/src/index.ts - Get a session bridge and send a command over NDJSON --
packages/aft-bridge/src/pool.ts,packages/aft-bridge/src/bridge.ts - Dispatch the request to a Rust handler and return structured JSON --
crates/aft/src/main.rs,crates/aft/src/commands/mod.rs
Edit pipeline:
- Validate permissions and map tool arguments to protocol params --
packages/opencode-plugin/src/tools/hoisted.ts,packages/opencode-plugin/src/tools/permissions.ts(or Pi equivalents) - Snapshot, mutate, diff, and validate content --
crates/aft/src/edit.rs - Auto-format and optionally collect diagnostics after write --
crates/aft/src/format.rs,crates/aft/src/context.rs
Call-graph and navigation flow:
- Configure project root and initialize file watching --
crates/aft/src/commands/configure.rs - Build or query lazy file-level graph data --
crates/aft/src/callgraph.rs - Serve navigation commands such as callers, call-tree, impact, trace-to, and trace-data --
crates/aft/src/commands/call_tree.rs,crates/aft/src/commands/callers.rs,crates/aft/src/commands/impact.rs,crates/aft/src/commands/trace_data.rs,crates/aft/src/commands/trace_to.rs,crates/aft/src/commands/trace_to_symbol.rs
Search and retrieval flow:
- Index project files with trigram-based search index --
crates/aft/src/search_index.rs - Optionally index with dense embeddings (fastembed, OpenAI-compatible, or Ollama) --
crates/aft/src/semantic_index.rs - Serve
grep(trigram, full-text) andaft_search(semantic + hybrid) queries --crates/aft/src/commands/grep.rs,crates/aft/src/commands/semantic_search.rs
Bash execution flow:
- Rewrite high-level commands (cat to read, grep to grep tool) --
crates/aft/src/bash_rewrite/ - Scan for dangerous commands and prompt for permission --
crates/aft/src/bash_permissions/ - Execute foreground, background, or PTY modes --
crates/aft/src/bash_background/ - Compress output through the tiered compressor --
crates/aft/src/compress/
Binary resolution flow:
- Check cache, npm platform package, PATH, and cargo install locations --
packages/aft-bridge/src/resolver.ts - Download and checksum-verify a release asset when local resolution fails --
packages/aft-bridge/src/downloader.ts - Start bridges against the resolved binary and hot-swap after version mismatch --
packages/aft-bridge/src/bridge.ts,packages/aft-bridge/src/pool.ts
BinaryBridge:
- Purpose: Keep one live
aftsubprocess available for request/response traffic. - Location:
packages/aft-bridge/src/bridge.ts - Pattern: Persistent child-process adapter with timeout-triggered restart
BridgePool:
- Purpose: Scope bridges per OpenCode/Pi session and preserve isolated undo history.
- Location:
packages/aft-bridge/src/pool.ts - Pattern: Session-keyed object pool with LRU eviction
Tool groups (OpenCode):
- Purpose: Group related OpenCode tool definitions by capability surface.
- Location:
packages/opencode-plugin/src/tools/hoisted.ts,packages/opencode-plugin/src/tools/reading.ts,packages/opencode-plugin/src/tools/imports.ts,packages/opencode-plugin/src/tools/structure.ts,packages/opencode-plugin/src/tools/navigation.ts,packages/opencode-plugin/src/tools/refactoring.ts,packages/opencode-plugin/src/tools/safety.ts,packages/opencode-plugin/src/tools/conflicts.ts,packages/opencode-plugin/src/tools/lsp.ts,packages/opencode-plugin/src/tools/ast.ts,packages/opencode-plugin/src/tools/bash.ts,packages/opencode-plugin/src/tools/bash_watch.ts,packages/opencode-plugin/src/tools/bash_write.ts,packages/opencode-plugin/src/tools/inspect.ts,packages/opencode-plugin/src/tools/search.ts,packages/opencode-plugin/src/tools/semantic.ts,packages/opencode-plugin/src/tools/permissions.ts,packages/opencode-plugin/src/tools/hoisted-internals.ts - Pattern: Thin TypeScript adapters over shared bridge transport
Tool groups (Pi):
- Purpose: Group related Pi tool definitions by capability surface.
- Location:
packages/pi-plugin/src/tools/hoisted.ts,packages/pi-plugin/src/tools/reading.ts,packages/pi-plugin/src/tools/imports.ts,packages/pi-plugin/src/tools/structure.ts,packages/pi-plugin/src/tools/navigate.ts,packages/pi-plugin/src/tools/refactor.ts,packages/pi-plugin/src/tools/safety.ts,packages/pi-plugin/src/tools/conflicts.ts,packages/pi-plugin/src/tools/ast.ts,packages/pi-plugin/src/tools/bash.ts,packages/pi-plugin/src/tools/semantic.ts,packages/pi-plugin/src/tools/inspect.ts,packages/pi-plugin/src/tools/fs.ts,packages/pi-plugin/src/tools/diff-format.ts,packages/pi-plugin/src/tools/render-helpers.ts - Pattern: Thin TypeScript adapters over shared bridge transport with Pi-specific argument mapping
AppContext:
- Purpose: Centralize runtime state for commands inside the Rust worker.
- Location:
crates/aft/src/context.rs - Pattern: Interior-mutable service container for a single-threaded request loop
- Contains:
CallGraph,SearchIndex,SemanticIndex,BgTaskRegistry,FilterRegistry, database connections, LSP manager, undo history
CallGraph:
- Purpose: Cache per-file call data and answer callers, call-tree, impact, and trace queries.
- Location:
crates/aft/src/callgraph.rs - Pattern: Lazy workspace index with invalidation on watcher events
SearchIndex:
- Purpose: Provide fast trigram-based full-text search across the project.
- Location:
crates/aft/src/search_index.rs - Pattern: On-disk trigram index rebuilt on watcher events, served from a background thread
SemanticIndex:
- Purpose: Provide dense-embedding semantic search across the project.
- Location:
crates/aft/src/semantic_index.rs - Pattern: Optional index backed by fastembed (local), OpenAI-compatible, or Ollama; configurable
max_filescap
BgTaskRegistry:
- Purpose: Manage background bash tasks and PTY sessions.
- Location:
crates/aft/src/bash_background/registry.rs - Pattern: Thread-safe registry with a watchdog thread for output compression, completion notification, and task lifecycle cleanup
Compressor:
- Purpose: Reduce hoisted-bash output to relevant tokens.
- Location:
crates/aft/src/compress/(multiple modules),crates/aft/src/compress/mod.rs - Pattern: Trait-based dispatch with per-command Rust modules, output-shape sniffers, package-manager modules, declarative TOML filters, and a generic fallback
Harness:
- Purpose: Represent the coding-agent harness (OpenCode or Pi) for config and CLI dispatch.
- Location:
crates/aft/src/harness.rs - Pattern: Simple enum with serde round-trip and display/from-str
OpenCode plugin entry point:
- Location:
packages/opencode-plugin/src/index.ts - Triggers: OpenCode loads the
@cortexkit/aft-opencodeplugin - Responsibilities: Load config, resolve the binary via
@cortexkit/aft-bridge, create the bridge pool, register tool definitions, manage session lifecycle, run auto-update checker, handle background completion push frames
Pi plugin entry point:
- Location:
packages/pi-plugin/src/index.ts - Triggers: Pi loads the
@cortexkit/aft-piplugin - Responsibilities: Load config, resolve the binary via
@cortexkit/aft-bridge, create the bridge pool, register tool definitions, manage LSP auto-install (npm + GitHub), handle background completion push frames
Unified CLI entry point:
- Location:
packages/aft-cli/src/index.ts - Triggers:
npx @cortexkit/aftinvocation - Responsibilities: Parse argv, auto-detect harness, dispatch to
setup,doctor, ordoctor lspcommands
Shared bridge entry point:
- Location:
packages/aft-bridge/src/index.ts - Triggers: Imported by
@cortexkit/aft-opencodeand@cortexkit/aft-pi - Responsibilities: Export
BinaryBridge,BridgePool, binary resolution (downloadBinary,ensureBinary,findBinary), ONNX runtime detection (ensureOnnxRuntime,isOrtAutoDownloadSupported), storage migration (ensureStorageMigrated), compact formatting helpers
Rust protocol entry point:
- Location:
crates/aft/src/main.rs - Triggers:
packages/aft-bridge/src/bridge.tsspawns theaftbinary - Responsibilities: Read NDJSON requests from stdin, dispatch handlers, drain watcher and LSP events, compress background task output, and write JSON responses
Rust binary CLI subcommands:
- Location:
crates/aft/src/cli/ - Triggers:
aft warmuporaft migrate-storageinvocations - Responsibilities: Pre-warm tree-sitter grammars, migrate storage between legacy and CortexKit paths
Release automation entry point:
- Location:
.github/workflows/release.yml - Triggers: Git tag pushes matching
v* - Responsibilities: Test the workspace, build platform binaries, publish crates and npm packages, and create a GitHub release
Strategy: Return structured Rust Response::error payloads from command handlers, convert failed responses into plugin-side exceptions, and restart hung or crashed worker processes in packages/aft-bridge/src/bridge.ts.
Goal: an agent reading any AFT response must be able to distinguish three states without ambiguity: (1) the work could not be performed, (2) the work was performed and the result is complete, (3) the work was performed but the result is partial.
Rule (tri-state):
-
success: false+code+message-- the requested work could not be performed. Codes are machine-actionable strings such as"path_not_found","no_lsp_server","project_too_large","invalid_request","ambiguous_match". The agent must read the message before continuing. -
success: true+ completion signaling -- the work was performed. Tools that produce results MUST report whether the result is complete and, if not, name the gaps. Conventional fields:complete: true-- the agent can trust absence of items in the resultcomplete: false+ a named gap field -- partial result. Gap fields includepending_files,unchecked_files,scope_warnings,skipped_files: [{file, reason}],walk_truncatedremoved: bool(mutations) -- did the file actually change?falseis a valid success when the requested change was a no-op.no_files_matched_scope: bool(search tools) -- distinguishes "the path/glob you gave me resolved to zero files" from "I searched N files and found nothing"
-
Side-effect skip codes -- when the main work succeeded but a non-essential side step was skipped (e.g. post-write formatting), use a
<step>_skipped_reasonfield so the agent gets specific feedback without treating the whole call as a failure. Approved values:format_skipped_reason:"unsupported_language"|"no_formatter_configured"|"formatter_not_installed"|"formatter_excluded_path"|"timeout"|"error"validate_skipped_reason:"unsupported_language"|"no_checker_configured"|"checker_not_installed"|"timeout"|"error"
Anti-patterns this convention exists to prevent:
- Returning
success: truewith empty results when the scope (path/glob) didn't resolve to any files -- the agent reads it as "all clear" but really nothing was checked. Returnno_files_matched_scope: true(when the scope was syntactically valid but matched zero files) orsuccess: false, code: "path_not_found"(when a passed path doesn't exist). - Reusing one skip-reason string for two distinct causes (e.g.,
"not_found"for both "language has no formatter configured" and "configured formatter binary missing"). The agent has different remediations for each -- split them. - Silently dropping files that fail to parse / open / decode inside a multi-file or directory operation. Always include a
skipped_files: [{file, reason}]array so the agent knows X out of Y files were actually processed. - Asserting
success: trueafter a partial transaction without acomplete: falseflag and a list of pending work.
Where this is documented in code: crates/aft/src/protocol.rs Response doc comment carries the canonical rule and the approved field set. New tools must follow this convention; existing tools are migrating.
Goal: reduce hoisted-bash output to fewer tokens while keeping the information the agent actually needs (errors, summaries, ref updates) and discarding the noise (progress bars, repeated headers, deep nested directory listings).
Five-tier dispatch in crates/aft/src/compress/mod.rs:
-
Specific Rust
Compressormodules -- hand-written parsers for high-traffic tools identified by tool tokens (e.g.git,cargo,vitest). Always wins when matched. Each module lives in its own file undercrates/aft/src/compress/(e.g.git.rs,cargo.rs,eslint.rs) and implements theCompressortrait (fn tokens(&[&str]) -> bool+fn compress(&str, &str) -> String). Modules includebiome,bun,cargo,eslint,git,go,mypy,next,npm,playwright,pnpm,prettier,pytest,ruff,tsc,vitest. -
Output-shape
Compressorsniffers -- inner-tool parsers that recognize their own private summaries even when invoked through wrappers such asnpm test,make test, or./scripts/check.sh. Tried after specific modules, before package-manager modules. -
Package-manager
Compressormodules -- broad head-token matchers (npm,pnpm,bun) that compress unclaimed package-manager output. -
Declarative TOML filters -- strip + truncate + cap + shortcircuit rules for the long tail of CLI tools, loaded from three sources at startup with project > user > builtin priority by filename:
- Builtin: shipped via
include_str!()fromcrates/aft/src/compress/builtin_filters/*.toml, registered incrates/aft/src/compress/builtin_filters.rs::ALL. Currently 22 filters: ansible-playbook, aws, curl, deno, df, docker, du, find, gh, gradle, helm, kubectl, ls, make, pip, psql, terraform, tree, uv, wc, wget, xcodebuild. - User:
<storage_dir>/filters/*.toml(XDG-aware via the activestorage_dir) - Project:
<project_root>/.aft/filters/*.toml-- gated bycrate::compress::trust; never loaded for an untrusted project
- Builtin: shipped via
-
Generic fallback -- ANSI strip + consecutive-line dedup + middle-truncate. Always applies when no Rust module or TOML filter matches.
Pipeline for TOML filters (in crates/aft/src/compress/toml_filter.rs::apply_filter):
- ANSI strip (when
[ansi].stripis true; default true) [strip]regexes drop matching lines (multiline mode)[shortcircuit]checks remaining content; if matched, returnreplacement[truncate]middle-truncates per line atline_maxchars[cap]enforcesmax_lineswithkeep = "head" | "tail" | "middle"
Trust model (crates/aft/src/compress/trust.rs): project filters can lie about output (e.g. strip real failures and replace with tests: ok). They are off by default. Users opt in via npx @cortexkit/aft doctor filters trust, which records the canonicalized project root in <storage_dir>/trusted-filter-projects.json (atomic temp-file rename, deserialized fail-closed). The CLI also exposes untrust, trust --list, --show <name>, and the default list view.
Concurrency: the filter registry is exposed as Arc<RwLock<FilterRegistry>> so the BgTaskRegistry watchdog thread can compress completed task output without holding AppContext. The compressor is installed as a closure on BgTaskRegistry from crates/aft/src/main.rs after AppContext::new constructs both.
Configure invalidation: crates/aft/src/commands/configure.rs::handle_configure calls ctx.sync_bash_compress_flag() and ctx.reset_filter_registry() on every configure so changes to experimental.bash.compress, storage_dir, project_root, or trust state pick up immediately without restart.
Compression site: terminal-state output only. Live tail of running tasks (via bash_status polling) is shown raw so agents debugging long commands see exactly what the process emitted. Compression fires inside BgTaskRegistry::maybe_compress_snapshot (status / list paths) and enqueue_completion_locked (completion frame + bash_drain_completions cache).
Logging: Write plugin logs through packages/opencode-plugin/src/logger.ts or packages/pi-plugin/src/logger.ts and Rust logs through env_logger in crates/aft/src/main.rs.
Caching: Cache resolved binaries in ~/.cache/aft/bin through packages/aft-bridge/src/downloader.ts, cache session bridges in packages/aft-bridge/src/pool.ts, cache tool availability in crates/aft/src/format.rs, cache call-graph state in crates/aft/src/callgraph.rs, cache trigram search indexes on disk via crates/aft/src/search_index.rs, cache semantic embeddings on disk via crates/aft/src/semantic_index.rs, and cache symbol data on disk via crates/aft/src/symbol_cache_disk.rs.
Storage: Store undo snapshots in crates/aft/src/backup.rs, named checkpoints in crates/aft/src/checkpoint.rs, database tables (backups, bash tasks, compression events, state) in crates/aft/src/db/, pending UI metadata in packages/opencode-plugin/src/metadata-store.ts, and downloaded binaries in the cache directory managed by packages/aft-bridge/src/downloader.ts. Storage lives under the CortexKit shared root (~/.local/share/cortexkit/aft/), migrated from the legacy path via crates/aft/src/migrate_storage.rs.