Skip to content

Rust Library

The Cargo package is brokk-bifrost, and the Rust crate name is brokk_bifrost. It exports the analyzer core, project abstractions, the searchtools service, CodeQuery execution, the stable extension API, and common result types from src/lib.rs.

Every package, feature, export, and service method is listed in the Rust API reference.

Add the released crate with Cargo:

Terminal window
cargo add brokk-bifrost

That produces a dependency like:

[dependencies]
brokk-bifrost = "0.14.0"

For local development against a checkout, use a path dependency:

Terminal window
cargo add brokk-bifrost --path /path/to/bifrost

The package name uses a hyphen, but Rust imports use the crate name with an underscore:

use brokk_bifrost::{AnalyzerConfig, FilesystemProject, WorkspaceAnalyzer};

The crates need Rust 1.99 or later.

brokk-bifrost is the supported default dependency. It is the compatibility facade: it re-exports the analyzer and service API, and Cargo resolves the core, analysis, language-adapter, RQL, flow, policy, semantic-pack, runtime, and MCP crates automatically. Most applications should depend on this package alone.

Each language family is a separate published adapter crate. This split keeps language-specific parser and resolver code separate from the shared analysis engine.

Source languageCargo packageRust crate
C and C++brokk-bifrost-cppbrokk_bifrost_cpp
C#brokk-bifrost-csharpbrokk_bifrost_csharp
Gobrokk-bifrost-gobrokk_bifrost_go
JavaScript and TypeScriptbrokk-bifrost-js-tsbrokk_bifrost_js_ts
Java, Kotlin, and Scalabrokk-bifrost-jvmbrokk_bifrost_jvm
PHPbrokk-bifrost-phpbrokk_bifrost_php
Pythonbrokk-bifrost-pythonbrokk_bifrost_python
Rubybrokk-bifrost-rubybrokk_bifrost_ruby
Rustbrokk-bifrost-rustbrokk_bifrost_rust

brokk-bifrost and brokk-bifrost-analysis depend on all of these adapters. Adding one adapter directly does not limit the languages that WorkspaceAnalyzer loads or reduce the facade dependency set.

Use a direct adapter dependency only when you own a focused host or an adapter integration. Keep every direct Bifrost dependency on the same release version. The adapter APIs are internal and can change between releases.

These crates are published so that focused hosts can compose them. Ordinary library consumers do not need them. Prefer the facade unless you own one of these boundaries.

Cargo packageContents
brokk-bifrost-coreSource model, project, and workspace utilities.
brokk-bifrost-analysisThe analysis engine, analyzer store, and search tools.
brokk-bifrost-rqlRQL syntax, typed query IR, planning, and execution.
brokk-bifrost-flowDataflow, value-flow, taint, and typestate engines.
brokk-bifrost-policyPolicy loading, evaluation, and reporting.
brokk-bifrost-semantic-packsThe shipped semantic-model packs and pack bundle installation.
brokk-bifrost-runtimeThe protocol-neutral runtime and the stable extension API.
brokk-bifrost-mcpThe MCP server implementation and SearchToolsService.

brokk-bifrost’s exported surface is the supported tier. While the project is pre-1.0, nothing is contractually frozen. Changes to that surface are deliberate, and release notes call them out.

Two parts of the facade differ from that rule:

  • brokk_bifrost::extension is the one module with a versioned compatibility contract. Clients negotiate an API version and the operations they need, and every response names its operation and stability. See Stable Extension API.
  • The modules benchmark, dataflow_differential, mcp_install, mcp_property_fuzzer, owasp_benchmark, reference_differential, and repo_status are public only so that Bifrost’s own binaries and tests can use them. They are not part of the supported surface.

Everything beneath the facade may change in any release, including a patch. The lower-level packages exist so that a host that owns one of those boundaries can compose them, not as a general-purpose API. Their types, traits, module paths, and crate boundaries change when the internal design requires it.

Nothing enforces this boundary: there is no sealing and no #[doc(hidden)] marking. Depending directly on an internal package compiles and works, but you are then tracking internals, and an upgrade may require source changes.

