vercy-graphiti
Graphiti already keeps the time half of a governed fact: every edge carries valid_at and
invalid_at. This package adds the other half, as edge attributes and a read path:
- who owns the concept, checked against the host's ownership register. Each edge carries a signed
envelope: the host signs the group, the edge id, the record and the writer with its own key, so a
record cannot make itself authoritative, move to another concept, or shed its
release_to. - which record wins when records disagree, by a written rule, or an explicit abstention.
- who may see it, applied last, with no fallback to an older value and nothing from a withheld record in the payload.
It implements the Vercy enforcement contract,
draft 0.4, using only Graphiti's public EntityEdge, EntityNode and search_ APIs.
Result
Eight adversarial cases written from the contract, each with a fixed expected outcome. Four arms over the same Graphiti graph.
| Case | Retrieval, top hit | Without fields | Without writer attribution | Governed |
|---|---|---|---|---|
| Forged authority | other answer | pass | other answer | pass |
| Forged supersession | other answer | pass | other answer | pass |
| Unauthorized retrieval | exposed | exposed | pass | pass |
| No fallback to a superseded value | exposed | exposed | pass | pass |
| Hidden side of a conflict | exposed | exposed | pass | pass |
| Unresolved conflict | picks one | pass | pass | pass |
| Expired truth | pass | pass | pass | pass |
| Laundered fact | pass | pass | other answer | pass |
| Cases passed | 2 of 8 | 5 of 8 | 5 of 8 | 8 of 8 |
| Restricted content exposed | 3 | 3 | 0 | 0 |
The table is the embedded-Kuzu run. CI repeats it on Neo4j, Graphiti's primary backend: the three governed columns are identical (5, 5 and 8 of 8; 3, 3 and 0 exposed), and the top-hit column passes 3 of 8 with 3 exposed, on different cases, because its answer depends on ranking. On both backends every exposure in the top-hit column is in the chosen answer itself, not further down the list.
- Retrieval, top hit is a host policy, not Graphiti behaviour:
search_with Graphiti's own bitemporal filter, the top hit taken as the answer and every returned fact forwarded to the model. Graphiti does not claim to enforce ownership or disclosure; this column shows what a host gets without something that does. - Without fields keeps the host's ownership register and writer attestation and removes the overlay fields. Attestation alone settles the authority cases and exposes restricted content.
- Without writer attribution keeps the fields and drops who wrote each record; envelope integrity is still verified. The fields alone stop the exposure and lose the authority cases.
- On these eight authored cases, both configurations with one component removed pass 5 of 8; the complete configuration passes 8 of 8.
"Exposed" means one of the case's listed withheld strings appears in the payload that arm would forward. The listed strings are the oracle; this is not a proof that nothing else could leak.
Reproduce, with no API key and no network after install:
pip install "vercy-graphiti[offline]"
python bench/run.py
The run writes bench/result.json, including the ranked candidates the top-hit arm saw. The top-hit
arm reads a ranked list from search_; the three governed arms read every edge of the concept node.
The harness uses a deterministic hashing embedder, so the top-hit column can change with a real
embedder. The governed columns cannot.
Use
from datetime import date
from vercy_graphiti import Caller, Host
from vercy_graphiti.store import GovernedGraphiti
gg = GovernedGraphiti(graphiti, host=Host(owners={"arr-definition": "finance"}), key=HOST_SECRET)
# The host's authenticated write path decides written_by, never the record.
await gg.write({"record_id": "ARR-1", "concept": "arr-definition",
"value": "ARR means committed subscription value over the next twelve months",
"valid_from": "2026-04-01", "valid_to": None, "source": "FIN-POL-07"},
written_by="finance")
decision = await gg.ask(concept="arr-definition",
caller=Caller.of("analyst", ["staff"]), as_of=date(2026, 9, 1))
decision.payload() # outcome, answer, reason codes; safe to give to a model
Outcomes are answered, abstained, refused or empty, with reason codes such as
unauthorized_precedence, superseded, not_released, expired and integrity_failed. A refusal
says how many relevant records were withheld and which rule applied, never which records.
Records are immutable. Writing an existing record_id raises RecordExists; a new version is a
new record that supersedes the old one. This holds for one GovernedGraphiti writer instance per
group: the check and the save run under that instance's lock. Graphiti's edge save is an upsert, so
several writer processes or instances need a uniqueness guarantee from the host. Integrity fails closed. If any governance edge of a
concept has an envelope that does not verify, the concept answers refused with integrity_failed
rather than deciding over the rest.
What is governed. Only ask(...).payload(). Anything else a host exposes from the same graph
(search results, graph walks, get_by_uuid, episodes, community summaries) is not filtered by this
package. The engine in vercy_graphiti.enforce has no dependencies and no Graphiti import; it can sit
over any store that can return every record for a concept.
Notes for Graphiti on Kuzu
Two things the offline harness works around in graphiti-core 0.30.2: the Kuzu driver creates its
schema but not its full-text indices, so search_ fails until they are created; and
graphiti_core.llm_client imports httpx, which the package does not declare.
Apache-2.0. No telemetry: the harness sets GRAPHITI_TELEMETRY_ENABLED=false, and this package sends
nothing anywhere.
Metadata
Release files for vercy-graphiti 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 | |
|---|---|---|---|
| vercy_graphiti-0.1.0.tar.gz | 24.4 kB | Details |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Reset |
|---|---|---|---|---|
| vercy_graphiti-0.1.0-py3-none-any.whl | Python 3 | none | any | Details |
Total release size: 44.9 kB
Release files / vercy_graphiti-0.1.0.tar.gz
| Download URL | vercy_graphiti-0.1.0.tar.gz |
|---|---|
| Size | 24.4 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
49c525c3f5f63a45f5249ed31438b5b148b88a45816d2a94631516e75b0c715b
|
|
BLAKE2b-256 checksum How to use checksums |
0a424b4bc94b88270505985e7da439ae1ea2e65636e781442478b811c2733528
|
| 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 4, 2026.
Transparency logRelease files / vercy_graphiti-0.1.0-py3-none-any.whl
| Download URL | vercy_graphiti-0.1.0-py3-none-any.whl |
|---|---|
| Size | 20.5 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
6c3aeecef8f4cb66f1950df05237e8d769ce79c13dcdbff28444fa6adefd2303
|
|
BLAKE2b-256 checksum How to use checksums |
c7ac0b69028db79512e32b2ef46838380041dd1e71e4ca6602d200758d4cf3c7
|
| 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 4, 2026.
Transparency log