Runtime Deployment
Source mirrored from
seocho/docs/RUNTIME_DEPLOYMENT.md
Goal: one successful local run in under 5 minutes.
If you only read one runtime document first, read this one.
This page is the onboarding path. Use it for first success and local product verification, not for benchmark claims.
If you want the Python SDK path immediately, continue with
/docs/python_sdk/.
If you want the bring-your-own-data path immediately, continue with
/docs/apply_your_data/.
If you are measuring quality or latency, continue with docs/BENCHMARKS.md.
What This Page Gives You
Section titled “What This Page Gives You”| Need | Read | Success looks like |
|---|---|---|
| start the local runtime | Setup -> Start the Runtime | docker compose ps shows the core services |
| choose the right execution path | Execution Modes Matter | you know local SDK vs runtime semantic vs debate |
| verify through the UI | First Success: UI Path | one ingest and one semantic question work |
| verify through the API | First Success: Direct API Path | curl returns route and response fields |
| verify through Python | First Success: Python SDK Path | ask_response(...) returns support metadata |
| debug local files | Know Where Your Files Go | you can find graph data, artifacts, and traces |
Default path: use semantic graph QA first. Use react/debate only when you explicitly need agentic tool use or multi-agent comparison.
1. Prerequisites
Section titled “1. Prerequisites”- Docker and Docker Compose
OPENAI_API_KEYrecommendedcurlandjqfor optional API checks
Without OPENAI_API_KEY, SEOCHO can still boot in local fallback mode for
basic verification.
Important:
uv pip install seochoalone does not provision DozerDB/Neo4j for you.- local runtime success still depends on the graph backend being reachable.
make upstarts the core local stack, not every legacy service in the repo.make uprebuilds an image-backedextraction-service, solocalhost:8001reflects a known source snapshot instead of a dirty bind-mounted checkout.
1.1 Execution Modes Matter
Section titled “1.1 Execution Modes Matter”SEOCHO exposes multiple query surfaces and they do not all use the same engine.
| Surface | Execution path | Use this when |
|---|---|---|
Seocho.local(...).ask(...) | local SDK query path | you want a serverless ontology-first local run |
Seocho(base_url=...).ask(...) | runtime primary query facade | you want one public surface that auto-routes to chat or semantic graph QA |
client.semantic(...) | runtime advanced semantic graph QA | you want direct control of the semantic lane for debugging or expert use |
client.react(...) | runtime router agent | you want provider-native reasoning plus tool use |
client.advanced(...) / client.debate(...) | runtime debate orchestrator | you want explicit multi-agent comparison/synthesis |
Important implications:
- local
ask()is not the same benchmark target as runtimereact()oradvanced() reasoning_mode+repair_budgetbelong to the semantic pathmax_steps+tool_budgetbelong to the runtime agentic paths- if you are comparing providers fairly on tool use, do it on runtime
react/debate, not localask()
2. Setup
Section titled “2. Setup”git clone https://github.com/tteon/seocho.gitcd seochomake setup-env3. Start the Runtime
Section titled “3. Start the Runtime”make updocker compose psIf you explicitly want a live bind-mounted edit loop instead:
make up-liveOr through the local CLI (from a clone):
uv sync --extra devuv run seocho servePublished-package local engine path:
uv pip install "seocho[local]"The default core stack is:
neo4jextraction-serviceevaluation-interface
Expected local surfaces:
- Platform UI:
http://localhost:8501 - Backend API docs:
http://localhost:8001/docs - DozerDB browser:
http://localhost:7474
3.1 Isolated Per-Worktree Runtime (multi-instance)
Section titled “3.1 Isolated Per-Worktree Runtime (multi-instance)”When more than one worktree (or agent) needs to drive the runtime at the same
time, boot an isolated instance instead of a second full stack. SEOCHO
follows a single-neo4j, multi-database model: the heavyweight graph backend
started by make up stays shared, while each instance gets its own
ephemeral logical database, its own app-tier containers, and offset ports.
make up # shared stack once (neo4j + default app tier)make up INSTANCE=alice # isolated app tier: offset ports + ephemeral DBmake up INSTANCE=bob # a second, concurrent, non-colliding instanceEach instance is derived deterministically from its id (src/seocho/instance.py):
| id | API port | UI port | ephemeral DB | compose project |
|---|---|---|---|---|
alice | 8880 | 9180 | wt522b276a356b | seocho-alice |
bob | 8890 | 9190 | wt48181acd22b3 | seocho-bob |
The ephemeral database name is validated against the runtime contract
(^[a-z][a-z0-9]{2,62}$) before it is created on the shared neo4j. Teardown
removes only that instance’s app tier and drops only its logical
database — the shared neo4j and every other instance are untouched:
make down INSTANCE=alice # docker compose down (alice) + DROP DATABASE wt522…make down # stop the shared stackEquivalent SDK CLI form (and --dry-run to preview without docker):
seocho serve --instance aliceseocho stop --instance aliceseocho serve --instance alice --dry-run --json # inspect ports/DB/commandApp-tier ports are hash-derived over 200 slots, so two distinct ids can in rare
cases land on the same slot (P(collision) ≈ 1% at 2 worktrees, ≈ 14% at 8);
InstanceLayout.collides_with(...) detects this. A port-slot collision is an
availability concern only — data isolation is unaffected because the data
plane routes by the ephemeral database key, which is derived independently of
the port slot (a 48-bit space; collisions negligible). This is validated in
scripts/experiments/isolation_experiment.py. Pick distinct short ids per
worktree (a branch name or tenant id works well).
4. First Success: UI Path
Section titled “4. First Success: UI Path”- Open
http://localhost:8501 - Use the ingest panel or click the sample flow
- Ask a semantic question
The default product path is:
- ingest data
- run semantic retrieval
- use bounded repair only when needed
- reserve debate for explicit advanced use
5. First Success: Direct API Path
Section titled “5. First Success: Direct API Path”Ingest two records:
curl -sS -X POST http://localhost:8001/platform/ingest/raw \ -H "Content-Type: application/json" \ -d '{ "workspace_id": "default", "target_database": "kgruntime", "records": [ {"id": "r1", "content": "ACME acquired Beta in 2024."}, {"id": "r2", "content": "Beta provides risk analytics to ACME."} ] }' | jq .Ask through the semantic endpoint:
curl -sS -X POST http://localhost:8001/run_agent_semantic \ -H "Content-Type: application/json" \ -d '{ "workspace_id": "default", "query": "What is ACME related to?", "databases": ["kgruntime"], "query_mode": "graph_cot", "reasoning_mode": true, "repair_budget": 2 }' | jq '{route, response, reasoning: .semantic_context.reasoning}'6. First Success: Python SDK Path
Section titled “6. First Success: Python SDK Path”from seocho import Seocho
client = Seocho(base_url="http://localhost:8001", workspace_id="default")
client.raw_ingest( [ {"id": "r1", "content": "ACME acquired Beta in 2024."}, {"id": "r2", "content": "Beta provides risk analytics to ACME."}, ], target_database="kgruntime",)
receipt = client.ask_response( "What is ACME related to?", databases=["kgruntime"], cot_mode=True, reasoning_mode=True, repair_budget=2,)
print(receipt.response)print(receipt.runtime_mode)print(receipt.semantic_context["reasoning"])print(receipt.support.status)print(receipt.strategy.next_mode_hint)print(receipt.graph_cot["guardrail_verdict"]["decision"])
recent = client.semantic_runs(limit=5, route="lpg")print(recent[0].run_id)7. Use React Or Debate Only As Explicit Agentic Modes
Section titled “7. Use React Or Debate Only As Explicit Agentic Modes”If you want the runtime agent loop with bounded turns and tool usage:
react = client.react( "What changed in ACME's graph?", graph_ids=["kgruntime"], max_steps=6, tool_budget=2,)
print(react.response)This is the correct surface for provider-native reasoning/tool-use experiments.
8. Use Debate Only as an Advanced Mode
Section titled “8. Use Debate Only as an Advanced Mode”If you explicitly want cross-graph comparison:
advanced = client.advanced( "Compare what each graph knows about ACME.", graph_ids=["kgnormal", "kgfibo"], max_steps=8, tool_budget=3,)
print(advanced.debate_state)Stay on the semantic path first. Inspect semantic.support, semantic.strategy,
and semantic.evidence before reaching for debate.
9. Inspect Runtime Semantic History
Section titled “9. Inspect Runtime Semantic History”curl -sS "http://localhost:8001/semantic/runs?workspace_id=default&limit=5&route=lpg" | jq .10. Validate the Runtime
Section titled “10. Validate the Runtime”make e2e-smoke11. Keep One Ontology Contract
Section titled “11. Keep One Ontology Contract”If you author locally with schema.jsonld but ingest/query through the runtime,
do not maintain a second hand-written runtime payload. Use the same ontology to
build runtime-safe artifacts:
from seocho import Ontology, Seocho
ontology = Ontology.load("schema.jsonld")client = Seocho(ontology=ontology)
artifacts = client.approved_artifacts_from_ontology()prompt_context = client.prompt_context_from_ontology()Ontology.load(...) also accepts .ttl, so local authoring and governance can
start from Turtle directly.
If you want the explicit local engine instead of Seocho.local(...), construct
Seocho(ontology=..., graph_store=..., llm=...). All three are required to
activate in-process extraction and query execution.
For ontology version review before rollout:
seocho ontology check --schema schema.ttlseocho ontology diff --left schema_v1.ttl --right schema_v2.ttlseocho ontology report --schema schema_v2.ttl --output outputs/ontology_report.json12. Troubleshooting
Section titled “12. Troubleshooting”Check service state:
docker compose psdocker compose logs --tail=200 extraction-servicedocker compose logs --tail=200 evaluation-interfaceCommon issues:
OPENAI_API_KEYmissing or placeholder only- port collision on
8001,8501,7474, or7687 - graph database not ready yet
13. Know Where Your Files Go
Section titled “13. Know Where Your Files Go”The main local locations are:
- ontology file: usually
schema.jsonld - local graph data:
data/neo4j/ - semantic artifacts:
outputs/semantic_artifacts/ - rule profile registry:
outputs/rule_profiles/rule_profiles.db - semantic run metadata:
outputs/semantic_metadata/ - JSONL tracing: path from
SEOCHO_TRACE_JSONL_PATH
See FILES_AND_ARTIFACTS.md for the full map and inspection commands.
14. GOPTS Layer-1 PROFILE Oracle (ADR-0097, optional)
Section titled “14. GOPTS Layer-1 PROFILE Oracle (ADR-0097, optional)”The GOPTS cost-ranked Cypher emitter (ADR-0097) ships with a Layer-1
ranking-quality evaluation harness. The PROFILE oracle is opt-in — it
needs a live DozerDB to collect real db_hits per candidate plan.
Start DozerDB
docker compose up -d neo4j
# APOC Extended Arrow/Parquet support is installed idempotently before Neo4j.# To prefetch or refresh the pinned local plugin artifacts explicitly:make apoc-extended# wait a few seconds for the bolt port to come upLoad the FIBO-lite test corpus (idempotent, workspace-scoped):
NEO4J_PASSWORD=... uv run python -m scripts.eval.load_gopts_fibo_corpus# tear down: uv run python -m scripts.eval.load_gopts_fibo_corpus --teardownRun the live Layer-1 integration test:
NEO4J_PASSWORD=... uv run pytest -m integration_gopts tests/seocho/integration/The test session auto-loads the corpus on entry and tears it down on
exit. It skips cleanly when NEO4J_PASSWORD is unset or the bolt
endpoint is unreachable — CI without Docker stays green.
What the harness reports
For each fixture, the harness compares the GOPTS cost model’s ranking
to Neo4j PROFILE’s db_hits-driven ranking:
avg_top1_accuracy— fraction of fixtures where cost model picks PROFILE’s top plan.avg_ndcg_at_k— graded relevance fallback for near-ties.avg_kendall_tau— full-ranking agreement diagnostic.
On today’s catalog the harness reports 1.0 across all three — cost
model and PROFILE agree everywhere. That is a positive result: it
proves the cost-model defaults are well-calibrated against actual
db_hits. The harness exists to catch future calibration drift
when F8 (multi-plan execution) or richer alternatives change the
picks.
Skipped fixtures: the compose stack ships uniqueness constraints
on FinancialMetric.name (and similar metric subclasses). The Layer-1
live runner filters out fixtures 05/06 (finance_metric_lookup,
finance_metric_delta) because they need multi-year metric rows that
the constraint forbids loading. Mock-oracle Layer-1
(uv run pytest tests/seocho/test_gopts_ranking.py) still covers them.
GOpt-inspired slow-query audit loop
Section titled “GOpt-inspired slow-query audit loop”SEOCHO does not claim to embed the GOpt optimizer. It adopts the measurable operational loop described by the Neo4j/GOpt integration work:
- run a semantics-preserving baseline and record p50/p95 plus result hashes;
- capture
PROFILEand normalize operators withseocho.eval.plan_audit.audit_profile; - attribute delay to scans, variable expansion, eager aggregation, sorting, DB hits, or estimated/actual cardinality divergence;
- propose a schema-derived candidate using labelled indexed anchors, typed relationship triplets, early workspace filters, and bounded hop/row budgets;
- rerun on the same frozen records and use
compare_plans; - promote only when result hashes match and latency improves. A faster query with different results is never promotable.
Text2Cypher receives the same constraints as optimization_hints: preferred
label/index access, allowed (source)-[:TYPE]->(target) triplets, maximum four
hops, maximum 50 rows, and forbidden global-scan/unbounded/cartesian shapes.
validate_cypher remains the enforcement boundary; prompt hints are not a
security control by themselves.
PostgreSQL to graph projection format
Section titled “PostgreSQL to graph projection format”The authoritative outbox projection validates its payload before graph I/O.
Every node contains id, label, and properties workspace_id,
memory_sequence, and schema_version. Every relationship contains source,
target, type, source_label, target_label, and the same authoritative
properties. Endpoint labels must match their nodes. This lets DozerDB/Neo4j use
label-specific index seeks and keeps replay, provenance, and schema drift
auditable. See seocho.memory.validate_projection_format.
15. Read Next
Section titled “15. Read Next”/docs/python_sdk//docs/apply_your_data//docs/tutorial/docs/BEGINNER_PIPELINES_DEMO.md/docs/architecture//docs/runtime_architecture/