langchain-gestaltdb
A graph-first LangChain integration for GestaltDB. The graph adapter, schema, ingestion, read-only-default agent toolkit, bounded typed-graph retriever, read-only claim status/provenance tools, and explicitly enabled claim writes are available. See the bootstrap epic and the technical debt register.
Install with an explicit embedded backend (Python 3.10–3.13):
pip install 'langchain-gestaltdb[lmdb]==0.1.0'
from langchain_gestaltdb import GestaltDBGraph
with GestaltDBGraph.create("my-graph", backend="lmdb") as graph:
rows = graph.query("MATCH (n) WHERE n.id = $id RETURN n", {"id": "alice"})
Wrapping an existing GraphDB borrows its handle; create/open own their handles.
Closing a borrowed wrapper leaves the original database open. Queries return
JSON-compatible dictionaries, including native nodes/edges and paths. Temporal
values use a $type tag plus exact fields; unsupported objects fail explicitly.
Low-level queries permit writes and propagate upstream errors unchanged: do not
give this API directly to an untrusted agent. Backend transaction/snapshot
guarantees are unchanged; there is no native async or execution timeout.
For parsed-AST write prevention, call graph.query_read_only(query, params).
It rejects mutations, schema changes, unknown syntax, and procedures by default,
including writes inside subqueries/UNION. Known read procedures may be explicitly
allowlisted by a trusted application. Query text is capped at 10000 characters.
Read-only is not protection against expensive reads: use process/resource
isolation for hostile workloads. Output caps are not execution timeouts.
Schema discovery
graph.get_schema returns stable JSON text; graph.get_structured_schema returns
a defensive copy. Both lazily cache inspection; call graph.refresh_schema()
after external writes. Defaults inspect 100 canonical nodes and 100 edges, plus
up to two endpoint nodes per edge. This bypasses stale secondary indexes.
For different bounds, wrap the database with
GestaltDBGraph(db, schema_node_limit=20, schema_edge_limit=20). Set a limit to
None only to explicitly allow exhaustive inspection, or supply a trusted
schema_override dictionary to avoid scans entirely. Sampling follows backend
key order, is not representative, and is not a coherent concurrent snapshot.
The schema includes truncation, property types, endpoint labels, index definitions,
and GestaltDB dialect guidance. It does not enforce a closed-world schema.
Graph-document ingestion
from langchain_core.documents import Document
from langchain_gestaltdb import GraphDocument, GraphNode, GraphRelationship
alice = GraphNode(id="alice", type="Person", properties={"name": "Alice"})
bob = GraphNode(id="bob", type="Person", properties={"name": "Bob"})
document = GraphDocument(
nodes=[alice, bob],
relationships=[GraphRelationship(source=alice, target=bob, type="KNOWS")],
source=Document(page_content="Alice knows Bob", metadata={"path": "example.txt"}),
)
# Inside the graph context above:
# graph.add_graph_documents([document], include_source=True)
Equivalent mappings/Pydantic graph models can be passed without installing
Community or Neo4j. This is structural compatibility, not a promise to inherit
their graph-store classes. Node labels union and incoming properties shallow-merge
over existing properties. Ordinary string IDs are unchanged; integer IDs use
__lc_int__:<integer>, and strings starting __lc_ are escaped to a hashed
__lc_str__: key, preventing integer/string/internal-source collisions.
Default relationship identity hashes endpoints, type, and normalized properties:
exact repeats collapse; distinct properties produce parallel edges. Supply
GraphRelationship.id to update properties of a particular edge or preserve
otherwise identical parallel edges. Explicit IDs cannot change endpoints/type.
Relationship properties['type'] must agree with the declared type if supplied.
With include_source=True, each source becomes a __LangChainSource__ node
with content/metadata and outgoing MENTIONS links. Document.id controls source
identity when supplied; otherwise content plus metadata is hashed. Sources are
not epistemic claims. Input is prevalidated, but writes are incremental and
not whole-import atomic: backend failures can leave partial data. The schema
cache is invalidated. JSON-safe property conversion follows query normalization.
Agent toolkit
from langchain_gestaltdb import GestaltDBToolkit
# Use inside a GestaltDBGraph context; toolkit/tools borrow the wrapper.
# schema_tool, query_tool = GestaltDBToolkit(graph=graph).get_tools()
# schema_tool.invoke({"refresh": True})
# query_tool.invoke({"query": "MATCH (n:Person) RETURN n.name AS name", "params": {}})
Tools implement LangChain BaseTool and accept tool-call messages. Query output
is JSON with records, returned_rows, and truncated. Defaults cap output at
100 rows/16000 characters; application configuration can lower those values.
An oversized row is omitted whole, preserving valid JSON. Oversized schema
returns an actionable error rather than silently dropping schema fields.
Only query and params are query inputs. Agents cannot enable writes, enlarge
limits, choose procedure permissions, or replace the graph via tool arguments.
Applications can explicitly construct GestaltDBToolkit(graph=graph, allow_writes=True);
that grants write capability and propagates upstream limitations (including TD-02).
Known read procedures may be explicitly enabled with allowed_procedures.
Tool-facing backend errors are redacted; low-level graph errors still propagate.
Treat graph/source text as untrusted data, not instructions. Read-only prevents
writes but does not bound execution cost, intermediate memory, or result collection.
LangChain's async tool adapter offloads synchronous work; no native async is claimed.
Bounded graph retrieval
from langchain_gestaltdb import GestaltDBRetriever, RetrievalHop
retriever = GestaltDBRetriever(
graph=graph, # must stay open for the retriever's lifetime
seed_label="Person", # indexed entity resolution
seed_property="name", # requires create_node_property_index("name")
hops=(RetrievalHop(edge_type="WORKS_FOR", direction="out", sample_size=5),),
)
# documents = retriever.invoke("Alice")
GestaltDBRetriever is a BaseRetriever, not a vector store. Configure exactly
one seed strategy: seed_ids (explicit), seed_resolver (a callable such as an
external vector-store lookup returning node IDs), or the indexed
seed_label/seed_property pair. Indexed resolution matches the query string
exactly against the indexed property value, so create that property index first;
non-string properties and fuzzy matching need a resolver or explicit seeds.
Absent seeds are skipped rather than fabricated, and an empty graph yields [].
Each returned Document preserves the entity id/labels/properties, the
normalized traversal paths (with edge_id, source_id, target_id,
edge_type, direction), distinct relationship_types, hops, and sources
(document_id plus metadata) linked through MENTIONS when
include_sources=True. Labels often carry the evidence text. Byte IDs are
decoded to strings and all metadata stays JSON-safe.
Traversal is bounded: at most 8 hops, sample_size of 1–100 per hop,
max_paths, max_entities, and max_content_chars. max_seeds bounds
resolution. result_truncated is a run-level flag for seed/path/entity caps,
copied onto every returned document. content_truncated and sources_truncated
describe per-document clipping; truncated is true if any of these flags is true.
Sampling intentionally selects a subset and does not set these flags. Output ordering is deterministic (seeds first, then
sorted IDs) but is not a relevance ranking, and no database ordering is
promised. The retriever only reads; it never writes or rebuilds indexes. Typed
adjacency remains usable during deferred edge ingestion; indexed seeding rejects
stale node-label/property indexes until explicitly rebuilt. External vector
search may supply seeds without making this package a vector store.
Caps bound returned entities and content, not database work or total metadata size. Resolvers, property normalization, and source lookup may still be costly.
Claim status and provenance tools
GestaltDB claims are sourced assertions or denials in a bitemporal epistemic
layer. They are not graph edges: they do not create current-state edges and
never appear in Cypher results or MATCH patterns. GestaltDBClaimToolkit
exposes read-only, bounded tools for them; writes require explicit opt-in (below).
from langchain_gestaltdb import GestaltDBClaimToolkit
# Use inside a GestaltDBGraph context; tools borrow the wrapper.
# status, search, explain = GestaltDBClaimToolkit(graph=graph).get_tools()
# status.invoke({"subject": "alice", "predicate": "WORKS_FOR", "object": "acme",
# "world": "reported", "valid_time": "2024-06-01T00:00:00Z"})
# explain.invoke({"claim_id": "claim:...", "valid_time": "2024-06-01T00:00:00Z"})
gestaltdb_claim_status reports a distinct supported, refuted, both or
unknown status with bounded supporting/refuting evidence under open-world
semantics. gestaltdb_claim_search lists claims by statement, subject,
predicate, object, agent, source, world or polarity. gestaltdb_claim_explanation
returns a bounded claim/rule derivation graph. Agent/source/world and polarity
filters plus valid time and system time (or through_commit) are supported.
Explanations preserve historical version/logical ids and explicit truncation
metadata. Only filters and times are agent inputs: the graph, caps
(max_evidence, max_claims, max_depth, max_nodes, max_edges,
max_chars) are application-controlled. Output caps bound returned
JSON, not execution time or memory.
Status and evidence use one claim resolution; the status is computed before
evidence truncation. Read-tool max_chars includes the complete response envelope.
If metadata alone cannot fit, the read tool returns a bounded JSON error with
truncated: true instead of silently clipping identities or times.
Search treats omitted or null object as any object; null-literal filtering is
not exposed by search (use status for a specific null-literal statement).
Omitting every indexed filter scans the full claim catalog.
Opt-in claim writes
Writing claims is explicitly opt-in. GestaltDBClaimToolkit(graph=graph, allow_writes=True) additionally returns append-only gestaltdb_claim_assert,
gestaltdb_claim_correct and gestaltdb_claim_retract tools; the default
toolkit never exposes them. Direct construction of a write tool also opts in.
Assertions require explicit agent, polarity, JSON provenance ({} if unavailable),
and fact-validity start. Corrections and retractions require an exact
supersedes_version_id; optional validity overrides use timezone-aware ISO-8601
instants with an exclusive end after the start. Agent authority and confidence
are never inferred. These tools grant write capability, not agent authentication
or per-claim authorization: trusted applications must enforce those policies.
Nothing is deleted. Fact validity (valid) stays separate from system knowledge
time (system_time); prior versions remain retrievable with historical horizons.
# write, correct and retract tools (opt-in):
# status, search, explain, assert_tool, correct_tool, retract_tool = \
# GestaltDBClaimToolkit(graph=graph, allow_writes=True).get_tools()
# assert_tool.invoke({"subject": "alice", "predicate": "WORKS_FOR", "object": "acme",
# "polarity": "positive", "agent": "source:hr",
# "provenance": {"document": "hr.csv"},
# "valid_from": "2024-01-01T00:00:00Z"})
# correct_tool.invoke({"claim_id": "claim:...", "supersedes_version_id": "uuid",
# "confidence": 0.9})
# retract_tool.invoke({"claim_id": "claim:...", "supersedes_version_id": "uuid",
# "reason": "employment ended", "valid_from": "2024-05-01T00:00:00Z"})
Successful writes return JSON with written: true and the new version. If the
full payload cannot fit max_chars (or cannot be serialized), the response is a
compact success receipt containing version_id, logical_id, and
truncated: true, not a failure. Do not retry a committed write merely because
its payload was omitted. Backend errors are redacted and may have uncertain
outcomes; reconcile stored state before retrying. Backend transaction guarantees
are unchanged, and an exact version target is not a latest-version compare-and-swap.
Development
Run the complete offline ingest → schema → tool-call → source-evidence example:
uv run --locked python examples/graph_agent.py
Run the offline claim status/contradiction/provenance example:
uv run --locked python examples/claim_agent.py
Run the offline opt-in claim-write example (fact validity vs system knowledge time):
uv run --locked python examples/claim_writes.py
No model, API key, database server, or network service is required. The scripted
tool call demonstrates the same ToolMessage dispatch used by LangChain agents;
it is not an LLM reasoning benchmark. See the API contract.
Use Python 3.10–3.13 (3.12 recommended) and uv:
uv sync --python 3.12 --locked --group dev --extra lmdb
uv run --locked ruff check .
uv run --locked ruff format --check .
uv run --locked mypy
uv run --locked pytest
uv build --no-sources
uv run --locked twine check dist/*
The committed lockfile makes development reproducible; the dependency ranges
in pyproject.toml describe the library's consumer contract. CI also exercises
minimum/latest direct dependencies, real deferred-index behavior, and additional
LevelDB/PyRex backend contracts on Linux/Python 3.12. All tests run offline and
enforce at least 90% package coverage in the Python matrix.
GestaltDB's base install has no storage backend. Development uses the lmdb
extra and JSON serialization explicitly; leveldb and rocksdb extras are
available for future backend coverage. There is no silent backend fallback.
Agent-oriented upstream documentation
uv run --locked python -m gestaltdb.agent_docs list
uv run --locked python -m gestaltdb.agent_docs get quickstart
uv run --locked python -m gestaltdb.agent_docs get cypher --examples
uv run --locked python -m gestaltdb.agent_docs search sample_typed_subgraph
Contributing and releases
Read AGENTS.md for branch, PR, CI, and release policy. Work goes
through issue-scoped branches and PRs; main is protected. Releases use explicit
maintainer approval and PyPI Trusted Publishing via publish.yml; merging alone
never publishes. See release notes and the
release runbook. This repository retains its Apache-2.0 license.
Scaffold provenance
The bootstrap was informed by the langchain-cli==0.0.37 integration template,
generated selectively with Python 3.12 into a disposable staging directory.
The old Poetry/Core 0.3 configuration was replaced with PEP 621, uv, Hatchling,
and Core 1.x. No provider stubs or template license were copied into this project.
Metadata
Release files for langchain-gestaltdb 0.1.0
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | Provenance | Actions |
|---|---|---|---|---|
| langchain_gestaltdb-0.1.0.tar.gz | 66.3 kB | With provenance | Details for langchain_gestaltdb-0.1.0.tar.gz |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Actions Reset |
|---|---|---|---|---|
| langchain_gestaltdb-0.1.0-py3-none-any.whl | Python 3 | none | any | Details for langchain_gestaltdb-0.1.0-py3-none-any.whl |
Total release size: 103.5 kB
Release files / langchain_gestaltdb-0.1.0.tar.gz
| Download URL | langchain_gestaltdb-0.1.0.tar.gz |
|---|---|
| Size | 66.3 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
e84ba7146a2213322fa624d829809fb8e160d018ae3cfe8cc5896ae98863ad0d
|
|
BLAKE2b-256 checksum How to use checksums |
70e6368c6d7ec30c34ac3a5a68f35920e05e630bc6e1be825859714e197d8c5b
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.14
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Oct 18, 2026.
Transparency logRelease files / langchain_gestaltdb-0.1.0-py3-none-any.whl
| Download URL | langchain_gestaltdb-0.1.0-py3-none-any.whl |
|---|---|
| Size | 37.3 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
1a2aa6910b16a01e84f7e7d2323c2c47ca6851d965b4892c611b3b827952d76c
|
|
BLAKE2b-256 checksum How to use checksums |
8480b9452d8c4097c3a2caddb1fdbaee0bcbd6838d0ee55ab961d37852744ebe
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.14
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Oct 18, 2026.
Transparency logSecurity for langchain-gestaltdb 0.1.0
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
Full provenance
All 2 files in this release have been uploaded with provenance metadata.
Attestation for langchain_gestaltdb-0.1.0.tar.gz
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Oct 18, 2026.
Transparency logA note on package security
PyPI is a repository for distributing packages, not a service for auditing them. While third-party security researchers and our staff do their best to detect and remove malicious packages, PyPI cannot guarantee the quality, security or suitability of any given package.
To learn more about protecting yourself from malicious actors, visit our package security documentation.