Skip to content

Workspace Code Intelligence Layer for Coding Agents #6295

Description

@github-actions

Auto-created from discussion #6294 by @pavver (Ideas)


Discussion: Workspace Code Intelligence Layer for Coding Agents

Description

I would like to open a discussion about whether LibreFang should grow a workspace-level code intelligence layer for coding agents.

This is not meant as a finished design or a request to implement this exact architecture.

The first question is whether this direction fits LibreFang at all.

If the direction seems useful, I would like to discuss the right architecture, boundaries, and migration path with the maintainers.

Short version:

This should not be "vectorize the codebase".
It should be a workspace/repository code navigation layer for coding agents.
It should combine structural indexing, lexical search, optional vector search, and optional LSP-backed language intelligence.

The goal is to help agents locate and understand relevant code with fewer blind file_list, grep, and file_read loops.

Main Questions

Before discussing implementation details, I think there are two larger questions.

1. Does this direction fit LibreFang?

Should LibreFang try to improve its coding-agent experience with a workspace/repository code intelligence layer?

In other words, should code navigation become a first-class workspace capability, rather than something every agent rediscovers through repeated filesystem searches and file reads?

2. If yes, what should the architecture look like?

The architecture below is only a starting point for discussion.

I am not attached to the exact shape.

The main idea is to start a design conversation and let a better solution emerge from review and discussion.

Some concrete architecture questions:

  • should this live in librefang-memory, librefang-runtime, or a new crate?
  • should workspace/repository state own the index, with agents only receiving scoped access?
  • should the first version focus on FTS/repo maps/tree-sitter before LSP and vectors?
  • should LSP be managed by LibreFang, or should LibreFang integrate with existing editor/LSP state where possible?
  • should code-intelligence tools be ordinary agent tools, context-engine helpers, or both?
  • how much context, if any, should be injected automatically?

The rest of this issue describes one possible shape, mainly to make the discussion concrete.

Core Idea

Coding agents need different kinds of code retrieval.

Different questions require different backends:

Where is this symbol defined?
  -> prefer LSP
  -> fallback to symbol index / tree-sitter / text search

Where is this symbol used?
  -> prefer LSP references
  -> fallback to reference index / lexical search

What files are relevant to this feature?
  -> hybrid lexical search + repo map + optional vector search

What does this repository look like?
  -> repo map from structural symbols and dependency ranking

What code is conceptually similar to this request?
  -> optional vector search

So the system should not have one universal "code search" mechanism.

It should route each query to the best available backend.

Why

Today, a coding agent usually has to explore a repository through repeated filesystem tools:

  • list files;
  • search text;
  • read files;
  • infer structure from partial context;
  • repeat when it missed something.

That works, but it is inefficient for large or unfamiliar codebases:

  • the agent spends tool calls rediscovering project structure;
  • it burns context on file contents that may not matter;
  • it may miss relevant files when relationships are not obvious from filenames;
  • multi-file changes require better localization;
  • multi-repository workspaces are difficult to navigate;
  • exact symbol/reference questions are poorly served by vector search alone.

A code intelligence layer would give agents a structured and traceable way to ask for code context.

What This Is Not

This is not primarily a vector database feature.

Vector search can be useful for semantic discovery, but it should not be the center of the design.

Vector-only code retrieval is weak for exact programming questions such as:

  • where is this function defined?
  • where is this type used?
  • what calls this method?
  • which implementation of this trait is active?
  • which route handler owns this endpoint?

Those questions are better served by LSP, symbol indexes, reference indexes, lexical search, and structural graphs.

The best design is hybrid:

  • deterministic text search for exact strings;
  • structural symbols for repository maps and navigation;
  • LSP for precise language-aware facts when available;
  • optional vectors for fuzzy semantic discovery;
  • git/file tracking for freshness.

Possible Architecture

Conceptually:

WorkspaceCodeIntelligence
  RepositoryRegistry
  RepositoryIndex[]
    SourceInventory
    TextSearchIndex
    SymbolIndex
    RepoMap
    ReferenceIndex optional
    VectorIndex optional
    LspState optional
    BackendHealth
  QueryRouter
  IncrementalUpdater

Backends:

TextBackend
  -> SQLite FTS5 / BM25

StructuralBackend
  -> tree-sitter

LanguageBackend
  -> LSP, when configured and healthy

VectorBackend
  -> optional embeddings for semantic queries

The query router could select the best backend for the task instead of treating every query as a vector query.

Possible Ownership Model

My current assumption is that the code intelligence layer should be owned by the workspace and its repositories.

Agents should not each build their own copy of the same repository index.

Conceptually:

workspace
  -> repositories[]
       -> code intelligence state