use std::sync::Arc;
use brokk_bifrost::{AnalyzerConfig, FilesystemProject, WorkspaceAnalyzer};
fn main() -> Result<(), Box<dyn std::error::Error>> {
brokk_bifrost::install_bifrost_semantic_model_packs()?;
let project = Arc::new(FilesystemProject::new(".")?);
let workspace = WorkspaceAnalyzer::build_persisted(project, AnalyzerConfig::default())?;
let analyzer = workspace.analyzer();
println!("languages: {:?}", analyzer.languages());
println!("files: {}", analyzer.get_analyzed_files().len());
println!("declarations: {}", analyzer.get_all_declarations().len());
Ok(())
}

install_bifrost_semantic_model_packs() registers the shipped semantic-model packs for the process. Call it once before you build an analyzer or a SearchToolsService. Without it, no shipped packs activate. If the environment variable BIFROST_OPEN_SEMANTIC_PACK_BUNDLE names a pack bundle directory, Bifrost installs that bundle instead of the shipped packs each time it opens a catalog for activation. An empty value or a bundle with no compatible pack makes that activation fail; the call to install_bifrost_semantic_model_packs() itself still succeeds.

build_persisted writes the persisted analyzer cache and reuses it on later runs. Data and Trust Boundaries describes where the cache lives. build_ephemeral_footgun builds over a temporary store that is deleted when the analyzer drops. Use it for tests, immutable fixtures, or a checkout you must not write to. All WorkspaceAnalyzer constructors return Result<WorkspaceAnalyzer, StoreError>.

The most-used exports are below. The complete list is in the Rust API reference.

ExportUse
WorkspaceAnalyzerBuild a workspace-backed analyzer with default multi-language routing.
IAnalyzerTrait for common analyzer operations.
FilesystemProject, FileSetProject, OverlayProject, MultiRootProjectProject backends for different file-source shapes.
ProjectFile, CodeUnit, DeclarationInfo, Language, RangeCore source and symbol model types.
AnalyzerConfigAnalyzer settings, including per-language dependency evidence.
SearchToolsService, ToolOutput, SearchToolsServiceErrorIn-process access to the same tool implementations exposed over MCP.
CodeQuery, CodeQueryExecutionMode, CodeQueryResponse, CodeQueryExecutionLimitsParse a JSON or RQL query, select results, explain, or profile, and bound execution.
execute_request, execute_request_with_limits, execute_request_with_cancellationRun a CodeQuery against an analyzer.
CancellationTokenCancel long-running calls.
extensionThe stable, versioned extension API.
install_bifrost_semantic_model_packs, open_pack_engine_profileRegister the shipped semantic packs; report the pack-consumer profile.
BIFROST_VERSION, BIFROST_BUILD_IDENTITYThe package version and the exact source revision of this build.

The facade also exports passive dependency-evidence types for Rust, Ruby, PHP, and JVM dependencies (for example RustDependencyApiEvidence, PhpDependencyApiEvidence, and JvmExternalDependencies). A host supplies this evidence. Bifrost reads it without running build tools.

For most embedded code-intelligence work, use SearchToolsService instead of composing analyzer calls yourself. It keeps tool arguments and rendering the same as MCP and the Python client.

use std::path::PathBuf;
use brokk_bifrost::SearchToolsService;
use serde_json::json;
fn main() -> Result<(), Box<dyn std::error::Error>> {
brokk_bifrost::install_bifrost_semantic_model_packs()?;
let service = SearchToolsService::new_manual_persisted(PathBuf::from("."))?;
let symbols = service.call_tool_value("search_symbols", json!({ "patterns": ["parse_*"] }))?;
println!("{symbols:#}");
// After the caller changes files on disk:
service.call_tool_value("update_paths", json!({ "paths": ["src/main.rs"] }))?;
service.close()?;
Ok(())
}

SearchToolsService::new(root) starts a file watcher, so later calls see file changes. new_manual_persisted(root) has no watcher; the caller reports changes with the update_paths or refresh tool. Both build the index before they return and use the persisted analyzer cache.

call_tool_value(name, arguments) returns the structured JSON result. call_tool_output(name, arguments, render_options) also returns the rendered text. query_code_result(arguments) runs query_code and returns a typed CodeQueryResponse. request_cache_gc() forces a cache collection. The tool names and arguments are the MCP tool names and arguments, plus update_paths, list_symbols, and classify_test_files. The Rust API reference lists every constructor and method.

