Response Format Reference — geodesia object, schema 1.0¶
Every chat response from G1-Proxy and G1-Proxy Light is a standard OpenAI (or Ollama) response with exactly one extra top-level key: geodesia. Nothing else is added. Everything Geodesia measured and decided for the turn lives inside that object.
This page is the authoritative reference for the object. It applies to:
| Endpoint | Format |
|---|---|
POST /v1/chat/completions | OpenAI chat completion / SSE chunks |
POST /v1/completions | OpenAI legacy text completion |
POST /api/chat | Ollama chat (JSON / NDJSON) |
POST /v1/glad/evaluate | OpenAI chat completion |
Versioning
schema_version is the version of this API schema, not of the product. It is always the first key of the object. It changes only when the shape of the object changes: a minor increase (1.1) adds optional fields; a major increase (2.0) removes or renames fields. Product releases (g1-proxy 0.4.2, …) do not change it.
Example — answer allowed¶
{
"id": "chatcmpl-geodesia-1790234386650",
"object": "chat.completion",
"created": 1790234393,
"model": "ministral3",
"choices": [{
"index": 0,
"message": { "role": "assistant", "content": "The capital of France is **Paris**." },
"logprobs": null,
"finish_reason": "stop"
}],
"usage": { "prompt_tokens": 1826, "completion_tokens": 23, "total_tokens": 1849 },
"geodesia": {
"schema_version": "1.0",
"event": "final",
"decision": "allowed",
"mode": "blocking",
"reason": null,
"axes": {
"halluc_context": {
"score": null, "threshold": 0.7551, "flagged": false, "available": false,
"role": "enforce", "unavailable_reason": "no context supplied"
},
"halluc_closedbook": {
"score": 0.4687, "threshold": 0.8555, "flagged": false, "available": true, "role": "advisory",
"details": {
"mean_surprisal": 0.369,
"method": "sledge(head⊕logprob+conformal)",
"fact_seeking": true,
"abstained": false,
"token_surprisal": [
{ "index": 0, "text": "The", "surprisal": 0.217, "start": 0, "end": 3 },
{ "index": 1, "text": " capital", "surprisal": 0.004, "start": 3, "end": 11 }
],
"uncertain_span": { "text": "anything else", "start": 19, "end": 21, "surprisal": 1.093 }
}
},
"prompt_safety": { "score": 0.0076, "threshold": 0.6377, "flagged": false, "available": true, "role": "enforce" },
"answer_safety": { "score": 0.021, "threshold": 0.7953, "flagged": false, "available": true, "role": "enforce" },
"jailbreak": {
"score": 0.3333, "threshold": 0.9864, "flagged": false, "available": true, "role": "enforce",
"details": { "head_score": 0.00059, "linear_probe_z": -9.6043 }
},
"rag_jailbreak": {
"score": null, "threshold": 0.5768, "flagged": false, "available": false,
"role": "advisory", "unavailable_reason": "no context supplied"
}
},
"additional_axes": {
"profanity": { "score": 0.026, "threshold": 0.7, "flagged": false, "available": true, "role": "advisory" },
"out_of_scope": { "score": 0.0678, "threshold": 0.9534, "flagged": false, "available": true, "role": "advisory" },
"prompt_complexity": {
"score": 0.0155, "threshold": 0.5, "flagged": false, "available": true,
"role": "classifier", "label": "simple"
}
},
"grounding": {
"available": true, "verdict": "grounded", "score": 0.7261, "risk": 0.4687,
"margin": -0.4521, "regime": "closed_book", "axis": "halluc_closedbook"
},
"pii": {
"enabled": true,
"input": { "count": 0, "by_type": {} },
"output": { "count": 0, "by_type": {} }
}
}
}
Example — prompt blocked¶
The answer is withheld: finish_reason is content_filter and the message carries the block notice.
{
"id": "geodesia-block-1790232549895",
"object": "chat.completion",
"created": 1790232549,
"model": "ministral3",
"choices": [{
"index": 0,
"message": { "role": "assistant", "content": "[Geodesia blocked — jailbreak (input)]" },
"finish_reason": "content_filter"
}],
"geodesia": {
"schema_version": "1.0",
"event": "final",
"decision": "blocked",
"mode": "blocking",
"reason": { "stage": "input", "axis": "jailbreak", "detail": null },
"axes": {
"prompt_safety": { "score": 0.9143, "threshold": 0.6377, "flagged": true, "available": true, "role": "enforce" },
"jailbreak": {
"score": 0.9981, "threshold": 0.9864, "flagged": true, "available": true, "role": "enforce",
"details": { "head_score": 0.993179, "linear_probe_z": 4.8373 }
}
}
}
}
(Other axes omitted for brevity; a real response lists every axis the deployment serves.)
Example — flagged in passthrough mode¶
With "mode": "passthrough" the real answer is returned and the violation is reported.
"geodesia": {
"schema_version": "1.0",
"event": "final",
"decision": "flagged",
"mode": "passthrough",
"reason": { "stage": "input", "axis": "jailbreak", "detail": null },
"axes": { "…": "…" }
}
Top-level fields¶
| Field | Type | Present | Description |
|---|---|---|---|
schema_version | string | always | Version of this schema ("1.0"). Always the first key. |
event | string | always | What this object reports: final, input_scan, progress or research. See Streaming. |
decision | string | final only | allowed, flagged, blocked or not_scanned. See Decision. |
mode | string | final only | Enforcement mode applied to this request: blocking or passthrough. |
reason | object | null | final only | Why the decision is not allowed; null when it is. See Reason. |
axes | object | when scored | Primary detection axes, keyed by axis name. See Axis object. |
additional_axes | object | when scored | Annotation-only axes (never block), same shape as axes. |
grounding | object | when scored | Fused grounding metric. See Grounding. |
thinking | object | thinking level ≥ 1 | Which detector tiers produced the verdict. See Thinking. |
pii | object | PII guard on | Personal data removed from the traffic, counts only. See PII. |
rag | object | knowledge base used | Retrieval sources and claim verification. See RAG. |
routing | object | complexity routing on | Which model answered and why. See Routing. |
context_judge | object | claim judge on | Per-claim grounding verdicts (advisory, uncalibrated). See Context judge. |
certificate | object | certificates on | Signed verdict certificate. See Certificate. |
reasoning_budget | object | reasoning upstream | max_tokens raised so a reasoning model can still answer. |
token_saving | object | token_saving switch on | {enabled, cached_prompt_tokens?, prompt_tokens?, cache_write_prompt_tokens?, prompt_cache_key?, upstream_closed_early?, closed_reason?}. cached_prompt_tokens is what the provider served from its prompt cache, present only when the provider reports it. prompt_cache_key is client or gateway (who set it), never the value. See Token & Cost Control. |
quota | object | quota exceeded | Plan or budget limit that rejected the request. |
tool_guard | object | tool guard blocked | MCP/tool-call findings that blocked the request. |
research | object | research events | One web-search progress event. |
Optional sections are omitted when they do not apply; they are never sent as empty placeholders.
Decision¶
decision | Content returned? | Meaning |
|---|---|---|
allowed | yes | No enforcing axis flagged. |
flagged | yes | A policy violation was detected but the content was delivered: the request ran in passthrough mode, or (streaming) the violation was found only after the answer had been streamed. |
blocked | no | Content was withheld. finish_reason is content_filter and the message carries a [Geodesia blocked — …] notice. |
not_scanned | yes | Scoring was switched off for this request (scan: false, or input and output validation disabled in the deployment). There is no verdict: reason is null and no axes are reported. |
A client that only needs "was this turn a violation?" can test decision in ("flagged", "blocked").
Reason¶
| Field | Values | Description |
|---|---|---|
stage | input, output, tools, quota | Where the decision was taken: the prompt, the generated answer, the tool/MCP pre-flight, or the plan/budget gate. |
axis | axis name | null | The axis that decided (highest-scoring flagged axis). null for tools and quota. |
detail | string | null | Extra context, e.g. "system prompt leak", "halted mid-stream", or the tool/quota message. |
Axis object¶
Every entry of axes and additional_axes has the same shape.
| Field | Type | Description |
|---|---|---|
score | number | null | Risk score in [0, 1] that the decision is based on. null when the axis could not be measured on this turn — never 0. |
threshold | number | null | Calibrated decision threshold. The axis is flagged when score exceeds it. |
flagged | boolean | The axis is over its threshold. For classifier axes this means "on the far side of the boundary", not "unsafe". |
available | boolean | false when the axis had nothing to read (no context, no logprobs, no answer yet). |
role | string | enforce (flag withholds content in blocking mode), advisory (flag is a warning, never a block), classifier (a label, not a risk). |
label | string | classifier axes only: the class, e.g. simple / complex. |
raw_score | number | Present when the displayed score was aligned to the final decision (fusion, guards, context verification); the model's own score before that step. |
hard_block | boolean | Present and true when an advisory axis withheld content anyway (e.g. closed-book hallucination above its extreme-confidence ceiling, or a verified system-prompt leak). |
unavailable_reason | string | Present when available is false: human-readable reason. |
details | object | Optional axis-specific evidence, see below. |
Axes¶
| Axis | Group | Role | Reads | Description |
|---|---|---|---|---|
prompt_safety | axes | enforce | prompt | Harmful request. |
jailbreak | axes | enforce | prompt | Jailbreak / instruction override / system-prompt extraction. |
answer_safety | axes | enforce | answer | Harmful answer content. |
halluc_context | axes | enforce | answer + context | Answer not supported by the supplied context (RAG faithfulness). |
halluc_closedbook | axes | advisory | answer + generator logprobs | Likely fabricated fact without context. Needs an upstream that exposes logprobs. |
rag_jailbreak | axes | advisory | context | Prompt injection hidden in retrieved/supplied context. |
halluc_context_joint | axes | advisory | answer + context | Joint claim–evidence verifier (when deployed). |
sysprompt_leak | axes | enforce | answer | Verbatim or fragmentary reproduction of the protected system prompt/tool descriptions. Present when system-prompt protection is on. |
profanity | additional_axes | advisory | prompt | Profane language. |
out_of_scope | additional_axes | advisory | prompt + declared scope | Request outside the application's declared scope. |
prompt_complexity | additional_axes | classifier | prompt | simple / complex; drives Model A/B routing. |
details fields¶
| Field | Axis | Description |
|---|---|---|
method | halluc_closedbook | Closed-book scoring method used. |
mean_surprisal | halluc_closedbook | Mean per-token surprisal of the answer (nats). |
fact_seeking | halluc_closedbook | The prompt asks for a fact (the axis gates on it). |
abstained | halluc_closedbook | The model declined to answer. |
token_surprisal | halluc_closedbook | Per-token surprisal: [{index, text, surprisal, start, end}] (start/end are token offsets). Only on the final event. |
uncertain_span | halluc_closedbook | The most uncertain span: {text, start, end, surprisal}. |
context_support | halluc_context | Lexical support of the answer by the context, in [0, 1]. |
suppressed_by | hallucination axes | Why a flag was withdrawn, e.g. context_support, rag_claim_verification, benign_tech_command. |
head_score | jailbreak | Score of the base detector head before the linear-probe combination. |
linear_probe_z | jailbreak | Logit of the linear intent probe combined with the head. |
pooled_score | prompt axes | Score before an input guard recovered a diluted or encoded attack. |
recovered_by | prompt axes | dilution_guard or decode_guard: the guard that recovered the attack. |
flag_kept_from_level_0 | any | At thinking level ≥ 1 the fused score alone would not flag; the flag comes from the level-0 verdict (fusion can only add flags, never remove them). |
verification_status | halluc_context_joint | Verifier status. |
leak_source | sysprompt_leak | Which protected field leaked, e.g. messages.system, tools.<name>.description. |
leak_kind | sysprompt_leak | whole_field, verbatim or fragments. |
leak_coverage | sysprompt_leak | Fraction of the protected field reproduced. |
shadow_mode | sysprompt_leak | true when the leak is only reported, not enforced. |
The protected text itself is never echoed back.
Grounding¶
A single grounding number fusing halluc_context and halluc_closedbook on each axis's own threshold.
| Field | Description |
|---|---|
available | false when neither axis could be measured. |
verdict | grounded, unsupported or not_measurable. |
score | [0, 1]; 1 = fully grounded, 0.5 = the calibrated operating point. Below 0.5 an axis crossed its threshold. |
risk | Raw probability of the deciding axis. |
margin | Normalised distance from the threshold (negative = safe side). |
regime | context, closed_book, both or not_measurable. |
axis | Axis that produced the number. |
deciding_axis | Axis whose flag produced unsupported (present only then). |
advisory_score | Score of the advisory (out-of-regime) axis when both were measured. |
reasons | Present when not measurable. |
Thinking¶
Present when the request ran at thinking level ≥ 1.
tiers_used lists the detector tiers that actually contributed (geodesia_g, geodesia_h, geodesia_a, native); it can be fewer than requested. escalated appears at level 1 only (cascade).
PII¶
"pii": { "enabled": true, "input": { "count": 1, "by_type": { "EMAIL": 1 } }, "output": { "count": 0, "by_type": {} } }
Counts per entity type only; detected values are never included.
RAG¶
{ "collection_id", "sources": [...], "source_count", "verification" } — present when the request used a knowledge base collection (rag request field). verification carries the claim-level check of the answer against the retrieved sources.
Routing¶
{ "enabled", "used_complex_model", "axis", "score", "threshold", "model" } — present when the application uses complexity routing. axis_missing: true means the complexity axis was unavailable and Model A answered.
Context judge¶
Present when enable_judge_for_context_hallucination is on. Advisory and uncalibrated: it never changes decision, axes or grounding.
"context_judge": {
"available": true, "calibrated": false, "score": 0.82, "threshold": 0.5, "flagged": true,
"verdict_counts": { "supported": 3, "unsupported": 1 },
"claims_total": 4, "claims_judged": 4, "truncated": false, "model": "Qwen3.5-9B", "seconds": 6.4,
"claims": [
{ "index": 3, "start": 120, "end": 171, "verdict": "unsupported", "quote": null,
"quote_verified": false, "confidence": 0.9, "risk": 0.82 }
]
}
verdict per claim is supported, contradicted, unsupported, no_claim or unjudged. When the judge cannot run, available is false, score is null and reason explains why.
Certificate¶
Present when certificates are enabled (GW_CERTIFICATE=on). A self-contained, optionally HMAC-signed record of the verdict, with the same axis vocabulary as above.
"certificate": {
"version": "geodesia-cert-3",
"verdict": "allowed",
"alpha": 0.05,
"brake_alpha": 0.025,
"axes": {
"jailbreak": {
"score": 0.3333, "available": true, "threshold": 0.9864, "flagged": false,
"role": "enforce", "tier": "primary", "alpha": 0.05,
"fpr_bound": "P(benign_score > threshold) <= 0.05", "threshold_method": "split-conformal"
}
},
"calibration": { "model": null, "bank_version": 0 },
"additional_axes": ["out_of_scope", "profanity", "prompt_complexity"],
"sig": "hmac-sha256:…"
}
verdict is the policy verdict: blocked when an axis that withholds content in blocking mode flagged, independently of the request's mode. In passthrough a turn can therefore carry decision: "flagged" with certificate.verdict: "blocked" — the certificate attests what the policy would do, decision what the gateway did. sig is null when no signing key is configured.
Streaming¶
With "stream": true the response is a sequence of SSE chunks. Each chunk that carries Geodesia information has a geodesia object with an event:
event | When | Contains |
|---|---|---|
input_scan | As soon as a detector tier has scored the prompt — once per tier (before or while the answer streams) | axes / additional_axes for the prompt axes, thinking.tiers_used so far. No decision. |
research | Web search only, before the answer | research: one progress event (search_started, page_found, page_read, page_blocked, page_skipped, search_done, search_error). page_read / page_blocked carry axes: {<axis>: {score, threshold, flagged}}; page_blocked also reason and axis (the deciding axis). |
progress | Periodic re-scoring during generation, and once per tier while the finished answer is scored | axes with the current scores (thinking.tiers_used on per-tier events). No decision. |
final | Last chunk (with finish_reason) | The complete verdict, same shape as the non-streaming object. |
Rules for clients:
Realtime tier scores. At thinking level ≥ 1 the prompt is scored by several detector tiers in sequence (Geodesia-G, then Geodesia-H, then Geodesia-A). The stream opens immediately and sends one input_scan per tier as soon as that tier has scored — Geodesia-G typically within tens of milliseconds, before the slower tiers and before the model's first token. Each input_scan carries thinking: {level, tiers_used} and replaces the previous one; the last one lists every tier that contributed. The same happens on the answer: one progress event per tier before final. In blocking mode the model is called only after the complete prompt verdict; in passthrough mode answer tokens and tier scores interleave.
- Take the verdict only from the
finalevent.input_scanandprogressare informational; a later one supersedes an earlier one. - If the answer is halted mid-stream, the
finalevent hasdecision: "blocked",reason.stage: "output"and the chunk'sfinish_reasoniscontent_filter. - A violation found after the answer was fully streamed is reported as
decision: "flagged"(the text was already delivered). - A request with
web_search: trueis always answered as a stream (researchevents first), whateverstreamsays. - A quota refusal (
reason.stage: "quota") ends withfinish_reason: "stop"; content and tool-guard blocks end withcontent_filter. Every block is delivered in the format the client asked for (SSE / NDJSON when streaming).
data: {"id":"chatcmpl-geodesia-…","object":"chat.completion.chunk","choices":[{"index":0,"delta":{},"finish_reason":null}],
"geodesia":{"schema_version":"1.0","event":"input_scan","axes":{"prompt_safety":{…},"jailbreak":{…}}}}
data: {"id":"chatcmpl-geodesia-…","object":"chat.completion.chunk","choices":[{"index":0,"delta":{"content":"Waves"},"finish_reason":null}]}
…
data: {"id":"chatcmpl-geodesia-…","object":"chat.completion.chunk","choices":[{"index":0,"delta":{},"finish_reason":"stop"}],
"geodesia":{"schema_version":"1.0","event":"final","decision":"allowed","mode":"blocking","reason":null,"axes":{…}}}
data: [DONE]
Ollama streaming (/api/chat, NDJSON) carries the same objects on its frames.
Request fields¶
Geodesia-specific request fields (all optional, never forwarded to the upstream model):
| Field | Type | Description |
|---|---|---|
mode | string | block / blocking or passthrough. Overrides the deployment default for this request. |
thinking_level | integer | 0–3. Default 2. See Thinking Levels. |
context | string | Grounding document for halluc_context (also injected into the upstream prompt). |
rag | object | { "collection_id": "…" } — retrieve context from a knowledge base. |
pii_guard | boolean | Turn the PII guard on/off for this request. |
scan | boolean | false bypasses all scoring and blocking for this request; the response carries decision: "not_scanned". When the gateway has GW_API_TOKEN set, only a caller presenting that token may do this (others get 403); requests relayed by G1-Studio carry it. Never let application code set it from user input. |
axes | array | string | Restrict the reported axes. Enforcing axes are always scored. |
threshold_overrides | object | Per-axis thresholds, e.g. { "jailbreak": 0.9 }. |
domain | string | Closed-book calibration domain (general, legal, …). |
web_search | boolean | Live web search with per-page screening. |
enable_judge_for_context_hallucination | boolean | Per-claim judge (context_judge). |
application_id / session_id | string | Application routing and conversation grouping. |
Migrating from the pre-1.0 payload¶
Before schema 1.0 the gateway added several top-level keys and used internal names. They are gone; there is no compatibility layer.
| Before | Schema 1.0 |
|---|---|
top-level glad_decision (passed / blocked) | geodesia.decision (allowed / flagged / blocked) |
top-level glad_mode | geodesia.mode |
top-level glad_scores.safety_decision_rule, geodesia.flagged_axis | geodesia.reason.axis |
top-level geodesia_research (stream) | geodesia.research with event: "research" |
geodesia.axis_energy | geodesia.axes |
axis p_detector / flag / verdict | score / flagged / label |
axis p_model, p_detector_raw | raw_score |
axis active | available |
axis p_pozzi, lin_z | details.head_score, details.linear_probe_z |
axis combinato, guardia_*, p_v5/p_v6/p_v7, sledge_*, prem_*, tier | removed (internal) |
closedbook_method, lsc_span, token_surprisal[].i/.s | details.method, details.uncertain_span, details.token_surprisal[].index/.surprisal |
geodesia.dominant_axis, geodesia.brake, geodesia.input | removed (use decision and reason) |
axis tier | removed: the group (axes / additional_axes) says it; still present inside the certificate |
energy_unit, energy_dHmax_joule, axis delta_E_joule / p_energy (debug payload, GW_CLEAN_PAYLOAD=0) | removed |
dilution_guard / decode_guard blocks, axis dilution_recovered / decode_recovered | details.pooled_score + details.recovered_by |
context_judge.flag, claims[].i, claims[].span, claims_truncated, advisory, score_kind | context_judge.flagged, claims[].index, claims[].start/end, truncated (advisory is implied) |
research page axes.{p, thr, flag}, dominant | axes.{score, threshold, flagged}, axis |
geodesia.sysprompt_leak | geodesia.axes.sysprompt_leak (the protected text is no longer echoed) |
geodesia.thinking_level, thinking_tiers_used, thinking_escalated | geodesia.thinking.level, .tiers_used, .escalated |
tier names glad_g, glad_h, glad_a, tier1_native | geodesia_g, geodesia_h, geodesia_a, native |
geodesia.pii_guard (by_label) | geodesia.pii (by_type) |
geodesia.ragionamento | geodesia.reasoning_budget |
geodesia.entitlement + glad_decision: "quota_exceeded" | geodesia.quota + decision: "blocked", reason.stage: "quota" |
geodesia.mcp | geodesia.tool_guard |
rag.n_sources | rag.source_count |
certificate glad-cert-2 (p_detector, flag, verdict, p_model, thr_kind, calib) | geodesia-cert-3 (score, flagged, label, raw_score, threshold_method, calibration) |
request glad_mode, glad_thinking_level, glad_scan, glad_pii_guard, glad_axes, glad_enable_judge_for_context_hallucination | mode, thinking_level, scan, pii_guard, axes, enable_judge_for_context_hallucination |