Raw capture โ raw/metis.md
url: https://github.com/arm/metis title: Metis โ AI-Powered Security Code Review author: Arm Product Security Team (open-source-office@arm.com) date_fetched: 2026-07-08 date_published: 2025
Metis is an open-source, agentic AI security framework for deep security code review, created by Arm's Product Security Team. It helps engineers detect subtle vulnerabilities in large, complex, or legacy codebases.
Metis is a Python 3.12+ application structured as a CLI-driven pipeline with a plugin architecture. The topology:
CLI (entry.py) โ MetisEngine (core.py) โ ReviewService + TriageService
โ
Provider Layer (LLM backends) + Plugin Layer (language support)
โ
LangGraph Graphs (review, triage, ask) + Tree-sitter Reachability (C/C++)
The key files and their roles:
Entry Point & CLI (src/metis/cli/):
- entry.py (456 lines): argparse-based CLI, interactive REPL and non-interactive modes, builds the engine from runtime config
- command_registry.py (174 lines): CommandSpec dataclass with invocation modes (path, question, index, args, meta), tool requirements, validation
- commands.py (357 lines): handlers for index, review_code, review_file, review_dir, review_patch, triage, ask, update
Engine Core (src/metis/engine/):
- core.py (298 lines): MetisEngine class โ the god object that wires together provider, reachability service, review service, triage service, tools, and usage tracking
- review_service.py (369 lines): ReviewService โ orchestrates file-by-file review, delegates C/C++ files to the reachability backend and everything else to the LangGraph LLM review graph; uses ThreadPoolExecutor for parallelism
- triage_service.py (54 lines): TriageService โ composition class inheriting from RuntimeMixin and ExecutionMixin; delegates to worker-pool-based parallel triage
LangGraph Graphs (src/metis/engine/graphs/):
- review.py (298 lines): ReviewGraph โ 3-node StateGraph (build_prompt โ review โ parse), chunk-splits files by token count, builds source maps to anchor findings to line numbers
- triage/graph.py (246 lines): TriageGraph โ 2-node StateGraph (collect_evidence โ triage), deterministic adjudication post-model
- triage/adjudication.py: Deterministic rules that gate LLM decisions (contradiction signals โ invalid, unresolved hops โ inconclusive, etc.)
Tree-sitter Reachability (src/metis/engine/reachability/):
- service.py (419 lines): TreeSitterReachabilityService โ the orchestrator for C/C++ analysis; builds call graphs, finds paths from sources to sinks, runs supplementary semantic analysis, confirms vulnerable paths with LLM, deduplicates findings
- graph.py (167 lines): ReachabilityGraph โ in-memory call graph: nodes indexed by unique_name (file::function), resolved calls, source/sink/entrypoint annotations
- c_family.py (299 lines): CFamilyTreeSitterExtractor โ parses C/C++ files with tree-sitter, extracts function definitions, call edges, globals, public declarations
- confirmer.py (391 lines): VulnerabilityConfirmer โ batches reachable paths and sends them to the LLM to verify exploitability; has codebase-wide and per-file modes
- supplementary.py / supplementary_lenses.py: Runs independent semantic analysis passes (lens-based) on the full graph to find issues the path-based approach might miss
Provider Layer (src/metis/providers/):
- base.py: ChatProvider and EmbeddingProvider abstract classes; ChatProvider returns LangChain BaseChatModel, EmbeddingProvider returns LlamaIndex BaseEmbedding
- Concrete providers: openai, azure_openai, anthropic, gemini, bedrock, bedrock_mantle, ollama, llamacpp, vllm, openai_compatible
- registry.py: Plugin-based provider discovery via Setuptools entry points (metis.providers group)
Plugin Layer (src/metis/plugins/):
- base.py: BaseLanguagePlugin ABC (get_name, can_handle, get_splitter, get_prompts, supports_reachability_review) + ConfigBackedLanguagePlugin for YAML-driven plugins
- 19 concrete plugins: c, cpp, python, java, javascript, typescript, go, rust, csharp, kotlin, ruby, php, solidity, terraform, systemverilog, verilog, tb (TableGen), aarch64_assembly
- Each plugin defines supported extensions, tree-sitter code splitting params, and prompt templates (security_review, validation_review, security_review_checks)
Vector Store (src/metis/vector_store/):
- base.py: BaseVectorStore ABC (init, get_retrievers, index_nodes, get_index_handles)
- chroma_store.py: ChromaDB backend (default, local)
- pgvector_store.py: PostgreSQL + pgvector backend (multi-project, scalable)
Memory System (src/metis/memory/):
- Uses LangGraph's BaseStore protocol for repository-level memory
- SQLite-backed with fingerprint-based deduplication for findings
Hybrid deterministic+LLM for C/C++: Tree-sitter builds the call graph deterministically; LLM only looks at paths the graph says are reachable. This is the core insight โ use static analysis to filter what the LLM sees, rather than dumping full files into context.
Two-tier language support: C/C++ gets deep reachability analysis; all other languages get a generic LLM review with language-specific prompts. The split is explicit in the code โ ReviewService.review_file() checks supports_file() and routes accordingly.
LangGraph as the workflow engine: Both review and triage use LangGraph StateGraphs (not agent loops). The graphs are deterministic pipelines: collect evidence, then ask the LLM, then parse. The triage graph has exactly 2 nodes; the review graph has 3.
Plugin discovery via Setuptools entry points: Both providers and language plugins use the metis.providers and metis.plugins entry point groups, allowing third-party extensions without modifying core code.
SARIF as the interchange format: Findings flow in and out as SARIF, making Metis composable with the broader SAST ecosystem. External tools' SARIF can be fed to Metis for triage; Metis output can be consumed by other SARIF tools.
Deterministic adjudication over model trust: The triage system applies deterministic rules after the LLM decision, downgrading valid/invalid to inconclusive when evidence coverage is insufficient. This prevents overconfident status flips.
| File | Lines | Role |
|---|---|---|
engine/core.py |
298 | MetisEngine god object |
engine/review_service.py |
369 | Review orchestration |
engine/reachability/service.py |
419 | Tree-sitter reachability orchestrator |
engine/reachability/confirmer.py |
391 | LLM path vulnerability confirmation |
engine/reachability/c_family.py |
299 | C/C++ tree-sitter extraction |
engine/reachability/graph.py |
167 | In-memory call graph |
engine/graphs/review.py |
298 | LangGraph review pipeline |
engine/graphs/triage/graph.py |
246 | LangGraph triage pipeline |
engine/triage_service_exec.py |
283 | Multi-threaded triage execution |
cli/entry.py |
456 | CLI entry point and REPL loop |
cli/commands.py |
357 | Command implementations |
providers/base.py |
69 | Provider abstraction |
plugins/base.py |
79 | Language plugin abstraction |
Core dependencies from pyproject.toml: - langchain 1.3.4, langgraph 1.2.4 โ LLM workflow orchestration - tree-sitter 0.25.2, tree-sitter-language-pack 1.8.1 โ AST parsing - chromadb 1.5.9 โ default vector store - llama-index 0.14.22 โ vector indexing and retrieval - pydantic 2.13.4 โ schema validation - prompt-toolkit 3.0.52 โ interactive CLI - pathspec 1.1.1 โ .metisignore glob matching - unidiff 0.7.5 โ diff parsing for review_patch
Optional providers: langchain-anthropic, langchain-google-genai, boto3+langchain-aws, llama-index-llms-langchain Optional backend: llama-index-vector-stores-postgres
47 test files covering: core engine, reachability (tree-sitter + triage), graphs, provider integration (anthropic, gemini, bedrock, bedrock_mantle, azure, openai_compatible, llamacpp), memory (registry + backend), CLI, tools, plugins, SARIF output, diff parsing, models.