Lint-AI

August 10, 2026 · View on GitHub

Lint-AI is for teams building agent memory and semantic review over large, fast-changing corpora: assistant sessions, task notes, traces, reports, decisions, code, and documentation that accumulate faster than any team can review manually.

It is a good fit for people who maintain:

  • long-running agent memory over conversations, notes, and decisions
  • markdown knowledge bases with lots of cross-links
  • internal docs where terminology drifts over time
  • codebases where symbols, ownership, usage, and review context matter
  • corpora where “what changed?” and “what is current?” matter as much as keyword search

Use Lint-AI when plain search is not enough and memory recall needs evidence. It treats stored context as a network of facts, concepts, links, symbols, ownership facts, and timestamps, then surfaces misalignment, missing context, and retrieval results suited for downstream review or LLM grounding.

The problem it solves is system-level consistency: reading individual documents is not enough when terminology drifts, definitions conflict, ownership changes, or outdated claims persist across a growing corpus. Lint-AI analyzes corpora collectively, not in isolation, to catch these issues before they spread.

Why people use it:

  • to recover the right past session or note when an agent needs context
  • to catch contradictions before they spread
  • to detect terminology drift across documents
  • to find orphaned or weakly linked pages
  • to ask corpus-level questions over facts, entities, symbols, and time
  • to build review packets from changed files and related semantic context
  • to feed grounded context into an LLM instead of raw text blobs

Benchmark

Evaluated on LongMemEval-S (500 questions), a public benchmark for long-context agent memory retrieval over multi-session conversation corpora. The scoped variant is used: each query searches only the sessions attached to that question, matching the realistic setting where a system knows which sessions are candidates for a given user. No embedding vectors are used anywhere in the pipeline.

The section below shows both the rust-bert POS/NER branch result and the default heuristic release result.

Rust-BERT POS/NER Branch

Aggregate (n=500):

metricvalue
recall@586.9%
recall@1093.8%
recall@2094.4%
recall_any@594.8%
recall_any@1098.2%
MRR87.1%
NDCG@1086.0%
avg query latency5.1 ms

recall@k is fractional recall over all gold sessions. recall_any@k counts 1.0 if any gold session appears in the top k. Latency is measured on a single CPU core with no GPU.

By question type:

question typenrecall@5recall@10recall_any@5MRRNDCG@10
single-session-assistant56100.0%100.0%100.0%99.1%99.3%
single-session-user7097.1%98.6%97.1%86.8%89.8%
knowledge-update7896.8%98.7%100.0%95.2%94.4%
single-session-preference3080.0%96.7%80.0%64.7%72.3%
temporal-reasoning13381.1%92.0%91.7%84.1%82.3%
multi-session13377.7%87.1%94.7%85.3%80.2%

Results file: benchmark/data/lintai_longmemeval_scoped_results_0512.json

Heuristic Release Backend

Aggregate (n=500):

metricvalue
recall@583.7%
recall@1089.6%
recall@2091.1%
recall_any@592.4%
recall_any@1095.6%
recall_any@2097.0%
MRR84.3%
NDCG@1081.9%
avg query latency6.1 ms

recall@k is fractional recall over all gold sessions. recall_any@k counts 1.0 if any gold session appears in the top k. Latency is measured on a single CPU core with no GPU.

By question type:

question typenrecall@5recall@10recall_any@5MRRNDCG@10
single-session-assistant56100.0%100.0%100.0%98.2%98.7%
single-session-user7094.3%98.6%94.3%77.4%82.6%
knowledge-update7894.2%96.8%98.7%94.6%92.4%
single-session-preference3080.0%93.3%80.0%65.8%72.2%
temporal-reasoning13377.0%85.7%86.5%81.8%78.2%
multi-session13372.5%79.3%93.2%82.7%74.3%

Results file: benchmark/data/lintai_longmemeval_scoped_results.json

Quickstart

See docs/quickstart.md for the shortest path to build, run, query, and use the crate from Rust.

Claude Code and Codex integrations

Lint-AI provides provider-specific integrations for Claude Code, Codex, and Gemini CLI. Each integration is opt-in at build time and combines lifecycle hooks with a project-scoped memory layer:

FeatureClaude CodeCodex
Project-scoped MCP serverYesYes
Lifecycle memory retrieval and captureYesYes
Segmented persistent memoryYesYes
search and info MCP toolsYesYes
record_session controlYesYes
Enable/disable Lint-AI at runtimeYesYes
Session status reportingNative status line plus MCPMCP plus terminal status line
Replay with Lint-AI enabled or disabledYesYes
Session import into provider memoryYesYes

Gemini CLI uses the opt-in gemini-cli feature and its documented JSON hooks:

cargo build --release --features gemini-cli
./lint-ai --gemini-cli-install /path/to/project

Gemini records hook events, injects project memory, and exposes the shared MCP tools through SessionStart, BeforeAgent, model, tool, compression, and session-end events. See docs/gemini-cli.md for details.

Antigravity CLI (agy) uses the opt-in agy feature and the same Gemini-compatible MCP and lifecycle-hook protocol:

cargo build --release --features agy
./lint-ai --agy-install /path/to/project

See docs/agy.md for configuration paths and session recording.

Build and install both integrations:

cargo build --release --features claude-code,codex
./lint-ai --claude-code-install /path/to/project
./lint-ai --codex-install /path/to/project

Provider memory remains isolated:

/path/to/project/.lint-ai/claude-memory/
/path/to/project/.lint-ai/codex-memory/

The MCP controls are available inside either client:

mcp__lint-ai__record_session       {"action":"start|stop|status"}
mcp__lint-ai__list_memories        {"limit":20}
mcp__lint-ai__enable_lint_ai       {}
mcp__lint-ai__disable_lint_ai      {}
mcp__lint-ai__lint_ai_status        {}

Enabling Lint-AI enables recording by default. Recording can then be stopped independently, so memory retrieval and full session capture remain separate choices. Recording is local, bounded, redacted, and capture-only; it does not inject memory by itself.

For a baseline/replay A/B comparison:

./lint-ai --replay-session <session-id> \
  --session-provider codex \
  --replay-disable-lint-ai
./lint-ai --replay-session <session-id> \
  --session-provider codex \
  --replay-enable-lint-ai

Each replay receives a new replay-* session ID and a separate archive. The comparison workflow measures task quality, token usage, latency, tool activity, memory retrieval, and recording completeness.

Integration benchmark

These are the latest validated smoke-run measurements for the segmented-routing scenario: one repetition, continuation phase only. They are diagnostic results for the stated run, not universal performance guarantees.

Provider / armContinuationInput tokensCached inputOutput tokensTool callsRecallHook time
Claude native memory22.47 s123,536123,5261,23942/30 ms
Claude Lint-AI only7.05 s15,91415,91237603/31.40 s
Claude both layers7.17 s15,91415,91227103/31.36 s
Codex native memory38.13 s151,00099,8401,50552/30 ms
Codex Lint-AI only18.53 s62,47843,26460422/31.33 s
Codex both layers13.98 s31,68425,08845412/31.39 s

The corresponding reports and run conditions are documented in the Claude Code performance tests and Codex performance tests. Both documents describe the one-run limitation and the provider/model/repository versions used.

MCP-path smoke validation measured 47.09 s for Claude MCP-only and 9.38 s for Codex with Lint-AI MCP. The Codex run recorded 13,246 input tokens, 77 output tokens, 2/3 recall, and 1.35 s hook time; the Claude run recorded 189,022 input tokens, 3,678 output tokens, and one Lint-AI MCP call. A separate direct Claude MCP search check returned results in approximately 220 ms; it did not measure agent search-selection behavior.

More detail: Claude Code integration, Codex integration, shared integration architecture, and the integration test strategy. and session metrics.

How It Works

Lint-AI ingests sessions, notes, traces, documents, and code-oriented artifacts, builds a lexical index plus sparse entity/term tables, and overlays graph structure for links, symbols, ownership, and co-occurrence. Queries are analyzed for intent, entities, and temporal hints, then scored with a blend of lexical, semantic, claim, topic, timestamp, and graph signals. The same corpus analysis also powers misalignment checks such as missing cross-refs, orphan pages, low-confidence claims, and review-oriented packet generation.

If you want the deeper implementation view, see:

  • docs/chunk-strategy.md
  • docs/lexical-data.md
  • docs/artifact-indexing.md

How To Use

Query semantics currently use the heuristic backend in the release build. The model-backed POS/NER path was used in the experimental rust-bert branch and is kept separate from the audited release graph.

CLI

Lint a corpus:

./lint-ai /path/to/repo

If you prefer cargo run:

cargo run --bin lint-ai -- /path/to/repo

Query the corpus:

./lint-ai --query "docker install linux" /path/to/repo/docs

Get LLM-ready retrieval context:

./lint-ai --llm-context "docker install linux" /path/to/repo/docs

Inspect the derived inventory:

./lint-ai /path/to/repo/docs --show-concepts
./lint-ai /path/to/repo/docs --show-headings

Show the main query-oriented modes:

./lint-ai --index /path/to/repo/docs
./lint-ai --show-tier0 /path/to/repo
./lint-ai --show-tier1-entities /path/to/repo
./lint-ai --show-tier1-terms /path/to/repo --tier1-term-ranker yake

Use spaCy for Tier 1 entities if available:

./lint-ai /path/to/repo --show-tier1-entities --tier1-ner-provider spacy --spacy-model en_core_web_sm

