Spec
python-sdk/docs/design/specs/2026-09-03-agent-facing-query-facade.md (codellm-devkit/python-sdk#318)
Summary
CLDK 2.0 becomes a complete static-analysis front end — symbol table, source bodies, repository artifacts, call graph, control and data flow, addressing, reachability, slicing and taint — behind one surface serving both human and agent callers.
This epic is a sibling of #35, not a child. #35 is schema-v2 conformance across the analyzers; this is the SDK's query facade. The divergence register the design produced belongs to #35 and #36 and is filed there.
The premise that changed
#35's SDK children (codellm-devkit/python-sdk#309, #308) assumed the SDK could model the schema once — one Node / Edge / Application. Measured against the three released analyzers that does not hold:
| Node |
python |
typescript |
java |
shared by all three |
type |
15 |
23 |
16 |
8 |
callable |
26 |
20 |
17 |
10 |
Every survivor is identity, containment or the graph. Not one shared semantic declaration field — attributes/fields, modifiers, decorators, base classes, exported/async/abstract/ambient, records and overloads all diverge. So the sharing moves up a layer: from the model to the query shape. Each language keeps its own models; one generic ABC defines what can be asked.
How the design was derived
By taking a production agent pipeline apart stage by stage and asking of each: what is this stage doing that is really a static-analysis query, and what signature would remove it? Five code-domain stages collapse — alert-to-sink mapping becomes locate_many, index building disappears into a corrected enumeration, entrypoint cataloguing drops a sharded LLM substage because analyzers already emit is_entrypoint, the reachability cone becomes a server-side traversal that returns its unresolved-dispatch ledger, and narrative source-to-sink argument becomes taint() with per-edge provenance.
That pipeline is one of several possible consumers. An IDE assistant, a code-review agent, a migration tool and a documentation generator decompose into the same four moves — locate, read, relate, prove.
Design decisions
Nine, locked in the spec. The load-bearing ones:
- One surface for humans and agents. Bulk calls, returned diagnostics, named rather than positional addressing and server-side traversal are ordinary good API design, not agent accommodation. Splitting the surface would let the human half rot.
- No shared declaration models. Each language owns its own; Python's stay a re-export.
- One generic ABC in three families — structural getters (per-language bodies, because TypeScript splits type kinds across five node labels), graph queries (one Cypher template plus a
P/N label-prefix pair, because PY_DDG/TS_DDG/J_DDG are structurally identical), and resolve() (per-language).
- API signatures backwards compatible, enforced per leg — which does not extend to graph generation; see the release plan.
- Drop C entirely. libclang, syntactic-only, no v2 graph, no analyzer, no deployment selects it.
- Neo4j substrate for this cut, attached read-only, so nothing is materialised.
- An ambiguous empty is a defect. Closed selector vocabulary, first-class diagnostics,
strict=True, and a graph_schema_mismatch probe on attach.
Explicitly out of scope: deriving edges no analyzer emitted (the shared-field join belongs upstream, to be raised against the analyzers); collapsing the per-language facades into one; the entrypoint vocabulary; unprefixing the Neo4j labels.
Release plan
| Leg |
Carries |
Ships |
| 1 |
Python — v2 models, ABC conformance, locate/locate_many, bodies, artifacts, entrypoints, external symbols, graph_schema_mismatch; drop C and four dead dependencies |
2.0.0-rc.1 |
| 2 |
Java — v2 models, ABC conformance, locate, bodies, artifacts; JGraphEdges and _CALLABLES_LOOKUP_TABLE retired |
2.0.0-rc.2 |
| 3 |
TypeScript + JavaScript — v2 models, prefixed (TS_/JS_) vocabulary, eager-init fix, locate, bodies, artifacts |
2.0.0-rc.3 |
| 4 |
Query sweep — selectors, diagnostics, backward_cone, slicing, taint, across all three at once |
2.0.0 |
Three concrete legs validate the ABC before anything shared is built on it; a query layer built against one backend would be an abstraction designed from one instance. The sweep is cheap because it comes last.
Signature compatibility is not graph compatibility. Each leg moves its language's Neo4j backend to the vocabulary the current analyzer emits, so a graph built by a matching generation starts working and one built by an older generation stops. That mismatch is silent today. The SDK will probe the attached graph on attach and raise graph_schema_mismatch naming expected labels, found labels and the generation each implies.
Definition of done
- All four legs shipped;
2.0.0 released.
- Each leg's facade passes its existing tests unmodified — the signature-stability gate.
- The twelve named tests in the spec's § 10 pass, each reproducing a recorded field failure: module-scope positions, unanalysed files, gaps between callable spans, untruncated bodies, external callee resolution,
graph_schema_mismatch, selector typos, unresolved-dispatch ledgers, and level_too_low.
pyproject.toml pins codeanalyzer-python 1.4.0 and codeanalyzer-typescript 1.2.0, bundles the codeanalyzer-java 3.0.1 JAR, and carries none of clang, libclang, tree-sitter-c, tree-sitter-go.
CLAUDE.md's supported-languages table drops C and describes the three families.
- A compatibility matrix — SDK version to analyzer generation to graph vocabulary — in each rc's release notes.
Caveats
Spec
python-sdk/docs/design/specs/2026-09-03-agent-facing-query-facade.md(codellm-devkit/python-sdk#318)Summary
CLDK 2.0 becomes a complete static-analysis front end — symbol table, source bodies, repository artifacts, call graph, control and data flow, addressing, reachability, slicing and taint — behind one surface serving both human and agent callers.
This epic is a sibling of #35, not a child. #35 is schema-v2 conformance across the analyzers; this is the SDK's query facade. The divergence register the design produced belongs to #35 and #36 and is filed there.
The premise that changed
#35's SDK children (codellm-devkit/python-sdk#309, #308) assumed the SDK could model the schema once — one
Node/Edge/Application. Measured against the three released analyzers that does not hold:typecallableEvery survivor is identity, containment or the graph. Not one shared semantic declaration field —
attributes/fields, modifiers, decorators, base classes, exported/async/abstract/ambient, records and overloads all diverge. So the sharing moves up a layer: from the model to the query shape. Each language keeps its own models; one generic ABC defines what can be asked.How the design was derived
By taking a production agent pipeline apart stage by stage and asking of each: what is this stage doing that is really a static-analysis query, and what signature would remove it? Five code-domain stages collapse — alert-to-sink mapping becomes
locate_many, index building disappears into a corrected enumeration, entrypoint cataloguing drops a sharded LLM substage because analyzers already emitis_entrypoint, the reachability cone becomes a server-side traversal that returns its unresolved-dispatch ledger, and narrative source-to-sink argument becomestaint()with per-edge provenance.That pipeline is one of several possible consumers. An IDE assistant, a code-review agent, a migration tool and a documentation generator decompose into the same four moves — locate, read, relate, prove.
Design decisions
Nine, locked in the spec. The load-bearing ones:
P/Nlabel-prefix pair, becausePY_DDG/TS_DDG/J_DDGare structurally identical), andresolve()(per-language).strict=True, and agraph_schema_mismatchprobe on attach.Explicitly out of scope: deriving edges no analyzer emitted (the shared-field join belongs upstream, to be raised against the analyzers); collapsing the per-language facades into one; the entrypoint vocabulary; unprefixing the Neo4j labels.
Release plan
locate/locate_many, bodies, artifacts, entrypoints, external symbols,graph_schema_mismatch; drop C and four dead dependencies2.0.0-rc.1locate, bodies, artifacts;JGraphEdgesand_CALLABLES_LOOKUP_TABLEretired2.0.0-rc.2TS_/JS_) vocabulary, eager-init fix,locate, bodies, artifacts2.0.0-rc.3backward_cone, slicing, taint, across all three at once2.0.0Three concrete legs validate the ABC before anything shared is built on it; a query layer built against one backend would be an abstraction designed from one instance. The sweep is cheap because it comes last.
Signature compatibility is not graph compatibility. Each leg moves its language's Neo4j backend to the vocabulary the current analyzer emits, so a graph built by a matching generation starts working and one built by an older generation stops. That mismatch is silent today. The SDK will probe the attached graph on attach and raise
graph_schema_mismatchnaming expected labels, found labels and the generation each implies.Definition of done
2.0.0released.graph_schema_mismatch, selector typos, unresolved-dispatch ledgers, andlevel_too_low.pyproject.tomlpinscodeanalyzer-python1.4.0 andcodeanalyzer-typescript1.2.0, bundles thecodeanalyzer-java3.0.1 JAR, and carries none ofclang,libclang,tree-sitter-c,tree-sitter-go.CLAUDE.md's supported-languages table drops C and describes the three families.Caveats
""and the templates stop caring — but until then the SDK is blessing something Projection-parity gate: prove analysis.json and the Neo4j graph agree #37 and Shared conformance suite: check every analyzer against canonical v2 #38 assume does not exist.