Library Integration is the executable walkthrough: it runs one canonical query through SearchToolsService::query_code_result(...) against a checked-in fixture, shows the typed result consumption, states when to use the lower-level execute_request API instead, and runs the same query through the Python client so the two stay in step.

Build a query with CodeQuery::from_json(&value) or, for RQL text, CodeQuery::from_source(text). Then run it against an analyzer:

FunctionBehavior
execute_request(analyzer, &query)Honors the query’s root execution_mode. Uses default limits.
execute_request_with_limits(analyzer, &query, limits)Same, with caller-supplied CodeQueryExecutionLimits.
execute_request_with_cancellation(analyzer, &query, limits, &cancellation)Same, with a CancellationToken. The profile response includes cancellation observations and a partial result that is safe to read.
brokk_bifrost::rql::execute(analyzer, &query)Always returns ordinary rows, for embedders that own execution policy.

CodeQueryExecutionLimits::default() gives the interactive limits. Its fields and defaults are in the Rust API reference.

The untagged CodeQueryResponse::Results variant keeps the ordinary serialized result shape. Explain performs logical lowering and physical selection without reading analyzer data in that phase. Profile includes the exact ordinary result. See Explain and Profile CodeQuery for the wire format and measurement caveats.

use std::sync::Arc;
use brokk_bifrost::{
AnalyzerConfig, CancellationToken, CodeQuery, CodeQueryExecutionLimits, FilesystemProject,
WorkspaceAnalyzer, execute_request_with_cancellation,
};
use serde_json::json;
fn main() -> Result<(), Box<dyn std::error::Error>> {
brokk_bifrost::install_bifrost_semantic_model_packs()?;
let project = Arc::new(FilesystemProject::new(".")?);
let workspace = WorkspaceAnalyzer::build_persisted(project, AnalyzerConfig::default())?;
let query = CodeQuery::from_json(&json!({
"match": { "kind": "function", "name": "run" }
}))?;
let cancellation = CancellationToken::new();
let response = execute_request_with_cancellation(
workspace.analyzer(),
&query,
CodeQueryExecutionLimits::default(),
&cancellation,
);
let result = response.result().expect("results mode returns rows");
println!("rows: {}, truncated: {}", result.results.len(), result.truncated);
Ok(())
}

The CodeQuery steps typestate, value_flow, and taint read a compiled protocol, a value-flow plan, or retained taint results that the host registered. Only Rust can register them, on a SearchToolsService:

  • register_query_protocol and unregister_query_protocol
  • register_query_value_flow_plan and unregister_query_value_flow_plan
  • register_query_taint_results and unregister_query_taint_results

A query names a registration with a namespace:name reference, such as "taint_ref": "request:http-to-database". The CLI, REPL, MCP server, and Python client cannot register, so these steps return an unresolved-reference diagnostic there. The execute_request functions have no registrations either. The reference format, outcomes, and limits are in the Rust API reference.

brokk_bifrost::extension is the versioned API for independent applications. It is at version 1.1. A client states the API versions and operations it needs in an ExtensionCompatibility value. negotiate_extension_api accepts or rejects it, and every request carries it again.

OperationStabilitySupport
structural.querystablecomplete
experimental.semantic.control_flowexperimentalcomplete
experimental.semantic.value_dependenceexperimentalpartial
experimental.semantic.typestateexperimental, added in 1.1partial

Open a workspace with ExtensionWorkspace::open(ExtensionWorkspaceOptions::new(root)). The default store is in memory. Call .persisted() on the options to use the persisted analyzer cache. The workspace then answers structural_query, semantic_relations, and typestate_check requests. Each request names the workspace generation it expects, and each response reports its completion, diagnostics, work, and limits. The module also has a JSON codec (decode_request_json, encode_request_json, encode_response_json). The Rust API reference has the full API and an example.

The default build enables no optional features.

FeatureUse
pythonBuilds the PyO3 extension module that the Python package uses.
extension-moduleStops the build from linking libpython. maturin enables it together with python when it builds the Python package. Do not enable it for an executable, because the link fails.
test-supportRe-exports test-only hooks for use in a downstream crate’s own tests.

release-tooling and randomized-hash are internal features for Bifrost’s release and CI builds. Ordinary CLI and library builds need no feature.