How to Coordinate the Egonex Multi-Agent Pipeline: Project-Scanner, File-Analyzer, and Architecture-Analyzer

The Egonex multi-agent pipeline coordinates three specialized agents through deterministic JSON contracts, where the project-scanner creates an inventory, the file-analyzer processes batched graph fragments, and the architecture-analyzer synthesizes layered architecture by reading and writing specific files in the .understand-anything directory.

The Egonex "Understand-Anything" plugin orchestrates a deterministic three-stage pipeline that transforms raw codebases into layered architecture graphs. To coordinate the project-scanner, file-analyzer, and architecture-analyzer agents effectively, you must understand their strict file-based contracts and the exact JSON schemas they exchange through the repository's .understand-anything directory. This guide walks through the precise data flow, authoritative source file paths, and deterministic scripts that bind these agents together.

Stage 1: Project-Scanner – Creating the Codebase Inventory

The project-scanner agent initiates the pipeline by performing a deterministic discovery of the codebase structure, followed by an LLM-driven narrative extraction phase. This agent writes the canonical scan-result.json file that downstream agents consume.

Discovery Phase: File Enumeration and Import Mapping

The scanner executes two bundled Node.js scripts in sequence to ensure reproducible results. First, it runs scan-project.mjs to enumerate every file, assign deterministic language and fileCategory values, count lines, and compute estimatedComplexity metrics. According to the implementation in agents/project-scanner.md, this script writes its output to ua-scan-files.json in the temporary directory.

Immediately following, the scanner invokes extract-import-map.mjs to parse all supported languages using tree-sitter and produce a deterministic import map. This script reads the previous output and writes ua-import-map-output.json containing the resolved dependency graph.

Final Assembly and the Scan-Result Contract

After the deterministic scripts complete, the agent merges the LLM-generated narrative fields (extracted from README, package.json, pyproject.toml, etc.) with the script outputs to build the final inventory. The resulting JSON is written to $PROJECT_ROOT/.understand-anything/intermediate/scan-result.json.

This file must contain:

  • files: An array of file objects with path, language, fileCategory, sizeLines, and estimatedComplexity
  • importMap: A mapping of file paths to their import targets
  • name, rawDescription, readmeHead, frameworks, and languages: LLM-extracted metadata

# Execute the deterministic scanning scripts

node understand-anything-plugin/skills/understand/scan-project.mjs \
  "$PROJECT_ROOT" \
  "$PROJECT_ROOT/.understand-anything/tmp/ua-scan-files.json"

node understand-anything-plugin/skills/understand/extract-import-map.mjs \
  "$PROJECT_ROOT/.understand-anything/tmp/ua-scan-files.json" \
  "$PROJECT_ROOT/.understand-anything/tmp/ua-import-map-output.json"

Stage 2: File-Analyzer – Processing Batched Graph Fragments

The file-analyzer agent consumes scan-result.json and processes the files array in batches to generate graph fragments containing nodes and edges. This agent must ensure every file from the scanner appears in exactly one batch and every import listed in the importMap is emitted as an edge.

Structural Extraction with extract-structure.mjs

For each batch, the analyzer writes a temporary input JSON (ua-file-analyzer-input-<batchIndex>.json) containing the path, language, sizeLines, and fileCategory for files in that batch, along with the relevant slice of the importMap. It then executes the bundled extract-structure.mjs script as specified in agents/file-analyzer.md.

This script performs static analysis to extract functions, classes, and structural metrics, writing results to ua-file-extract-results-<batchIndex>.json.

Semantic Analysis and Mandatory Edge Emission

Following structural extraction, the agent creates Graph Node objects for each file, function, class, and configuration element. Crucially, it must emit an imports edge for every entry in batchImportData[filePath] as defined in the contract. The resulting fragment is validated to ensure no duplicate node IDs exist and all imports are represented.

The final batch fragments are written to .understand-anything/intermediate/batch-<batchIndex>.json (or split into batch-<batchIndex>-part-<k>.json files if the fragment exceeds size limits).