agent
  -> access policy
  -> search/tool preferences

This would avoid duplicated indexing and prevent different agents from having inconsistent views of the same codebase.

Multi-Repository Workspaces

A workspace may contain several repositories.

Example:

workspace/
  backend/          .git
  web-frontend/     .git
  android-app/      .git
  protocol-lib/     .git

These repositories may have different:

  • git branches;
  • commit heads;
  • build systems;
  • languages;
  • LSP servers;
  • ignore rules;
  • dependency graphs;
  • generated/vendor directories.

So the real indexing unit may need to be a repository root, while the workspace acts as the container.

Conceptually:

Workspace
  Repository: backend
    CodeIntelligenceState
  Repository: web-frontend
    CodeIntelligenceState
  Repository: android-app
    CodeIntelligenceState
  Repository: protocol-lib
    CodeIntelligenceState

Search results should always include the repository identity:

repo: protocol-lib
path: src/auth/token.rs
symbol: AuthToken
lines: 12..48
source: rust-analyzer
confidence: exact

This matters because the same file names, modules, or class names can exist in multiple repositories.

Per-Agent Access

If the index is shared at workspace/repository level, access should probably be configurable per agent.

Examples:

backend agent:
  repositories: backend, protocol-lib

android agent:
  repositories: android-app, protocol-lib

web agent:
  repositories: web-frontend, protocol-lib

architect:
  repositories: all

project manager:
  repositories: all
  mode: summary/map only

Possible per-agent settings:

  • enabled/disabled;
  • allowed repositories;
  • allowed paths;
  • allowed tools;
  • max search results;
  • whether a small repo map may be auto-injected;
  • whether vector search is available;
  • whether only summaries/maps are allowed.

Example:

[tools.code_intelligence]
enabled = true
repositories = ["backend", "protocol-lib"]
allowed_tools = ["code_search", "symbol_search", "code_map"]
max_results = 12
auto_repo_map = true

For a project manager:

[tools.code_intelligence]
enabled = true
repositories = ["backend", "web-frontend", "android-app", "protocol-lib"]
allowed_tools = ["code_map"]
mode = "summary_only"

Query Router

I think the query router may be the important design boundary.

It could choose the best backend for the query type:

code_search(query)
  -> FTS/BM25 baseline
  -> optional vector merge/rerank
  -> optional repo-map ranking

symbol_search(name)
  -> symbol index
  -> LSP workspace symbols, if available
  -> lexical fallback

find_definition(symbol)
  -> prefer LSP
  -> fallback symbol index

find_references(symbol)
  -> prefer LSP references
  -> fallback reference index / lexical search

code_map(repo/path)
  -> structural symbols + dependency ranking

impact_analysis(path_or_symbol)
  -> git diff + references + imports/dependencies

This keeps the system honest about which retrieval methods are exact and which are approximate.

LSP Role

LSP could be the preferred backend for precise language intelligence when it is available and healthy.

It can provide things that vector search and syntax-only parsing cannot reliably provide:

  • go to definition;
  • find references;
  • document symbols;
  • workspace symbols;
  • diagnostics;
  • type-aware navigation;
  • call hierarchy, where supported;
  • module/crate/package resolution.

Examples:

  • Rust: rust-analyzer;
  • TypeScript: typescript-language-server;
  • Python: pyright / basedpyright;
  • Kotlin/Android: Kotlin or Android-aware tooling where available.

However, I do not think LSP should be the only foundation.

LSP has real operational cost:

  • slow startup;
  • indexing delays;
  • build-system dependencies;
  • language-specific setup;
  • varied server quality;
  • failures and degraded states;
  • heavier resource usage.

Therefore LSP may work best as an optional exact backend with health and fallback behavior.

Suggested LSP health states:

unavailable
starting
indexing
ready
degraded
failed

Tool results should expose provenance:

source: rust-analyzer
confidence: exact
fresh: true
fallback_used: false

or:

source: tree-sitter + FTS
confidence: approximate
fresh: true
fallback_used: true
fallback_reason: rust-analyzer unavailable

Tree-Sitter Role

Tree-sitter could be the robust structural baseline.

It is useful for:

  • parsing many languages;
  • extracting classes/functions/types;
  • building repo maps;
  • finding approximate references;
  • creating symbol-based chunks;
  • supporting languages where no good LSP is available.

It is not a full replacement for LSP because it usually does not understand types, build systems, crate graphs, trait resolution, imports at the same level, or generated code.

It may be a better MVP foundation than LSP because it is more predictable and easier to run inside a runtime service.

Text Search Role

Text search seems like the baseline that should always be available.

SQLite FTS5/BM25 would likely be enough for a first version.

