Local Server¶
TongGraph Server is an optional local or internal-network HTTP wrapper around the
embedded Graph API.
Install¶
or install the package with the server extra:
Minimal Config¶
host: 127.0.0.1
port: 8719
data_dir: .tonggraph
graphs:
shared_kg: shared.db
operations:
request_logging: true
request_timeout_seconds: 30
metrics: true
auth:
mode: token
users:
admin:
admin: true
token: admin-dev-token
graphs:
"*": write
alice:
token: alice-dev-token
graphs:
shared_kg: write
Start¶
Use With Curl¶
curl -H 'Authorization: Bearer admin-dev-token' http://127.0.0.1:8719/health
curl -X POST http://127.0.0.1:8719/admin/graphs \
-H 'Authorization: Bearer admin-dev-token' \
-H 'Content-Type: application/json' \
-d '{"name":"alice_memory","grants":{"alice":"write"}}'
curl -X POST http://127.0.0.1:8719/graphs/alice_memory/nodes \
-H 'Authorization: Bearer alice-dev-token' \
-H 'Content-Type: application/json' \
-d '{"external_id":"alice","labels":["Person"],"properties":{"name":"Alice"}}'
curl -X POST http://127.0.0.1:8719/graphs/alice_memory/query \
-H 'Authorization: Bearer alice-dev-token' \
-H 'Content-Type: application/json' \
-d '{"spec":{"match":[{"node":"n","external_id":"alice"}]}}'
curl -X POST http://127.0.0.1:8719/graphs/alice_memory/vector/embeddings/search-batch \
-H 'Authorization: Bearer alice-dev-token' \
-H 'Content-Type: application/json' \
-d '{"query_vectors":[[1.0,0.0],[0.5,0.5]],"limit":3}'
Python Client¶
from tonggraph.server.client import TongGraphClient
admin = TongGraphClient("http://127.0.0.1:8719", token="admin-dev-token")
admin.create_graph("alice_memory", grants={"alice": "write"})
client = TongGraphClient("http://127.0.0.1:8719", token="alice-dev-token")
graph = client.graph("alice_memory")
alice = graph.add_node(
"alice",
labels=["Person"],
properties={"name": "Alice"},
)
bob = graph.add_node("bob", labels=["Person"], properties={"name": "Bob"})
graph.add_edge(alice, bob, "KNOWS", properties={"weight": 1.0})
rows = graph.query({"match": [{"node": "n", "external_id": "alice"}]})
graph.create_vector_index("embeddings", 2, target="node", metric="cosine")
graph.upsert_vector("embeddings", alice, [1.0, 0.0])
graph.upsert_vector("embeddings", bob, [0.5, 0.5])
nearest = graph.search_vectors("embeddings", [[1.0, 0.0], [0.5, 0.5]], limit=1)
snapshot = graph.create_snapshot(ttl_seconds=600)
graph.add_node("later")
stable_count = snapshot.node_count()
print(rows, nearest, stable_count)
The first Python client returns JSON-compatible dict/list values. It does not
start the server process; point it at an already running tonggraph-server.
Logical Graph Namespaces¶
A physical server graph can be enabled as a logical-graph workspace. This lets clients store many agent graphs in one SQLite database without asking an administrator to create a new database file for each run.
Administrators can also create one dynamically:
A writer can then create logical graphs inside that workspace and use the
convenience proxy. The proxy automatically sends logical_graph_id on scoped
record, retrieval, query, traversal, compute, and snapshot calls.
workspace = client.graph("agent_workspace")
workspace.create_logical_graph("q450")
graph = workspace.logical("q450")
alice = graph.add_node("agent:q450:alice", labels=["Person"], properties={"name": "Alice"})
assert graph.node_count() == 1
HTTP clients pass logical_graph_id in the body for POST/PATCH APIs and as a
query parameter for GET/DELETE APIs:
curl -X POST http://127.0.0.1:8719/graphs/agent_workspace/nodes/batch \
-H 'Authorization: Bearer alice-dev-token' \
-H 'Content-Type: application/json' \
-d '{"logical_graph_id":"q450","records":[{"external_id":"agent:q450:alice","labels":["Person"],"properties":{"name":"Alice"}}]}'
curl 'http://127.0.0.1:8719/graphs/agent_workspace/nodes/count?logical_graph_id=q450' \
-H 'Authorization: Bearer alice-dev-token'
On logical-graph-enabled physical graphs, non-admin data API calls must include
logical_graph_id. Cypher is intentionally unavailable to non-admin users in
this mode because the server cannot safely inject a namespace filter into
arbitrary Cypher text.
Each scoped data request targets one logical graph. To search or query several logical graphs, loop in the client and merge the results explicitly:
workspace = client.graph("agent_workspace")
logical_graph_ids = ["q450", "q451", "q452"]
rows = []
for logical_graph_id in logical_graph_ids:
graph = workspace.logical(logical_graph_id)
for row in graph.search_vector("embeddings", query_vector, labels=["Belief"], limit=10):
row["logical_graph_id"] = logical_graph_id
rows.append(row)
rows.sort(key=lambda row: row.get("score", 0.0), reverse=True)
top_rows = rows[:10]
The same pattern applies to full-text search, retrieve_context(), structured
query(), counts, traversal, algorithms, and snapshots. The server does not run
cross-logical-graph traversal or ranking in one request; the caller decides how
to combine, sort, truncate, and deduplicate results from multiple logical graphs.
Bulk Ingest And Context Retrieval¶
Use batch writes for remote ingest, then combine text/vector candidates with graph
expansion through retrieve_context():
curl -X POST http://127.0.0.1:8719/graphs/alice_memory/nodes/batch \
-H 'Authorization: Bearer alice-dev-token' \
-H 'Content-Type: application/json' \
-d '{"records":[{"external_id":"doc:1","labels":["Document"],"properties":{"text":"graph memory retrieval"}},{"external_id":"chunk:1","labels":["Chunk"],"properties":{"text":"retrieval context"}}]}'
curl -X POST http://127.0.0.1:8719/graphs/alice_memory/edges/batch \
-H 'Authorization: Bearer alice-dev-token' \
-H 'Content-Type: application/json' \
-d '{"records":[{"source":0,"target":1,"edge_type":"HAS_CHUNK"}]}'
curl -X POST http://127.0.0.1:8719/graphs/alice_memory/retrieve/context \
-H 'Authorization: Bearer alice-dev-token' \
-H 'Content-Type: application/json' \
-d '{"text_index":"chunks","text_query":"graph retrieval","vector_index":"chunks","vector_query":[1.0,0.2,0.4],"radius":1,"limit":5}'
ids = graph.add_nodes([
{"external_id": "doc:1", "labels": ["Document"], "properties": {"text": "graph memory retrieval"}},
{"external_id": "chunk:1", "labels": ["Chunk"], "properties": {"text": "retrieval context"}},
])
graph.add_edges([{"source": ids[0], "target": ids[1], "edge_type": "HAS_CHUNK"}])
graph.create_fulltext_index("chunks", ["text"])
graph.create_vector_index("chunks", 3)
graph.upsert_vectors("chunks", {ids[0]: [1.0, 0.2, 0.4], ids[1]: [0.9, 0.1, 0.3]})
rows = graph.retrieve_context(
text_index="chunks",
text_query="graph retrieval",
vector_index="chunks",
vector_query=[1.0, 0.2, 0.4],
radius=1,
limit=5,
)
Controlled Import And Export¶
Server-side import paths are resolved under <data_dir>/imports/; export paths
are resolved under <data_dir>/exports/. Absolute paths and .. escapes are
rejected.
curl -X POST http://127.0.0.1:8719/graphs/alice_memory/import/nodes/jsonl \
-H 'Authorization: Bearer alice-dev-token' \
-H 'Content-Type: application/json' \
-d '{"path":"nodes.jsonl"}'
curl -X POST http://127.0.0.1:8719/graphs/alice_memory/export/nodes/jsonl \
-H 'Authorization: Bearer alice-dev-token' \
-H 'Content-Type: application/json' \
-d '{"path":"nodes.jsonl"}'
imported = graph.import_nodes_jsonl("nodes.jsonl")
graph.export_nodes_jsonl("nodes.jsonl")
graph.export_query_rows_jsonl("rows.jsonl", rows=[{"node": imported[0]}])
Traversal And Compute¶
curl 'http://127.0.0.1:8719/graphs/alice_memory/traversal/neighbors/0?direction=out' \
-H 'Authorization: Bearer alice-dev-token'
curl 'http://127.0.0.1:8719/graphs/alice_memory/algorithms/shortest-path?start=0&target=1&weight_property=weight' \
-H 'Authorization: Bearer alice-dev-token'
curl -X POST http://127.0.0.1:8719/graphs/alice_memory/compute/batch \
-H 'Authorization: Bearer alice-dev-token' \
-H 'Content-Type: application/json' \
-d '{"jobs":[{"op":"bfs","start":0,"max_depth":2},{"op":"pagerank","iterations":5}]}'
Snapshots¶
Create a read-only snapshot before later writes:
SNAPSHOT_ID=$(curl -s -X POST http://127.0.0.1:8719/graphs/alice_memory/snapshots \
-H 'Authorization: Bearer alice-dev-token' \
-H 'Content-Type: application/json' \
-d '{"ttl_seconds":600}' | python -c 'import json,sys; print(json.load(sys.stdin)["snapshot"]["snapshot_id"])')
curl http://127.0.0.1:8719/graphs/alice_memory/snapshots/$SNAPSHOT_ID/nodes/count \
-H 'Authorization: Bearer alice-dev-token'
curl -X POST http://127.0.0.1:8719/graphs/alice_memory/snapshots/$SNAPSHOT_ID/query \
-H 'Authorization: Bearer alice-dev-token' \
-H 'Content-Type: application/json' \
-d '{"spec":{"match":[{"node":"n","external_id":"alice"}]}}'
curl -X POST http://127.0.0.1:8719/graphs/alice_memory/snapshots/$SNAPSHOT_ID/fulltext/people/search \
-H 'Authorization: Bearer alice-dev-token' \
-H 'Content-Type: application/json' \
-d '{"query":"Alice","limit":3}'
curl -X POST http://127.0.0.1:8719/graphs/alice_memory/snapshots/$SNAPSHOT_ID/vector/embeddings/search \
-H 'Authorization: Bearer alice-dev-token' \
-H 'Content-Type: application/json' \
-d '{"query_vector":[1.0,0.0],"limit":3}'
curl -X POST http://127.0.0.1:8719/graphs/alice_memory/snapshots/$SNAPSHOT_ID/vector/embeddings/search-batch \
-H 'Authorization: Bearer alice-dev-token' \
-H 'Content-Type: application/json' \
-d '{"query_vectors":[[1.0,0.0],[0.5,0.5]],"limit":3}'
curl -X DELETE http://127.0.0.1:8719/graphs/alice_memory/snapshots/$SNAPSHOT_ID \
-H 'Authorization: Bearer alice-dev-token'
Snapshots are in-memory, read-only, TTL-bound resources. They are not persisted across server restart.
Inference¶
The Python client also exposes probability transfer and finite-discrete belief propagation endpoints:
from tonggraph.server.client import TongGraphClient
client = TongGraphClient("http://127.0.0.1:8719", token="alice-dev-token")
graph = client.graph("alice_memory")
source = graph.add_node("source")
target = graph.add_node("target")
graph.add_edge(source, target, "P", properties={"probability": "0.5"})
scores = graph.propagate({source: 1.0}, steps=1, edge_type="P")
parent = graph.add_variable("binary", owner_id=source, prior={"p": 0.6})
child = graph.add_variable("binary", owner_id=target)
graph.add_cpd(child, [parent], [0.9, 0.1, 0.2, 0.8])
result = graph.belief_propagation(
[child],
evidence={parent: "true"},
damping=0.0,
persist=True,
)
posterior = graph.posterior(child)
print(scores, result["beliefs"], posterior)
persist=false belief propagation is a read operation. persist=true stores the
posterior and trace, so it requires graph write access.
Auth Management¶
Administrators can create users, grant graph access, rotate tokens, and disable users without restarting the server:
curl -X POST http://127.0.0.1:8719/admin/users \
-H 'Authorization: Bearer admin-dev-token' \
-H 'Content-Type: application/json' \
-d '{"user_id":"bob","token":"bob-dev-token","graphs":{"alice_memory":"read"}}'
curl -X POST http://127.0.0.1:8719/admin/users/bob/token \
-H 'Authorization: Bearer admin-dev-token' \
-H 'Content-Type: application/json' \
-d '{}'
curl -X PATCH http://127.0.0.1:8719/admin/users/bob \
-H 'Authorization: Bearer admin-dev-token' \
-H 'Content-Type: application/json' \
-d '{"disabled":true}'
The generated token is returned only by the rotation response. User list and get
responses report has_token but do not expose token values. Dynamic users and
token overrides are stored in <data_dir>/server-state.json; do not commit real
tokens.
admin = TongGraphClient("http://127.0.0.1:8719", token="admin-dev-token")
admin.create_user("bob", token="bob-dev-token", graphs={"alice_memory": "read"})
rotated = admin.rotate_user_token("bob")
admin.update_user("bob", disabled=True)
Backup And Restore¶
Administrators can create local .tar.gz graph backups and restore them as new
or existing graphs:
curl -X POST http://127.0.0.1:8719/admin/graphs/alice_memory/backup \
-H 'Authorization: Bearer admin-dev-token' \
-H 'Content-Type: application/json' \
-d '{"note":"before migration"}'
curl http://127.0.0.1:8719/admin/backups \
-H 'Authorization: Bearer admin-dev-token'
curl -X POST http://127.0.0.1:8719/admin/backups/$BACKUP_ID/restore \
-H 'Authorization: Bearer admin-dev-token' \
-H 'Content-Type: application/json' \
-d '{"graph":"alice_memory_copy","grants":{"alice":"write"}}'
Backups are stored under <data_dir>/backups/ and include the graph SQLite file,
optional WAL/SHM files, .segments/ sidecar files, and graph metadata. In-memory
snapshots are not backed up.
admin = TongGraphClient("http://127.0.0.1:8719", token="admin-dev-token")
backup = admin.backup_graph("alice_memory", note="before migration")
restored = admin.restore_backup(
backup["backup_id"],
"alice_memory_copy",
grants={"alice": "write"},
)
admin.delete_backup(backup["backup_id"])
Bare-Metal Deployment¶
The repository includes deployment assets for a single-node local or internal network service:
deploy/tonggraph-server.yml
deploy/tonggraph-server.env.example
deploy/systemd/tonggraph-server.service
scripts/server/start.sh
scripts/server/health.sh
scripts/server/smoke.sh
Use the template config and environment file as a starting point. The template
binds to 127.0.0.1:8719 and reads tokens from environment variables. The
start script automatically loads deploy/tonggraph-server.env when it exists.
cp deploy/tonggraph-server.env.example deploy/tonggraph-server.env
# Edit deploy/tonggraph-server.env and set private token values.
./scripts/server/start.sh
The start script reads deploy/tonggraph-server.yml by default. Override it for
a copied production config:
TONGGRAPH_CONFIG=/etc/tonggraph/tonggraph-server.yml \
TONGGRAPH_HOST=127.0.0.1 \
TONGGRAPH_PORT=8719 \
./scripts/server/start.sh
Check health and run a minimal smoke test against an already running server:
TONGGRAPH_BASE_URL=http://127.0.0.1:8719 ./scripts/server/health.sh
TONGGRAPH_BASE_URL=http://127.0.0.1:8719 \
TONGGRAPH_ADMIN_TOKEN="$TONGGRAPH_ADMIN_TOKEN" \
./scripts/server/smoke.sh
For systemd, copy the config and env file to /etc/tonggraph/, install the
unit from deploy/systemd/tonggraph-server.service, then adjust
WorkingDirectory, ExecStart, and the service user for your installation
path.
sudo install -d /etc/tonggraph
sudo cp deploy/tonggraph-server.yml /etc/tonggraph/tonggraph-server.yml
sudo cp deploy/tonggraph-server.env.example /etc/tonggraph/tonggraph-server.env
sudo cp deploy/systemd/tonggraph-server.service /etc/systemd/system/tonggraph-server.service
sudo systemctl daemon-reload
sudo systemctl enable --now tonggraph-server
The deployment assets are intentionally bare-metal first. Docker, Compose, Kubernetes, TLS termination, and public-network hardening are left to later deployment work.
Operations¶
/metrics returns JSON request counters, latency totals, status and route
counts, uptime, and graph summaries. In token auth mode it requires an admin
token.
Vector Benchmark¶
Run the local exact vector benchmark when sizing server deployments:
uv run python -m tests.benchmark.gbench.vector \
--vectors 10000 \
--dimensions 128 \
--queries 20 \
--batch-size 8 \
--repeat 3 \
--output tests/benchmark/.gbench/results/vector-exact-10k.json
The benchmark reports embedded and HTTP server exact-search latency in JSON.
Generated results live under tests/benchmark/.gbench/, which is gitignored.
Current Boundaries¶
The current server is single-node and internal-network oriented. Snapshot resources are in-memory and expire by TTL. The server does not provide distributed storage, public multi-tenant hosting, Docker/Compose deployment assets, TLS termination, or fine-grained node and edge permissions.