Batch Processing Orchestration

A typical orchestrator chunks the file list into batches of 60 or fewer files to manage memory and processing constraints:

// run-file-analyzer.js
const fs = require('fs');
const path = require('path');
const { execSync } = require('child_process');

const batchIdx = process.argv[2];
const batchFiles = JSON.parse(fs.readFileSync(process.argv[3], 'utf8'));
const importMap = JSON.parse(fs.readFileSync(process.argv[4], 'utf8'));

const tmpDir = path.resolve('.understand-anything/tmp');
const interDir = path.resolve('.understand-anything/intermediate');

// Build input for extract-structure.mjs
const input = {
  projectRoot: process.cwd(),
  batchFiles,
  batchImportData: importMap
};

const inputPath = `${tmpDir}/ua-file-analyzer-input-${batchIdx}.json`;
fs.writeFileSync(inputPath, JSON.stringify(input, null, 2));

// Execute structural extraction
execSync(`node understand-anything-plugin/skills/understand/extract-structure.mjs \
  ${inputPath} \
  ${tmpDir}/ua-file-extract-results-${batchIdx}.json`, { stdio: 'inherit' });

// Convert to graph fragment (simplified)
const results = JSON.parse(fs.readFileSync(`${tmpDir}/ua-file-extract-results-${batchIdx}.json`));
const fragment = {
  nodes: results.results.map(r => ({
    id: `file:${r.path}`,
    type: 'file',
    name: path.basename(r.path),
    filePath: r.path,
    complexity: r.metrics?.functionCount > 5 ? 'complex' : 'simple'
  })),
  edges: [] // Populate with imports from batchImportData
};

fs.writeFileSync(`${interDir}/batch-${batchIdx}.json`, JSON.stringify(fragment, null, 2));

Stage 3: Architecture-Analyzer – Synthesizing Layered Architecture

The architecture-analyzer consumes the merged knowledge graph (produced by stitching all batch fragments together) and runs a deterministic analysis script to compute structural patterns and assign files to architectural layers.

Input Preparation and Deterministic Analysis

The agent prepares ua-arch-input.json containing three top-level arrays: fileNodes, importEdges, and allEdges. It then executes the ua-arch-analyze.js script, which computes:

  • directoryGroups: Files grouped by top-level folder
  • nodeTypeGroups: Categorization of code vs. config vs. documents
  • crossCategoryEdges: Dependencies between different file types
  • interGroupImports and intraGroupDensity: Connectivity metrics
  • patternMatches: Recognized architectural patterns (e.g., routes → api, services → service)

# Prepare merged graph (conceptual merge step)

jq -s 'add' .understand-anything/intermediate/batch-*.json > .understand-anything/tmp/merged-graph.json

# Build architecture input

jq -n \
  --argfile nodes .understand-anything/tmp/merged-graph.json \
  '{fileNodes: $nodes.nodes, importEdges: $nodes.edges, allEdges: $nodes.edges}' \
  > .understand-anything/tmp/ua-arch-input.json

# Run deterministic analysis

node understand-anything-plugin/agents/architecture-analyzer.mjs \
  .understand-anything/tmp/ua-arch-input.json \
  .understand-anything/intermediate/layers.json

Layer Selection and Node Assignment

Based on the analysis results, the agent selects 3-10 layers using specific heuristics:

  • Pattern matches provide strong signals for layer names
  • Intra-group density exceeding 0.3 indicates a distinct layer boundary
  • Dependency direction identifies foundation layers (high inbound edge count)
  • Non-code signals map Dockerfile to infrastructure and CI configs to ci-cd

Every file node from the input must be assigned to exactly one layer's nodeIds array. The agent validates that the total count matches the input fileStats.totalFileNodes before writing the final output.

Final Output Contract

The analyzer writes a JSON array to .understand-anything/intermediate/layers.json, where each layer object contains:

  • id: Unique identifier (e.g., layer:api)
  • name: Human-readable layer name
  • description: Contextual explanation of the layer's purpose
  • nodeIds: Array of file and service identifiers belonging to this layer