It is deterministic, cheap, and very useful for code:

  • function names;
  • error strings;
  • config keys;
  • route paths;
  • environment variables;
  • type names;
  • file names;
  • log messages.

Even after adding LSP and vectors, text search should remain a core backend.

Vector Search Role

Vector search should probably be optional.

It is useful for fuzzy questions like:

  • where is token refresh logic handled?
  • what code looks related to offline sync?
  • where is a similar validation flow?
  • which files implement this kind of behavior?

It is not reliable enough for exact code intelligence questions by itself.

Embeddings should be stored per chunk hash, not per whole repository.

If one function changes in a large file, only the affected chunk should need a new embedding.

Repository Map

A small repo map can be more useful than large retrieved snippets.

It should contain important files, symbols, signatures, and relationships, with a token budget.

Possible ranking signals:

  • symbol reference count;
  • import/dependency graph centrality;
  • files mentioned in the current task;
  • recently edited files;
  • failed test/compiler output paths;
  • current git diff;
  • agent role and allowed repositories.

The runtime could optionally inject a small repo map for agents that opt in.

Detailed code should still be retrieved through tools.

Incremental Updates

The system should be incremental if it supports large workspaces.

Large workspaces can contain millions of lines of code.

Rebuilding the whole index after every commit or branch switch would be too expensive.

The database should track file/chunk/index state, for example:

workspace_id
repository_id
repo_path
repo_relative_path
branch
git_head_sha
file_hash
mtime
size
language
backend_used
indexed_at

For chunks/symbols:

repository_id
path
start_line
end_line
symbol_name
symbol_kind
content_hash
embedding_hash
embedding_model

When a repository changes from one git head to another, the updater can use:

git diff --name-status <old_head> <new_head>

Then:

  • deleted files are removed from the index;
  • renamed files update their path;
  • modified files are re-indexed;
  • unchanged files are left alone;
  • if git state is unavailable, fall back to scanning and comparing file hashes.

The updater should be background/lazy where possible, not blocking the agent loop on large repositories.

Git and Filesystem Events

The system could notice:

  • file writes from LibreFang tools;
  • patch application;
  • external file changes;
  • git commit changes;
  • branch switches;
  • repository checkout changes;
  • manual refresh requests;
  • manual rebuild requests.

Possible update strategy:

file_write/apply_patch:
  mark affected files dirty or re-index them immediately

git head changed:
  compute changed paths and update only those paths

branch changed:
  update repository git state and diff from previous indexed head if possible

manual code_intelligence_refresh:
  refresh dirty files and verify hashes

manual code_intelligence_rebuild:
  rebuild the repository index from scratch

A full rebuild should be reserved for:

  • first index creation;
  • index schema version change;
  • embedding model change;
  • corrupted/missing index state;
  • explicit user request.

Tools Exposed to Agents

I do not think the model should automatically receive a large pile of retrieved code on every turn.

Instead, coding agents should get tools that retrieve the specific context they need.

Possible tools:

code_search(query, repo?, path?, language?, limit?)
symbol_search(name, repo?, kind?, limit?)
code_map(repo?, path?, depth?)
find_definition(symbol, repo?)
find_references(symbol, repo?)
find_callers(symbol, repo?)
impact_analysis(path_or_symbol, repo?)
code_intelligence_status(repo?)
code_intelligence_refresh(repo?)

This keeps context usage controlled and makes retrieval decisions visible in traces.

Suggested Configuration Shape

Workspace-level configuration:

[workspace.code_intelligence]
enabled = true
auto_discover_repositories = true
auto_update = true
symbol_backend = "auto"
search = ["fts"]

[[workspace.code_intelligence.repositories]]
name = "backend"
path = "backend"

[[workspace.code_intelligence.repositories]]
name = "web"
path = "web-frontend"

[[workspace.code_intelligence.repositories]]
name = "android"
path = "android-app"

[[workspace.code_intelligence.repositories]]
name = "protocol"
path = "protocol-lib"

[workspace.code_intelligence.languages.rust]
backend = "auto"
lsp = "rust-analyzer"

[workspace.code_intelligence.languages.kotlin]
backend = "auto"

[workspace.code_intelligence.fallback]
backend = "tree_sitter"

Agent-level access:

[tools.code_intelligence]
enabled = true
repositories = ["backend", "protocol"]
allowed_tools = ["code_search", "symbol_search", "code_map"]
max_results = 12
auto_repo_map = true

Vector search could be enabled separately:

[workspace.code_intelligence.vector]
enabled = false
embedding_model = "..."

Runtime Safety and Validation

The code intelligence layer should respect existing workspace safety boundaries.