For optional Claude Code support, build with --features claude-code and see docs/claude-code.md for the MCP, hooks, and install flow.

Common graph exports, chunking knobs, lexical subset regeneration, and artifact indexing details are documented in docs/.

Rust Library

Use IndexStore when you want mutable ingestion, MemoryIndex when you want an immutable query snapshot, and SourceDocument to add content. For the broader semantic graph, use CorpusGraph, SymbolStore, UsageGraph, and the review model types that are re-exported from the crate root.

use lint_ai::{IndexStore, PipelineOptions, SourceDocument};

fn main() -> anyhow::Result<()> {
    let mut index = IndexStore::in_memory(PipelineOptions::default());

    index.upsert(SourceDocument {
        doc_id: "artifact-1".to_string(),
        source: "artifact://artifact-1".to_string(),
        content: "docker install guide for linux hosts".to_string(),
        concept: "docker install".to_string(),
        group_id: None,
        headings: vec!["Overview".to_string()],
        links: vec![],
        timestamp: None,
        doc_length: 36,
        author_agent: None,
    });

    let results = index.query("docker install", 5)?;
    println!("{}", serde_json::to_string_pretty(&results)?);
    Ok(())
}

For corpus-local persistence under .lint-ai/, use:

use std::path::Path;
use lint_ai::{IndexStore, PipelineOptions};

let index = IndexStore::for_corpus(Path::new("/path/to/corpus"), PipelineOptions::default())?;

If you already have prepared DocRecord values and want the built search structure directly, use lint_ai::index::MemoryIndex.

For symbol and review workflows, the crate root also re-exports:

  • CorpusGraph for querying documents, symbols, and usage together
  • SymbolRecord / SymbolStore for symbol indexing
  • OwnershipRecord / OwnershipSummary for ownership facts and summaries
  • UsageGraph / UsageEdge / UsageNode for symbol and ownership relationship tracing
  • ReviewPacket / ReviewFinding / ReviewDiff for structured review output

Advanced

By default the linter skips files larger than 5MB and stops after 50k files. Override these limits:

./lint-ai /path/to/repo --max-bytes 10000000 --max-files 100000

Limit directory traversal depth:

./lint-ai /path/to/repo --max-depth 10

Limit total bytes read across the corpus:

./lint-ai /path/to/repo --max-total-bytes 100000000

The tool automatically scopes to /path/to/repo/docs/** when that folder exists.

Configuration

Analyze a corpus and emit a suggested lint-ai.json config:

./lint-ai /path/to/repo/docs --analyze

Example output:

Suggested config:
{
  "stopwords": ["group messages", "pairing", "channel routing"],
  "ignore_sections": ["unscoped", "related"],
  "ignore_crossref_sections": ["unscoped", "related"],
  "ignore_paths": [],
  "allowlist_concepts": []
}

Stats:
pages: 31
top concepts:
- group messages (25)
- pairing (25)
- channel routing (22)
- slack (11)
- telegram (11)
top sections:
- configuration (41)
- setup (35)
- unscoped (31)
- security (28)
- related (22)

You can place a lint-ai.json file in the target root, or pass --config /path/to/lint-ai.json, to control filters.

Use --strict-config to fail fast if the config is invalid.

Limit config size:

./lint-ai /path/to/repo --max-config-bytes 2000000

Example used for Openclaw channels:

{
  "stopwords": ["channel", "message", "messages", "bot", "client", "config"],
  "ignore_sections": ["related", "unscoped"],
  "ignore_crossref_sections": ["related", "unscoped"],
  "ignore_paths": [],
  "allowlist_concepts": ["discord", "slack", "telegram", "whatsapp", "signal", "matrix"],
  "scope_prefix": "docs/channels/"
}

Run it with ./lint-ai /path/to/openclaw/docs/channels --config /path/to/openclaw/lint-ai.json.

Contributing

If you want to contribute, the most useful workflow is:

cargo build
cargo test
cargo fmt --all

Please keep changes focused and include tests when behavior changes.

Useful contribution rules:

  • run cargo test before opening a PR
  • run cargo fmt --all for Rust code changes
  • update docs when CLI flags, benchmarks, or query behavior change
  • include benchmark notes when ranking or retrieval logic changes
  • prefer small PRs with a clear scope

If the change affects query quality, mention the benchmark result it moves.

Output Examples

Running the linter emits findings tagged with severity and link-debt signals:

Missing cross-ref in docs/channels/discord.md -> [[signal]] (high)
Low link density in docs/channels/location.md (outgoing 1, avg 4.2)
Unreachable page: docs/channels/legacy.md
Orphan page: docs/channels/unused.md

Use --show-concepts when you need the derived concept inventory for tuning stopwords or allowlists in lint-ai.json.