[
  {
    "id": "layer:api",
    "name": "API Layer",
    "description": "HTTP route handlers that expose the public REST endpoints.",
    "nodeIds": [
      "file:src/routes/index.ts",
      "file:src/controllers/auth.ts"
    ]
  },
  {
    "id": "layer:infrastructure",
    "name": "Infrastructure",
    "description": "Dockerfile, compose file and CI pipelines that build and deploy the service.",
    "nodeIds": [
      "service:Dockerfile",
      "pipeline:.github/workflows/ci.yml"
    ]
  }
]

End-to-End Data Flow and Contract Boundaries

The pipeline follows a strict linear progression where each agent's output becomes the next agent's input:

  1. Project-Scanner writes scan-result.json containing the complete file inventory and import map
  2. File-Analyzer reads scan-result.json, processes batches, and writes batch-<N>.json fragments
  3. Merge Script (typically merge-batch-graphs.py or equivalent) consolidates fragments into a unified graph
  4. Architecture-Analyzer reads the merged graph, runs ua-arch-analyze.js, and writes layers.json

This separation of concerns ensures determinism: the bundled scripts (scan-project.mjs, extract-import-map.mjs, extract-structure.mjs, and ua-arch-analyze.js) guarantee identical outputs on every execution, which is required for reproducible graph construction. Never re-implement the file walking, language detection, or import resolution logic, as downstream agents rely on the exact JSON payload shapes produced by these canonical scripts.

Summary

  • Project-Scanner builds the deterministic inventory using scan-project.mjs and extract-import-map.mjs, outputting scan-result.json with narrative metadata and file statistics.
  • File-Analyzer processes the inventory in batches via extract-structure.mjs, emitting complete graph fragments with all import edges preserved in batch-<N>.json files.
  • Architecture-Analyzer consumes the merged graph, executes ua-arch-analyze.js to compute structural patterns, and produces layers.json containing 3-10 validated architectural layers.
  • Contract Compliance requires adhering to specific file paths, JSON schemas, and edge emission rules to ensure pipeline reliability.

Frequently Asked Questions

How do the agents communicate if they run in separate processes?

The Egonex multi-agent pipeline uses file-based communication exclusively through the .understand-anything directory. Each agent writes JSON files to specific subdirectories (tmp/ for intermediate files, intermediate/ for stage outputs) that subsequent agents read as input. This design ensures process isolation and provides an audit trail of the pipeline state at every stage.

What happens if a file from the scanner doesn't appear in the analyzer output?

The file-analyzer contract requires that every file listed in scan-result.json must appear in exactly one batch fragment. If a file is missing, the downstream merge-batch-graphs script will detect the discrepancy when validating against fileStats.totalFileNodes, and the architecture-analyzer will fail to assign the node to a layer. Always verify that your batch processing logic handles the complete file list without omission.

Can I modify the layer selection logic in the architecture-analyzer?

While you can adjust the heuristic thresholds (such as the 0.3 intra-group density threshold) in your implementation of the agent logic, you must not modify the deterministic ua-arch-analyze.js script itself. The script computes the structural metrics (directory groups, cross-category edges, pattern matches) that feed into the layer selection algorithm. Modify the selection logic that runs after the script execution, but ensure the final layers.json still assigns every file node to exactly one layer and maintains the required JSON schema.

Why must imports be emitted as edges in every batch?

The file-analyzer must emit an imports edge for every entry in batchImportData[filePath] because the downstream architecture-analyzer relies on the complete dependency graph to calculate crossCategoryEdges and identify foundation layers. Missing import edges would cause the density calculations and layer boundary detection to fail, resulting in an incorrect architectural view that doesn't reflect actual code dependencies.

Have a question about this repo?

These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:

Share the following with your agent to get started:
curl -s "https://instagit.com/install.md"

Works with
Claude Codex Cursor VS Code OpenClaw Any MCP Client

Maintain an open-source project? Get it listed too →