Important constraints:

  • do not index files outside allowed workspace roots;
  • respect ignore files and configured excludes;
  • exclude generated/vendor/build directories by default;
  • avoid indexing secrets where possible;
  • avoid sending large retrieved snippets automatically;
  • make all retrieval visible in traces;
  • let agents access only repositories/paths allowed by configuration;
  • distinguish exact results from approximate results.

Suggested default excludes:

.git/
target/
node_modules/
build/
dist/
.gradle/
.idea/
.vscode/
vendor/

Debugging and Tracing

The system should emit trace information such as:

repo backend: detected git head change abc -> def
repo backend: 14 files modified, 2 deleted, 1 renamed
src/foo.rs: reused 18 chunks, re-embedded 1 chunk
code_search("auth token") returned 8 results from FTS + vector rerank
symbol_search("AuthToken") resolved via rust-analyzer
rust-analyzer unavailable, falling back to tree-sitter for backend
find_references("AuthToken") used approximate lexical fallback

This is important because retrieval quality becomes part of agent quality.

When an agent misses relevant code, we need to know whether the issue came from:

  • stale index;
  • bad search query;
  • missing backend;
  • LSP failure;
  • embedding mismatch;
  • access restrictions;
  • overly aggressive result limits;
  • approximate fallback being mistaken for exact language intelligence.

Possible Migration Path

If maintainers think this direction is worth pursuing, I think it should be built incrementally.

Phase 1: Workspace/Repository Registry

  • Detect repositories inside a workspace.
  • Store repository identity and git state.
  • Add config for workspace-level code intelligence.
  • Add per-agent access controls.

Phase 2: Lexical Search and Repo Map

  • Build file/chunk tables.
  • Add SQLite FTS5/BM25 search.
  • Add code_search.
  • Add code_map.
  • Update changed files incrementally by hash/git diff.

This phase is useful even without embeddings or LSP.

Phase 3: Tree-Sitter Symbol Index

  • Add a structural backend.
  • Extract symbols and signatures.
  • Create symbol-based chunks.
  • Add symbol_search.
  • Improve repo-map ranking with references/imports where available.

This may be a good first structural step because it is predictable and works without running language servers.

Phase 4: LSP Backend

  • Add optional LSP process management.
  • Add health/readiness state.
  • Add fallback behavior.
  • Use LSP for exact definitions/references/diagnostics where available.
  • Cache results with freshness metadata.

Phase 5: Optional Vector Search

  • Chunk by symbols/functions where possible.
  • Store embeddings per chunk hash.
  • Reuse the existing embedding infrastructure if possible.
  • Add hybrid ranking across FTS, symbols, repo-map signals, and vector results.

Phase 6: Graph Queries

  • Add call/import/reference graph where supported.
  • Add find_callers, find_references, and impact_analysis.

Questions for Maintainers

  • Does this direction fit LibreFang's goals as an agent operating system?
  • Should coding-agent code navigation become a first-class workspace capability?
  • Is the workspace/repository ownership model the right boundary, or should this live somewhere else?
  • Does the query-router idea make sense, or would a simpler tool/search layer fit the architecture better?
  • Is the proposed MVP too large, too small, or aimed at the wrong first piece?
  • Where should workspace-level code intelligence configuration live?
  • Should repository discovery be automatic by default, explicit by default, or both?
  • Should LSP processes be managed by LibreFang, or should LibreFang connect to editor/LSP state when available?
  • Which crate should own persistent index storage: librefang-memory, librefang-runtime, or a new crate?
  • Should vector search use the existing memory embedding driver?
  • Should code-intelligence tools be regular tools, context-engine helpers, or both?
  • How should retrieved code snippets be budgeted against the agent context window?
  • How should generated files and vendored dependencies be excluded by default?
  • How should exact and approximate results be represented in tool output?

Possible First Useful Version

If this direction is accepted, I think the first version should be deliberately smaller than the full vision.

A practical MVP:

  • workspace/repository registry;
  • per-agent enable/disable and repository allowlist;
  • SQLite FTS5/BM25 over source chunks;
  • simple repo map;
  • tree-sitter symbol extraction if feasible in the same phase, otherwise next phase;
  • incremental update by file hash and git diff;
  • no automatic large context injection;
  • no vectors yet;
  • no LSP yet.

Then add LSP and vectors later.

The goal would be to keep the first implementation small while establishing the right architecture:

Workspace owns code intelligence.
Repositories own index state.
Agents receive scoped tool access.
Query routing chooses the right backend.
Exact and approximate retrieval are clearly distinguished.
Updates are incremental.


Source: #6294

Metadata

Metadata

Assignees

No one assigned

    Labels

    auto-close-candidateBot thinks this is resolved; awaiting confirmationenhancementNew feature or request

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions