JetBrains Context (jbcontext) is a CLI that indexes a repository, stores the vector index either locally or on
JetBrains servers, and exposes semantic code search to coding agents over MCP, skills, subagents and hooks. JetBrains
launched it in July
as an early-access add-on to JetBrains AI. Internally it is still called EmbArk almost everywhere, and JetBrains'
September write-up of the RAG pipeline
calls it Air Context. That write-up covers chunking, quantization and privacy from JetBrains' side, and I refer to it
below where it confirms or explains what I found.
This covers the release channels, the install footprint, the index format, what the local server actually does, the search pipeline and its network traffic, the MCP surface, and the prompt text the CLI writes into Claude Code, Codex and Junie.
Summary
- The CLI is a GraalVM native image of a Kotlin program (
ai.grazie.indexing.code.cli.*), published asembark-native-<os>-<arch>-<version>and, in newer versions, also ascontext-native-*. - Public stable builds come from
download.jetbrains.com/jetbrains-context. Preview and dev channels (embark,embark-eap,embark-dev) and the older prototypes live on a JetBrains Space file tree. - Embeddings are computed by JetBrains, not on your machine. The CLI POSTs chunk text to
api.jetbrains.ai/user/v5/indexing/embeddings/qwen/computeand gets 2560-dimensional float vectors back, which it stores as 1-bit vectors. The model isqwen3-embedding-4b, index descriptorCODE_BLOCKS-qwen3-embedding-4b-2560-bit-6. - "Local storage" (the default under 1 million LOC) means the vector index is stored locally: a USearch HNSW index and a
SQLite metadata database under
~/.jbcontext/local/indices/code_blocks-6/. The source still goes to JetBrains to be embedded. - Larger repositories are stored remotely. Remote search runs in two round trips: the server retrieves 50 candidates, the CLI fills in the snippet text from the local checkout and sends it back, and the server reranks with a Qwen3 pointwise reranker.
- The downloaded
embeddings-serveris a signed C++ binary from JetBrains' full-line completion stack.jbcontextuses it as a local vector store and nearest-neighbor search service. - The MCP server exposes
code_search(andfind_repositorieswith--multi-repo). setup-agentinstalls instructions, skills, subagents and hooks into Claude Code, Codex and Junie. The agent files are published ingithub.com/JetBrains/context. The CLI itself is closed source.
Method
otool,nm,strings,codesignandsqlite3on the installed binaries and index files.- Read-only CLI runs:
--help,--version,status,stats,analyze,setup-agent --dump-auto --print-instructions, plusindex,searchandremove-indexagainst a throwaway repository. - The app data directory
~/.jbcontext, the generated agent files, and the run log. - The public repository
github.com/JetBrains/context, which hosts the agent integrations and release notes but not the CLI source. - A Proxyman TLS capture on 2026-09-16, covering an index run on a small repository and a search against a remote index.
No disassembly of the GraalVM image. A native image has no JVM bytecode to decompile.
I've since uninstalled jbcontext and deleted ~/.jbcontext, so the local paths below describe the install as it was on
2026-09-12.
What jbcontext is
jbcontext --version prints version, build, commit and channel, for example
jbcontext version 0.9.12 (build 803, commit 762750b0cb, channel stable). Commands:
login Authenticate to the JetBrains AI Platform
logout Log out and clear stored credentials
index Index a project directory for semantic search
search Search for code using semantic search
status Show indexing status for the current repository
remove-index Remove your indexed snapshots for the current repository
stats Watch local search, repos and indexing activity (alias: dashboard)
mcp Start an MCP server over stdio for AI agent integration
setup-agent Set up skills, subagents, hooks, instructions, MCP
remove-agent Remove the agent integration
upgrade Check for updates and upgrade (alias: update)
doctor Run end-to-end self-tests
send-feedback Open a pre-filled feedback form
analyze Estimate token/cost savings across agent sessions
Hidden or experimental commands that appear in the binary and the generated agent files but not in --help:
config get/set/list, account, history, find-repositories (alias repos), and search --repository-id. Some of
these may only work in builds newer than 0.9.12.
Global options include --version, --enable-local-logging and --ci. --ci is unattended mode: logs go to stderr,
indexing can use the whole machine, it defaults to the unlimited optimization mode, and it requires org service
account credentials for large repositories.
Product lineage
| Era | Name | Versions | What it was |
|---|---|---|---|
| Jun 2025 | dev-agent | 0.0.2 to 0.0.4 | Experimental terminal SWE agent |
| Jun 2025 | delta | 0.0.1 to 0.0.10 | Near-identical successor to dev-agent |
| 2026 | embark / EmbArk | 0.8.135 to 0.8.248 | Code indexing and semantic search CLI |
| Mar 2026 | ci-code-indexer | 0.8.150 | CI build of the indexer |
| Apr 2026 | embark-localdeltas | 0.8.170-localdeltas | Experiment: local overlay for uncommitted changes (index-local) |
| 2026 | context | 0.8.214+ | Transitional rename |
| Jul 2026 | jbcontext / JetBrains Context | 0.8.x to 0.9.13 | Public early access |
| Sep 2026 | Air Context | Name used in JetBrains' RAG blog post |
EmbArk is still everywhere in class names and log tags: EmbArkEmbeddingsClient, EmbArkRerankClient,
EmbArkSearchClient, EmbarkIndexingClient, EmbarkLLMProfileIDs, estimatedWithEmbark, and the
org.jetbrains.embark.* protobuf package.
The 2025 delta and dev-agent binaries were full SWE agents with wrench, vanilla, find, explore and web
search commands. Their --version required login. The indexing product started with embark in 2026.
Distribution channels and endpoints
Public stable is a content-addressed tree:
https://download.jetbrains.com/jetbrains-context/release/version.txt -> 0.9.12.803
https://download.jetbrains.com/jetbrains-context/builds/v0.9.12.803/context-native-macos-arm64-0.9.12.803
https://download.jetbrains.com/jetbrains-context/builds/v0.9.12.803/run-jbcontext.sh
Channel pointers on 2026-09-12:
| Public channel | version.txt |
|---|---|
release | 0.9.12.803 |
beta | 0.9.13.845 |
alpha | 0.9.13.860 |
eap | 0.8.239 |
The preview mirror is a JetBrains Space file tree:
https://packages.jetbrains.team/files/p/grazi/jetbrains-ai-public-releases/
It hosts embark, embark-eap and embark-dev, plus the older delta, dev-agent, ci-code-indexer and
embark-localdeltas. Crawling it gave 611 directories, 4,399 files and about 528 GB of binaries across 604 version
directories. Dev builds carry branch names that show what was being worked on: reranking, multi-repo indexing, Codex
hooks, a JCP auth flow, keyring integration, release channels, and YouTrack IDs like EMBARK-1184 and AIR-5314.
Of the 604 version directories, 308 ship the binary under both names, 281 only as embark-native-* and 3 only as
context-native-*; the rest are delta and dev builds. The one pair I hashed (0.8.248, macOS arm64) was
byte-identical. Binaries range from about 95 MB to 258 MB.
Other endpoints in the CLI:
https://api.jetbrains.ai prod API
https://api.stgn.jetbrains.ai staging API
https://api.jetbrains.cloud JetBrains Cloud
https://api.jetbrains.cloud/org
https://account.jetbrains.com account / license
https://oauth.account.jetbrains.com
https://download.jetbrains.com/resources/ml/full-line/servers/3.0.185/macos/arm_64/embeddings-server.zip
https://github.com/JetBrains/context agent integrations + release notes
https://www.jetbrains.com/context/ product page
https://www.jetbrains.com/legal/docs/terms/jetbrains-context-cli-eap/
The indexing API paths are listed in What the traffic shows.
Gateway client classes in the native image: EmbArkEmbeddingsClient, EmbArkRerankClient, EmbArkSearchClient,
CoreExperimentalIndexingClient, ExperimentalIndexingAPIClient, ExperimentalIndexJobAPIClient,
ExperimentalSearchAPIClient, LicenseServerAuthClient, QuotaAPIClient, EnterpriseQuotaAPIClient,
FeedbackAPIClient, LlmAPIClient, AuthAPIClient.
Install footprint
jbcontext is a 69-byte shim that execs run-jbcontext.sh. The runner reads active-version.txt, picks
~/.jbcontext/versions/<version>/jbcontext_binary, and runs it through a Perl wrapper that explains macOS Gatekeeper
kills (exit 137). Layout:
~/.jbcontext/
bin/ launchers, active-version.txt, run-jbcontext.sh
versions/<version>/ one directory per installed version, with .channel marker
config.json agent setups, access selection, active version
grazie-token-prod.json encrypted credential blob (mode 600)
cache/
project-sizes.json cached LOC map for local paths
auto-storage.json per-repository local/remote decision
locks/index-v1/ per-repository 0-byte lock files
logs/jbcontext.log
stats/<date>.json append-only local event log
local/
indices/code_blocks-6/ local vector indexes + SQLite metadata
runtime/ embeddings-server.state (pid, port), lock, warnings log
server/3.0.185/ downloaded embeddings-server.app
analytics/ local agent-session analytics cursors and outbox
Nothing is written into the project directory and no .gitignore entries are added. Exclusions go in
.jbcontextignore, which takes .gitignore-style patterns since 0.9.7.
The installed binary was public stable 0.9.12.803 for macos-arm64, SHA-256
7fe2b87a77144b43892226810342500192bf0fe233d34a0ece248ba551ed1faf, 167,802,176 bytes, matching the artifact under
builds/v0.9.12.803/. Signed by team 2ZEFAR8TH3 (JetBrains).
Indexing
Repository identity
The repository id comes from the git remote, lower-cased: github.com/helgesverre/token. index, search and status
take either a local path or a git remote URL. A path below the git root is lifted to the root. Branch and revision are
stored with the snapshot.
Clustering and incremental indexing
The indexer groups files into clusters and splits them into chunks (byte ranges with offsets and line numbers).
According to JetBrains, chunking follows syntax for Kotlin, Java, Python, JavaScript, TypeScript, C#, PHP, Go and Rust,
keeping doc comments and annotations attached to declarations, and falls back to line-based splitting for everything
else. Each chunk is embedded with its file path prepended; in the capture that looks like
Filepath: BUGS.md\n---\n<chunk text>.
Worker counts follow the CPU: 11 cluster workers and 2 embedding workers on a 12-core machine.
Re-indexing is cluster-incremental. Unchanged clusters from the previous snapshot are reused, and only changed ones are embedded again. A lab repository with 10 clusters and one changed file logged:
Loaded 10 clusters from server
Clusters in diff: fully reuse 8 clusters, modified 2 clusters
Fully reused 8 of 8 clusters (1 batches of 256)
Reindexing needed for 1 clusters, tried to reuse 2 (1 batches of 256)
index --json-output emits progress (0/5/95/100), skipped (with a reason) and finished events. Skip reasons:
ALREADY_INDEXEDwhen the revision already has a completed snapshot. Session hooks hit this almost every time.INDEXING_IN_PROGRESSwhen another process holds the repository lock.
Locks are 0-byte files at ~/.jbcontext/locks/index-v1/<64-hex>.lock. They stay after indexing finishes and are not
removed by remove-index. I couldn't work out what the filename hash is computed from.
Local vs remote storage
Since 0.9.8, repositories under 1 million lines of code store their index locally by default, and larger ones store it
on JetBrains' servers. The runtime.storage setting overrides this. The decision is cached per repository in
cache/auto-storage.json as {"storage": "local"|"remote", "resolverVersion": 1}. As of 0.9.12, local storage rejects
repositories above the same size limit as the server, and indexing large repositories is limited to the JetBrains
Central program.
Either way, the chunk text goes to /embeddings/qwen/compute. Storage mode only decides where the vectors are kept.
A lab repository finished with:
{ "type": "finished", "totalClusters": 10, "processedNewClusters": 10, "storage": "local" }
My token editor repository went remote. The stats filesystem scan put it at about 2.6M LOC, but that count includes
an untracked temporary-docs/ folder. The indexer processed 154 clusters, 1,280 files, 1,192,990 lines and 76.9 MB of
source in about 106 seconds. The resulting snapshot reported about 116 MiB, which looks like repository file size rather
than index size. Uploads go in batches of 32 clusters to /user/v5/indexing/index-chunks, with 60-second timeouts and
exponential backoff (200 ms to 6.4 s, 6 attempts), then poll for snapshot completeness. On that run the final
completeness poll gave up with a RetryException, and the CLI still recorded the index as successful.
Snapshot status
status --json-output lists snapshots with revision, branches, cluster count, size and creation time. remove-index
deletes all snapshots for the repository, both remote snapshots and the local index directory:
{ "type": "remove_index_result", "repositoryId": "github.com/...", "removedSnapshots": 3, "snapshotsRemain": false }
Local index format
Layout
~/.jbcontext/local/indices/code_blocks-6/github.com_<owner>_<repo>/
index.usearch USearch HNSW vector index
metadata.db SQLite metadata
The directory name is the index scope (code_blocks) and descriptor version (6), not the model name.
Descriptor
The single row in the meta table holds the descriptor:
{
"scope": "CODE_BLOCKS",
"embedModelId": "qwen3-embedding-4b",
"dimensions": 2560,
"precision": "Bit",
"env": { "_type": "ai.grazie.code.indexing.model.IndexEnvironment.Stable" },
"version": 6
}
precision: "Bit" means each of the 2560 dimensions is stored as one bit, so a vector is 320 bytes. The index.usearch
header contains 0x140 (320) in its second word, which matches. The API returns full float vectors, and the client does
the quantization. JetBrains' post describes it as keeping only the sign of each component and comparing vectors by
Hamming distance. The server lists descriptor versions 4, 5 and 6, all with the same model, dimensions and precision.
SQLite schema
The metadata database is written by the Kotlin CLI through sqlite-jdbc. Tables:
meta(id, schema_version, descriptor, repository, created_at, last_snapshot_at,
last_snapshot_files_count, last_snapshot_files_size_kb, total_snapshot_count,
completed_snapshot_count, total_file_size_kb, index_size_bytes)
snapshot(id, revision, branches, created_at, created_by_ver, user, clusters, status,
indexer_type, library_version, files_count, files_size_kb, files_processed,
kbytes_processed, loc_processed, permanent, languages, committer_time)
cluster(id, cluster_id, status, user, hash, leaf_hash, paths_hash, files_processed,
chunks_processed, kbytes_processed, loc_processed, languages, metadata)
cluster_to_snapshot(snapshot_id, cluster_pk_id, cluster_id)
chunk_hash_to_vector(id, hash)
chunk(cluster_pk_id, type, path, from_offset, to_offset, vector_id, metadata)
chunk_hash_to_vector de-duplicates vectors by content hash, so there can be more chunk rows than vectors. The lab
index had 5,060 chunks. The snapshot row also stores a language breakdown, for example
{"Rust":120,"Markdown":8,"Python":12}, and indexer_type (observed: USER).
Chunk rows hold paths and offsets, not source text. JetBrains says the same about its server-side storage: a chunk is a cluster reference, a path, start and end offsets and a vector reference, and snippets are rebuilt from your checkout.
The local embeddings server
What it is
With local storage, the CLI downloads a native service from JetBrains' full-line completion stack:
https://download.jetbrains.com/resources/ml/full-line/servers/3.0.185/macos/arm_64/embeddings-server.zip
It is extracted to ~/.jbcontext/local/server/3.0.185/embeddings-server.app:
| Property | Value |
|---|---|
| Path | embeddings-server.app/Contents/MacOS/embeddings-server |
| Size | 31,245,056 bytes |
| Type | Mach-O 64-bit executable arm64, min macOS 12.0 |
| Bundle id | org.jetbrains.completion.embeddings.local.server |
| Bundle version | 3.0 (download path version 3.0.185) |
| Signature | Developer ID Application: JetBrains s.r.o. (2ZEFAR8TH3) |
| SHA-256 | c269fb8e6276a6339f31ca4344f2557f3612eb45818c1d00d6ea530d68b35423 |
It's C++, built on JetBrains TeamCity from cpp/embeddings/{server/exe,server/lib,storage,tokenizer}/src and
cpp/utils/watchdog.
The name suggests it computes the Qwen3 embeddings. It doesn't. The binary is 31 MB and ships no Qwen3 weights, its
built-in tokenizer is BERT WordPiece while Qwen3 uses BPE, and the defaults are 128-dimensional vectors with 64-token
inputs. The capture shows where the embeddings actually come from: /embeddings/qwen/compute for chunks and
/embeddings/qwen/retrieve for queries. jbcontext uses this server to store vectors and search them.
Libraries
System frameworks: libc++, CoreFoundation, Security, Foundation, Accelerate, libresolv. Statically linked:
- ONNX Runtime, CPU only, with the BERT contrib ops and MLAS kernels. This is the inference path
jbcontextdoesn't use. - gRPC and Protocol Buffers, including the xDS/grpclb machinery, c-ares and BoringSSL.
- USearch HNSW (
expansion_addandexpansion_searchdefault to 64). - A BERT/WordPiece tokenizer (
basic_tokenizer,wordpiece_tokenizer,bert_tokenizer,batch_tokenize). - No SQLite. The CLI owns the metadata database.
gRPC API
Service org.jetbrains.embark.local.server.stubs.embedding_service:
start_indexing_session finish_indexing_session
ensure_texts_present ensure_vectors_present
filter_absent_ids estimate_similarity
search_by_text search_by_vector
get_storage_stats clear_storage
update_parent
ensure_vectors_present and search_by_vector are the calls that fit a store fed with vectors computed elsewhere. The
*_text calls need a local model.
The CLI starts the server as a child process on a random localhost port recorded in
~/.jbcontext/local/runtime/embeddings-server.state (pid=<n>, port=<n>). Transport is plaintext gRPC. A watchdog
exits the server when the parent process dies. On my machine it logged
process_checker threw an exception: parent process start time is equal to 0 on every tick, which is harmless but fills
embeddings-server.warnings.log.
Server options
-p, --port listening port (random if unset)
--parent-pid parent process PID
--orphan-timeout seconds to stay alive after parent death (default 0)
--disable-logging
--warning-log-file warnings for adopting parents
-S, --storage-root storage root for all embedding indices (required)
--vector-length vector length (default 128)
-q, --quantization f64, f32, f16, i8, b1 (default i8)
-k, --metric-kind ip, cos, l2sq (default ip)
--working-threads-limit 1..12 (default 8)
--batch-insertion-threads 1..12 (default 4)
--index-size-limit per-storage limit (default 1000000)
--capacity-lower-bound default 20000
--expansion-add HNSW expansion_add (default 64)
--expansion-search HNSW expansion_search (default 64)
-m, --model-path ONNX model file
-t, --n-threads worker threads, 0 = auto (default 2)
-b, --batch-size batch size for repository indexing (default 32)
--inference-deadline max inference time in ms, 0 disables (default 60000)
--models-pool-size 1..4 concurrent models (default 1)
--use-global-memory-arena
-v, --vocab-path tokenizer vocabulary file
-L, --max-sequence-length tokenizer max length (default 64)
--insert-special-tokens wrap with [CLS]/[SEP] (default off)
--skip-unknown-tokens
--pad-to-max-sequence-length
Without a model and vocabulary path, the server starts with inference disabled and logs "Both model and tokenizer vocab
paths are absent, model inference will be disabled". That is the mode jbcontext needs.
Search
Remote index
The capture shows a search against the remote token index taking two calls to /user/v5/indexing/search.
Step 0, retrieval:
{
"text": "how does token load files when dropped into the gui?",
"repository": "github.com/helgesverre/token",
"maxResults": 50,
"minScore": 0.3,
"searchPipelineConfig": {
"pipeline": "RETRIEVE_RERANK",
"customReranker": "POINTWISE_QWEN",
"candidatesPoolSize": 50
},
"filter": { "type": "branch", "branch": "main", "defaultBranch": "main" },
"context": { "commitSha": "8428a3d9…", "committerDate": 1789553346000, "branch": "main" }
}
The server returns 50 candidates as paths and byte offsets, with scores between about 0.71 and 0.73. The CLI then reads
those byte ranges from the local checkout and sends the same request again with "step": 1 and a
candidatesWithContent array holding the snippet text. The server reranks them and returns new scores from about 0.97
to 0.997.
So the server doesn't need to store your source to show snippets, but at search time the candidate snippets are sent to it for reranking.
Local index
For a locally stored index, the query goes to /embeddings/qwen/retrieve with an instruction prefix:
Instruct: Given a question about coding, retrieval code or passage that can solve user's question
Query: <your query>
The returned vector is searched against the local USearch index through the local server. The capture has one local query and no rerank call after it, so I can't say whether local results get reranked.
Scores
The raw retrieval scores sit in a narrow band. With 1-bit vectors, about half the bits of two unrelated vectors agree by chance, so relevant and irrelevant chunks score close together. JetBrains' post says the same, and says they keep 16-bit vectors where an absolute threshold is needed. Ranking works; thresholds don't.
The scores in jbcontext search --json-output are the reranker's. Good hits score above 0.99, but the scale isn't
compressed: in a 50-result search the lowest score was 0.017.
Very large markdown files can crowd out code. In one search, 45 of 50 results were from concatenated agent transcripts
under docs/. -p src fixed it. JetBrains says -p is implemented by writing the path scope into the query text so it
matches the path prefix embedded with each chunk, rather than as a metadata filter. In my test all 3 results landed
under src/, but that doesn't tell the two approaches apart.
Other flags: --limit (default 50), --revision, -p/--path-filter, --branch, --json-output,
--enable-remote-logging. Revision resolution: config repository-revision, then the snapshot for the current branch,
then the default branch, then the most recent snapshot.
Multi-repo
--repository-id and find-repositories drive cross-repository search. MultiRepoSearchExecutor fans a query out
across repositories and logs Search failed for repository ... with IndexNotFoundException when one has no remote
index.
Result schema
{
"type": "search_result",
"results": [
{
"result": {
"scoredText": { "similarity": 0.9913 },
"sourcePosition": {
"relativePath": "src/theme.rs",
"startOffset": 42479,
"endOffset": 43132
},
"indexItemType": "CHUNKS"
},
"content": "/// Syntax highlighting colors (resolved)\n...",
"contentStartLine": 1253
}
],
"message": "Search completed successfully",
"revision": "73418e388cb3a6014db3a810c4d658a4452b87e3"
}
content and contentStartLine are filled in by the CLI from the local checkout. The server response only has the
position.
Reranking and query expansion in the binary
The CLI contains an ai.grazie.code.indexing.advsearch package with a pipeline of steps (ExpandedRetrievalStep,
snippet providers, merge steps) and several rerankers:
advsearch.reranker.relevance.Qwen3Rankeradvsearch.reranker.relevance.LLMRelevanceRankeradvsearch.reranker.joint.JointRankeradvsearch.reranker.joint.ListwiseRankeradvsearch.reranker.joint.WinrateEstimatorRerankerFactory,RerankerConfig
The run log shows Qwen3Ranker - Reranking 50 candidates for query '<query>', which matches POINTWISE_QWEN and
candidatesPoolSize: 50 in the capture. Only the Qwen3 pointwise reranker showed up in traffic. The others may be
server-side options or unused.
There is also a query-expansion prompt embedded in the CLI:
You are an AI language model tasked with generating multiple search queries to improve document
retrieval from a vector database. You have access to the user's original question as well as
preliminary search results obtained via kNN nearest neighbor search. Your goal is to overcome the
limitations of distance-based similarity search by providing different queries.
[INSTRUCTIONS]
1. Understand the Intent ...
2. Analyze the preliminary results to identify missing details ...
3. Analyze the preliminary results to understand the properties and specifics of the database ...
4. Determine the best match: create a query that aligns with database contents ...
IMPORTANT: Output only the queries, each query as a separate line without additional explanations
or text.
Bundled under messages/fleet/ there is also a relevance prompt ("Given a query and a snippet of code, you must rank
snippets by relevance to the query") and a retrieval-subagent prompt that returns ranked file:line locations through a
submit_results tool on a 0 to 3 scale (3 = exact answer, 2 = indirect, 1 = related, 0 = dropped). None of these
appeared in the captured traffic.
MCP server
jbcontext mcp runs a stdio MCP server:
| Tool | Availability | Parameters |
|---|---|---|
code_search | default | text, pathFilter |
find_repositories | --multi-repo | repository name query, plus listing/pagination options |
The tool description, from github.com/JetBrains/context:
Search for code using jbcontext semantic search in the specified repository or repositories.
- Use this tool when you need to find code whose location you don't already know - e.g., when the
task asks 'where is X', 'how does Y work', or describes behavior or intent without naming exact
symbols.
- When not to use: if you already know the relevant file, class, or symbol (use direct navigation
or keyword search instead), or for non-code-discovery tasks such as git operations, builds,
tests, or reviewing an existing diff.
This tool performs AI-powered semantic code search that understands code meaning and context, not
just keyword matching. ...
Parameters:
text: "Use clear, focused natural language queries that describe a single concept or intent. ... Avoid single words like 'email' or 'error', and avoid keyword-style queries that just concatenate terms."pathFilter: "Must be a path relative to the project root."
With --multi-repo, code_search gets a multi-repo hint and find_repositories is exposed.
Agent integration
jbcontext setup-agent writes into each agent's config directory. Targets from --dump-auto:
| Agent | Skills | Subagents | Instructions | Hooks | MCP | Rules |
|---|---|---|---|---|---|---|
| Claude Code | ~/.claude/skills | ~/.claude/agents/*.md | ~/.claude/CLAUDE.md | ~/.claude/settings.json | yes (minimal) | no |
| OpenAI Codex | no | ~/.codex/agents/*.toml | ~/.codex/AGENTS.md | ~/.codex/config.toml | no | ~/.codex/rules/jbcontext.rules |
| Junie | ~/.junie/skills | no | ~/.junie/AGENTS.md | ~/.junie/config.json | no | no |
Components: --skills, --hooks, --no-session-hooks, --instructions, --mcp, --mcp-minimal, --subagents,
--rules. Profiles: --auto plus --strong/--aggressive and --multi-repo. --dump-auto --print-instructions
prints the plan and instruction block without installing anything.
The instruction block sits between sentinel comments so it can be replaced without touching the rest of the file:
<!-- jbcontext-instructions-start -->
...
<!-- jbcontext-instructions-end -->
Session hooks run jbcontext index --silent in the background at session start (and at session end where the agent
supports it). Claude Code gets:
"SessionStart": [
{ "matcher": "", "hooks": [
{ "type": "command",
"command": "/Users/<user>/.jbcontext/bin/jbcontext_binary index --silent",
"async": true } ] }
]
Codex gets a SessionStart hook in ~/.codex/config.toml with a 2-second timeout running
sh -c 'jbcontext index --silent >/dev/null 2>&1 &', plus execution-policy rules that let the read-only commands out of
the sandbox, since they need network, Keychain and the JetBrains daemon socket:
prefix_rule(pattern = ["jbcontext", "search"], decision = "allow", ...)
prefix_rule(pattern = ["jbcontext", "repos"], decision = "allow", ...)
prefix_rule(pattern = ["jbcontext", "find-repositories"], decision = "allow", ...)
The Claude Code MCP entry is a stdio server pointing at jbcontext mcp.
The prompts
There are three layers: files written into agent configs, context injected by hooks, and prompts used by the search
backend. The agent files are published in github.com/JetBrains/context. I diffed the installed Claude subagent, the
context-search skill and the Codex agent against upstream and they matched. I didn't diff the instruction blocks or
hooks. Quotes below are from the shipped files, with em dashes rendered as hyphens.
Instruction block
The default (subagent) variant starts:
# Tools
## Code discovery: context-explorer first
When a task requires finding or understanding code whose location you don't already know, your FIRST code-discovery step
MUST be:
Task(subagent_type='context-explorer', description=<short label>, prompt=<1-2 sentence intent describing what to find>)
Start there instead of opening with your own `grep`/`glob`/`bash` searches or git history: the subagent runs the
semantic exploration in its own context and hands back concrete `file:line` references, so you don't burn your context
re-reading the same files.
This governs _how_ you begin code discovery - not whether every task needs it. Do NOT call context-explorer when the
task doesn't involve locating code:
- the task names the exact file, class, or symbol - open it or grep directly;
- the relevant file is already open or identified;
- the work is a git operation (rebase, merge, commit), a test/build run, shell/statusline/config setup, or a review of a
diff you already have.
Then the search section:
## Semantic Code Search (jbcontext)
### Usage
jbcontext search "<detailed and descriptive query>" jbcontext search -p <path> "<query>"
### Single-Shot Policy
- If no relevant file is open yet, start with one `jbcontext search`.
- Make the first query specific to the issue's named feature, class, method, config flag...
- After the first search, open at least one returned file and inspect it locally.
- If the first hit is relevant but incomplete, inspect neighboring files locally...
- If a semantic retry is still needed, use `jbcontext search -p <path> ...`
The MCP variant says "You have access to the jbcontext MCP code_search tool" and uses pathFilter instead of
-p <path>. The --strong/--aggressive variants add a mandatory section. Its heading is "MANDATORY: jbcontext-first
bootstrap" in the Claude files and "MANDATORY: Use jbcontext first for code discovery" in the generic ones. The generic
no-skills variant says "MANDATORY: Always use jbcontext for code search". The --multi-repo variant adds a section that
drives jbcontext repos and per-repository searches with --git-remote-url.
The Claude subagent instructions say the subagent is "restricted to jbcontext search via Bash and Read only". The
subagent's own frontmatter gives it mcp__jbcontext__code_search, Read, Grep, Glob. The two files don't agree.
Skills
The installed skill is context-search (frontmatter name, description, argument-hint: query). Upstream also has
context-review (review changes by searching for similar patterns, callers and tests), context-install (install the
CLI and map errors: 401 means not logged in, 404 means no index), context-research (deeper exploration using
jbcontext search and jbcontext history), and the multi-repo skills org-search, dependency-search and
blast-radius.
context-search uses the same framing as the instruction block:
name: context-search
description: "Semantic code search for discovering and understanding code by meaning rather than keywords.
- Use this skill when you need to find code whose location you don't already know...
- When not to use: if you already know the relevant file, class, or symbol (use direct navigation
or keyword search instead), or for non-code-discovery tasks such as git operations, builds,
tests, or reviewing an existing diff."
context-review runs git status / git diff, then searches for similar patterns, callers and related tests for each
changed file, with a checklist covering callers, tests and previously fixed bugs.
The org-wide skills are the interesting ones for large codebases. dependency-search uses jbcontext repos "<terms>"
to pick candidate repositories, runs jbcontext search --repository-id ... --json-output --limit 10 in each, then uses
gh to fetch manifests and metadata. blast-radius estimates the impact of changing an API, schema, event or shared
library by finding producers, consumers, owners and tests across repositories.
Subagent
context-explorer is a read-only research agent. The Claude version:
name: context-explorer
description: "Iteratively explore an unfamiliar codebase using semantic search... Skip when the task
already names an exact file, class, or symbol (keyword grep is faster there), or when the task isn't
code discovery at all - git operations (rebase, merge, commit), test/build runs, shell/statusline/config
setup, or reviewing a diff already in hand."
tools: [mcp__jbcontext__code_search, Read, Grep, Glob]
model: haiku
The system prompt uses XML-style sections:
<role>: "You are a code research agent... You do not edit. You report findings - including the actual code snippets you read - to a parent agent so it does not have to re-fetch them."<workflow>: check the intent, don't search on non-discovery tasks, up to 3 semantic searches and 3 reads, stop early when confident, refine withpathFilterinstead of repeating a query.<query_style>: natural-language intent, not keyword lists. Good: "function that validates user email addresses and returns boolean". Bad: "validateEmail user email function".<output>: a report withSearched,Read,FindingsandNotessections, each finding with a 10 to 30 line snippet and a confidence level.<rules>: onlymcp__jbcontext__code_searchandRead, no bash, no edits, no invented paths or line numbers, output around 1.5K to 3K tokens.
The Codex version (context_explorer, TOML) has the same budget and report format but differs in setup. It calls the
jbcontext search CLI instead of MCP, and sets model = "gpt-5.6-luna", model_reasoning_effort = "medium",
sandbox_mode = "read-only" and nickname_candidates = ["Scout", "Cartographer", "Pathfinder", "Ranger"].
Hook-injected context
The UserPromptSubmit hook adds this to every prompt:
Workflow: if the relevant file or subsystem is still unclear, use semantic search before broad
local discovery. After a semantic result, read at least one returned file before expanding locally
with nearby files in that same directory or subsystem. If another semantic search is still needed,
narrow it: on CLI use `jbcontext search -p <path> ...`, and on MCP set `pathFilter`.
The opt-in PreToolUse hook (Claude only, --hooks) keeps per-session state in
/tmp/claude-context-hook-<session>.json with bootstrap_done, read_after_bootstrap and narrowed_retry_used, and
blocks tool calls with permissionDecision: "deny":
grep/rg/findbefore the first search: "Do not start broad local discovery before semantic bootstrap. Use one broadjbcontext searchfirst when the relevant area is still unknown."- Local search tools after the first search but before reading a hit: "Read at least one returned file from the bootstrap search before switching to local search tools."
git log/show/blamebefore the first search: "Do not use git history for initial discovery. Start with semantic bootstrap, then inspect nearby code locally."- A second broad search: "Do not issue a second broad
jbcontext search. Read a returned file first, inspect nearby code locally, and if a retry is still needed usejbcontext search -p <path> ...." - A narrowed retry before reading a hit: "Read at least one returned file from the bootstrap search before a semantic
retry. Inspect nearby code locally first, then retry with
jbcontext search -p <path> ...if still needed." - A second narrowed retry: "The narrowed semantic retry has already been used. Continue from the files and directories
you have already identified instead of issuing another
jbcontext search."
The hook only recognizes Bash commands containing jbcontext search as the first search. An agent that searches through
MCP code_search never sets bootstrap_done, so Grep stays blocked for the whole session.
Codex gets reminders instead of blocks. The SessionStart instruction hook prints a shorter tip:
Tip: when the relevant file is unknown, do one broad `jbcontext search` first, then read at least
one returned file and inspect nearby code locally. If that still fails, do at most one narrowed
retry ... Do not issue a second broad semantic search.
What the traffic shows
The 2026-09-16 capture covers an index run over several small repositories and a search against the remote token
index. JetBrains endpoints seen:
| Endpoint | What it carries |
|---|---|
POST /user/v5/indexing/embeddings/qwen/compute | Chunk text with Filepath: prefix, dimensions: 2560, task: "compute". Returns float vectors. |
POST /user/v5/indexing/embeddings/qwen/retrieve | Query text with the Instruct: prefix, task: "retrieve". Returns a float vector. |
POST /user/v5/indexing/search | Two-step retrieve and rerank, as above. Step 1 includes snippet text. |
POST /user/v5/indexing/indices | Repository id, sometimes with the index alias. Sent for local-storage repositories too. |
POST /user/v5/indexing/completed-snapshots | Descriptor and repository id. |
GET /user/v5/indexing/index-descriptor?indexAlias=code-blocks&maxVersion=6 | Returns descriptor versions 4 to 6. |
POST /auth/jcp/provide-access/global-access-token | License id. |
The capture didn't include a remote upload to /index-chunks, so I haven't seen that payload. The CLI gets embeddings
from /compute before storing them, so the upload probably carries vectors and positions rather than source, but that
is inference.
Data flow
| Data | Where it goes |
|---|---|
| Chunk text + file path | JetBrains, /embeddings/qwen/compute, in both storage modes |
| Search query text | JetBrains, /embeddings/qwen/retrieve (local) or /indexing/search (remote) |
| Candidate snippet text | JetBrains, /indexing/search step 1, for reranking (remote index) |
| Vectors, local mode | ~/.jbcontext/local/indices/ |
| Vectors, remote mode | /user/v5/indexing/index-chunks |
| Repository ids and snapshot metadata | JetBrains, in both storage modes |
| Index and search events | ~/.jbcontext/stats/<date>.json |
| LOC cache | ~/.jbcontext/cache/project-sizes.json (about 200 paths, including ~/Downloads) |
| Credentials | macOS Keychain plus ~/.jbcontext/grazie-token-prod.json |
| Agent session analytics | Cursors and outbox under ~/.jbcontext/analytics/ |
JetBrains says source code is not stored on its servers and that embeddings run on open-weight models on GPUs it operates, with no third-party embedding APIs. The traffic fits that: chunk text is sent to JetBrains to be processed, not uploaded to be kept. "Local storage" doesn't mean your code stays on your machine. It means the vectors do.
remove-index deletes remote snapshots and the local index. config set repository-profile-upload false stops index
from uploading repository profile data. jbcontext login supports user and app tokens, IDE Services automation tokens,
JCP permanent and service tokens, context-principal auth, org service accounts and license verification. The launch post
says usage consumes no AI quota.
stats --json-output reports totalSearches, semanticSearches, totalRepositorySearches, avgSearchDurationMs,
totalIndexings, avgIndexDurationMs, totalErrors and estimatedTokens, broken down per day, repository and client
type. Running stats also scans the filesystem to size local repositories.
analyze reads local Claude Code, Codex and Junie transcripts and splits token spend into Exploring, Editing,
Running, Thinking and Other, including an estimate of what the same tasks would have cost without semantic search.
I saw no transcript upload.
Artifacts
| Artifact | Version | Size | SHA-256 |
|---|---|---|---|
context-native-macos-arm64-0.9.12.803 (jbcontext_binary) | 0.9.12.803 | 167,802,176 B | 7fe2b87a77144b43892226810342500192bf0fe233d34a0ece248ba551ed1faf |
embeddings-server (macOS arm64) | 3.0.185 | 31,245,056 B | c269fb8e6276a6339f31ca4344f2557f3612eb45818c1d00d6ea530d68b35423 |
Still open
- The
/index-chunksupload payload for remote repositories. - Whether local-index searches are reranked.
- What the lock filename hash is computed from.
- What
gemini-embedding-001in the CLI is for. - The
index-localoverlay for uncommitted changes, which only exists in theembark-localdeltasexperiment build.
Reproducing
jbcontext --version
jbcontext status --json-output
jbcontext setup-agent --dump-auto --print-instructions --agent claude
jbcontext setup-agent --dump-auto --agent codex
jbcontext stats --json-output
# Local index
for db in ~/.jbcontext/local/indices/code_blocks-6/*/metadata.db; do
sqlite3 "$db" 'SELECT descriptor FROM meta'
done
sqlite3 ~/.jbcontext/local/indices/code_blocks-6/github.com_<owner>_<repo>/metadata.db '.schema'
# Embeddings server
S=~/.jbcontext/local/server/3.0.185/embeddings-server.app/Contents/MacOS/embeddings-server
file "$S"; otool -L "$S"; "$S" --help
The traffic capture was made with Proxyman while running jbcontext index and jbcontext search.
