Advertise OpenAI adapter CultNet contracts

This commit is contained in:
Meta Vens 2026-05-13 05:11:20 +01:00
commit 85e6b4dd61
12 changed files with 352 additions and 7 deletions

View file

@ -32,6 +32,10 @@ pub const RUNTIME_SESSION_TYPE: &str = "epiphany.runtime.session";
pub const RUNTIME_JOB_TYPE: &str = "epiphany.runtime.job";
pub const RUNTIME_JOB_RESULT_TYPE: &str = "epiphany.runtime.job_result";
pub const RUNTIME_EVENT_TYPE: &str = "epiphany.runtime.event";
pub const OPENAI_ADAPTER_STATUS_TYPE: &str = "epiphany.openai_adapter_status.v0";
pub const OPENAI_MODEL_REQUEST_TYPE: &str = "epiphany.openai_model_request.v0";
pub const OPENAI_MODEL_STREAM_EVENT_TYPE: &str = "epiphany.openai_model_stream_event.v0";
pub const OPENAI_MODEL_RECEIPT_TYPE: &str = "epiphany.openai_model_receipt.v0";
pub const SURFACE_SCENE_TYPE: &str = "epiphany.surface.scene";
pub const SURFACE_FRESHNESS_TYPE: &str = "epiphany.surface.freshness";
pub const SURFACE_CONTEXT_TYPE: &str = "epiphany.surface.context";
@ -53,6 +57,10 @@ pub const SURFACE_RIDER_BRIDGE_TYPE: &str = "epiphany.surface.rider_bridge";
pub const SURFACE_UNITY_BRIDGE_TYPE: &str = "epiphany.surface.unity_bridge";
pub const RUNTIME_IDENTITY_KEY: &str = "self";
pub const RUNTIME_SPINE_SCHEMA_VERSION: &str = "epiphany.runtime_spine.v0";
pub const OPENAI_ADAPTER_STATUS_SCHEMA_VERSION: &str = "epiphany.openai_adapter_status.v0";
pub const OPENAI_MODEL_REQUEST_SCHEMA_VERSION: &str = "epiphany.openai_model_request.v0";
pub const OPENAI_MODEL_STREAM_EVENT_SCHEMA_VERSION: &str = "epiphany.openai_model_stream_event.v0";
pub const OPENAI_MODEL_RECEIPT_SCHEMA_VERSION: &str = "epiphany.openai_model_receipt.v0";
pub const SCENE_SURFACE_SCHEMA_VERSION: &str = "epiphany.scene_surface.v0";
pub const FRESHNESS_SURFACE_SCHEMA_VERSION: &str = "epiphany.freshness_surface.v0";
pub const CONTEXT_SURFACE_SCHEMA_VERSION: &str = "epiphany.context_surface.v0";
@ -942,6 +950,50 @@ fn epiphany_mutation_contracts() -> Vec<CultNetDocumentMutationContract> {
vec![],
vec!["Runtime events are append-only projections for inspection."],
),
mutation_contract(
OPENAI_ADAPTER_STATUS_TYPE,
OPENAI_ADAPTER_STATUS_SCHEMA_VERSION,
vec![CultNetDocumentOperation::Snapshot],
CultNetMutationAuthority::ReadOnly,
vec![],
vec![],
vec![
"OpenAI adapter status is a typed document; it is not a Codex app-server status blob.",
],
),
mutation_contract(
OPENAI_MODEL_REQUEST_TYPE,
OPENAI_MODEL_REQUEST_SCHEMA_VERSION,
vec![
CultNetDocumentOperation::IntentSubmit,
CultNetDocumentOperation::ReceiptWatch,
],
CultNetMutationAuthority::Coordinator,
vec![OPENAI_MODEL_REQUEST_TYPE],
vec![OPENAI_MODEL_STREAM_EVENT_TYPE, OPENAI_MODEL_RECEIPT_TYPE],
vec![
"Model turns enter through typed Epiphany request documents and return typed stream events/receipts.",
"The Codex spine may authenticate and transport; it must not own Epiphany state or scheduling.",
],
),
mutation_contract(
OPENAI_MODEL_STREAM_EVENT_TYPE,
OPENAI_MODEL_STREAM_EVENT_SCHEMA_VERSION,
vec![CultNetDocumentOperation::ReceiptWatch],
CultNetMutationAuthority::ReadOnly,
vec![],
vec![],
vec!["OpenAI stream events are receipts from a typed model request."],
),
mutation_contract(
OPENAI_MODEL_RECEIPT_TYPE,
OPENAI_MODEL_RECEIPT_SCHEMA_VERSION,
vec![CultNetDocumentOperation::ReceiptWatch],
CultNetMutationAuthority::ReadOnly,
vec![],
vec![],
vec!["Terminal model receipts carry response id, usage, and transport evidence."],
),
mutation_contract(
AGENT_MEMORY_TYPE,
AGENT_MEMORY_PAYLOAD_SCHEMA_VERSION,
@ -1401,12 +1453,17 @@ mod tests {
.iter()
.find(|contract| contract.document_type == SURFACE_FACE_TYPE)
.expect("face surface should advertise an interactive contract");
assert_eq!(face_contract.authority, CultNetMutationAuthority::Coordinator);
assert_eq!(
face_contract.authority,
CultNetMutationAuthority::Coordinator
);
assert!(
face_contract
.receipt_document_types
.as_ref()
.is_some_and(|items| items.iter().any(|item| item == "epiphany.face_bubble.v0"))
.is_some_and(|items| items
.iter()
.any(|item| item == "epiphany.face_bubble.v0"))
);
let unity_contract = contracts
.iter()
@ -1416,7 +1473,9 @@ mod tests {
unity_contract
.intent_document_types
.as_ref()
.is_some_and(|items| items.iter().any(|item| item == "epiphany.unity_probe_intent.v0"))
.is_some_and(|items| items
.iter()
.any(|item| item == "epiphany.unity_probe_intent.v0"))
);
let birth_contract = contracts
.iter()
@ -1426,7 +1485,25 @@ mod tests {
birth_contract
.receipt_document_types
.as_ref()
.is_some_and(|items| items.iter().any(|item| item == "epiphany.repo_birth_runner.v0"))
.is_some_and(|items| items
.iter()
.any(|item| item == "epiphany.repo_birth_runner.v0"))
);
let openai_contract = contracts
.iter()
.find(|contract| contract.document_type == OPENAI_MODEL_REQUEST_TYPE)
.expect("OpenAI adapter should advertise typed model request contract");
assert_eq!(
openai_contract.authority,
CultNetMutationAuthority::Coordinator
);
assert!(
openai_contract
.receipt_document_types
.as_ref()
.is_some_and(|items| items
.iter()
.any(|item| item == OPENAI_MODEL_RECEIPT_TYPE))
);
}
other => panic!("expected hello, got {other:?}"),
@ -1458,6 +1535,14 @@ mod tests {
schema.document_type.as_deref() == Some("epiphany.swarm_control_receipt.v0")
&& schema.schema_version.as_deref() == Some("epiphany.swarm_control_receipt.v0")
}));
assert!(schemas.iter().any(|schema| {
schema.document_type.as_deref() == Some(OPENAI_MODEL_REQUEST_TYPE)
&& schema.schema_version.as_deref() == Some(OPENAI_MODEL_REQUEST_SCHEMA_VERSION)
}));
assert!(schemas.iter().any(|schema| {
schema.document_type.as_deref() == Some(OPENAI_MODEL_RECEIPT_TYPE)
&& schema.schema_version.as_deref() == Some(OPENAI_MODEL_RECEIPT_SCHEMA_VERSION)
}));
Ok(())
}
}

View file

@ -4,6 +4,7 @@ use serde::Serialize;
pub const OPENAI_ADAPTER_REQUEST_SCHEMA_ID: &str = "epiphany.openai_model_request.v0";
pub const OPENAI_ADAPTER_EVENT_SCHEMA_ID: &str = "epiphany.openai_model_stream_event.v0";
pub const OPENAI_ADAPTER_RECEIPT_SCHEMA_ID: &str = "epiphany.openai_model_receipt.v0";
pub const OPENAI_ADAPTER_STATUS_SCHEMA_ID: &str = "epiphany.openai_adapter_status.v0";
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub enum EpiphanyOpenAiAuthMode {
@ -15,6 +16,7 @@ pub enum EpiphanyOpenAiAuthMode {
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct EpiphanyOpenAiAdapterStatus {
pub schema_id: String,
pub adapter_id: String,
pub auth_mode: EpiphanyOpenAiAuthMode,
pub account_id: Option<String>,

View file

@ -29,6 +29,7 @@ use epiphany_openai_adapter::EpiphanyOpenAiModelReceipt;
use epiphany_openai_adapter::EpiphanyOpenAiModelRequest;
use epiphany_openai_adapter::EpiphanyOpenAiStreamEvent;
use epiphany_openai_adapter::EpiphanyOpenAiStreamPayload;
use epiphany_openai_adapter::OPENAI_ADAPTER_STATUS_SCHEMA_ID;
use futures::StreamExt;
pub const CODEX_SPINE_ADAPTER_ID: &str = "codex-openai-subscription-spine";
@ -47,6 +48,7 @@ pub async fn status_from_auth_manager(
.map(|plan| format!("{plan:?}"));
EpiphanyOpenAiAdapterStatus {
schema_id: OPENAI_ADAPTER_STATUS_SCHEMA_ID.to_string(),
adapter_id: CODEX_SPINE_ADAPTER_ID.to_string(),
auth_mode,
account_id,
@ -63,6 +65,7 @@ pub fn status_from_codex_auth(
supports_websockets: bool,
) -> EpiphanyOpenAiAdapterStatus {
EpiphanyOpenAiAdapterStatus {
schema_id: OPENAI_ADAPTER_STATUS_SCHEMA_ID.to_string(),
adapter_id: CODEX_SPINE_ADAPTER_ID.to_string(),
auth_mode: auth_mode_from_codex_auth(auth),
account_id: auth.and_then(CodexAuth::get_account_id),

View file

@ -134,5 +134,12 @@ the next cut is to make an Epiphany-native runtime call this transport boundary
directly instead of reaching model turns through `thread/epiphany/*` JSON-RPC or
the Codex host brain.
The CultNet paperwork is now public too. `schemas/cultnet/` contains typed
schemas for OpenAI adapter status, model request, stream event, and terminal
receipt; `epiphany-runtime-spine` advertises those document types and mutation
contracts in its hello/schema catalog. This is the contract bridge the native
runtime should consume next. Do not add another JSON-RPC model endpoint and call
it progress.
The point is ownership: Epiphany calls a model adapter; it does not live inside
the Codex host brain.

View file

@ -561,7 +561,11 @@ same spine now owns the first HTTP Responses transport wrapper: typed
`ResponsesApiRequest`, auth/provider resolves through `codex-login` and
`codex-model-provider`, the stream opens through `codex-api`, and deltas /
completion map back into typed `EpiphanyOpenAiStreamEvent` and
`EpiphanyOpenAiModelReceipt` documents.
`EpiphanyOpenAiModelReceipt` documents. The CultNet schema catalog now advertises
OpenAI adapter status, model request, stream event, and receipt document types
plus the coordinator-owned model request contract. The native runtime should
consume that contract next; do not make a new JSON-RPC model endpoint and
pretend the whale got lighter.
Also: MCP itself is allowed to be JSON. The target is not "replace MCP JSON";
the target is an Epiphany-owned boundary that speaks typed Epiphany

View file

@ -0,0 +1,18 @@
{
"$schema": "https://json-schema.org/draft/2020-12/schema",
"$id": "https://gamecult.dev/epiphany/cultnet/epiphany.openai-adapter-status.schema.json",
"title": "Epiphany OpenAI Adapter Status",
"type": "object",
"required": ["schema_id", "adapter_id", "auth_mode", "supports_websockets", "codex_transport_attached"],
"properties": {
"schema_id": { "const": "epiphany.openai_adapter_status.v0" },
"adapter_id": { "type": "string" },
"auth_mode": { "enum": ["ChatGptSubscription", "ApiKey", "ExternalBearer", "Unknown"] },
"account_id": { "type": ["string", "null"] },
"plan_type": { "type": ["string", "null"] },
"default_model": { "type": ["string", "null"] },
"supports_websockets": { "type": "boolean" },
"codex_transport_attached": { "type": "boolean" }
},
"additionalProperties": false
}

View file

@ -0,0 +1,18 @@
{
"$schema": "https://json-schema.org/draft/2020-12/schema",
"$id": "https://gamecult.dev/epiphany/cultnet/epiphany.openai-model-receipt.schema.json",
"title": "Epiphany OpenAI Model Receipt",
"type": "object",
"required": ["schema_id", "request_id", "model"],
"properties": {
"schema_id": { "const": "epiphany.openai_model_receipt.v0" },
"request_id": { "type": "string" },
"model": { "type": "string" },
"response_id": { "type": ["string", "null"] },
"input_tokens": { "type": ["integer", "null"], "minimum": 0 },
"output_tokens": { "type": ["integer", "null"], "minimum": 0 },
"reasoning_output_tokens": { "type": ["integer", "null"], "minimum": 0 },
"transport": { "type": ["string", "null"] }
},
"additionalProperties": false
}

View file

@ -0,0 +1,71 @@
{
"$schema": "https://json-schema.org/draft/2020-12/schema",
"$id": "https://gamecult.dev/epiphany/cultnet/epiphany.openai-model-request.schema.json",
"title": "Epiphany OpenAI Model Request",
"type": "object",
"required": ["schema_id", "request_id", "conversation_id", "model", "instructions", "input"],
"properties": {
"schema_id": { "const": "epiphany.openai_model_request.v0" },
"request_id": { "type": "string" },
"conversation_id": { "type": "string" },
"model": { "type": "string" },
"instructions": { "type": "string" },
"input": {
"type": "array",
"items": { "$ref": "#/$defs/input_item" }
},
"reasoning_effort": { "type": ["string", "null"] },
"reasoning_summary": { "type": ["string", "null"] },
"service_tier": { "type": ["string", "null"] },
"output_contract_id": { "type": ["string", "null"] }
},
"additionalProperties": false,
"$defs": {
"input_item": {
"oneOf": [
{
"type": "object",
"required": ["UserText"],
"properties": {
"UserText": {
"type": "object",
"required": ["text"],
"properties": { "text": { "type": "string" } },
"additionalProperties": false
}
},
"additionalProperties": false
},
{
"type": "object",
"required": ["AssistantText"],
"properties": {
"AssistantText": {
"type": "object",
"required": ["text"],
"properties": { "text": { "type": "string" } },
"additionalProperties": false
}
},
"additionalProperties": false
},
{
"type": "object",
"required": ["ToolResult"],
"properties": {
"ToolResult": {
"type": "object",
"required": ["call_id", "output"],
"properties": {
"call_id": { "type": "string" },
"output": { "type": "string" }
},
"additionalProperties": false
}
},
"additionalProperties": false
}
]
}
}
}

View file

@ -0,0 +1,93 @@
{
"$schema": "https://json-schema.org/draft/2020-12/schema",
"$id": "https://gamecult.dev/epiphany/cultnet/epiphany.openai-model-stream-event.schema.json",
"title": "Epiphany OpenAI Model Stream Event",
"type": "object",
"required": ["schema_id", "request_id", "sequence", "payload"],
"properties": {
"schema_id": { "const": "epiphany.openai_model_stream_event.v0" },
"request_id": { "type": "string" },
"sequence": { "type": "integer", "minimum": 0 },
"payload": {
"oneOf": [
{ "$ref": "#/$defs/text_delta" },
{ "$ref": "#/$defs/reasoning_delta" },
{ "$ref": "#/$defs/tool_call" },
{ "$ref": "#/$defs/completed" },
{ "$ref": "#/$defs/failed" }
]
}
},
"additionalProperties": false,
"$defs": {
"text_delta": {
"type": "object",
"required": ["TextDelta"],
"properties": {
"TextDelta": {
"type": "object",
"required": ["text"],
"properties": { "text": { "type": "string" } },
"additionalProperties": false
}
},
"additionalProperties": false
},
"reasoning_delta": {
"type": "object",
"required": ["ReasoningDelta"],
"properties": {
"ReasoningDelta": {
"type": "object",
"required": ["text"],
"properties": { "text": { "type": "string" } },
"additionalProperties": false
}
},
"additionalProperties": false
},
"tool_call": {
"type": "object",
"required": ["ToolCall"],
"properties": {
"ToolCall": {
"type": "object",
"required": ["call_id", "name", "arguments"],
"properties": {
"call_id": { "type": "string" },
"name": { "type": "string" },
"arguments": { "type": "string" }
},
"additionalProperties": false
}
},
"additionalProperties": false
},
"completed": {
"type": "object",
"required": ["Completed"],
"properties": {
"Completed": {
"type": "object",
"required": ["receipt"],
"properties": { "receipt": { "$ref": "epiphany.openai-model-receipt.schema.json" } },
"additionalProperties": false
}
},
"additionalProperties": false
},
"failed": {
"type": "object",
"required": ["Failed"],
"properties": {
"Failed": {
"type": "object",
"required": ["message"],
"properties": { "message": { "type": "string" } },
"additionalProperties": false
}
},
"additionalProperties": false
}
}
}

View file

@ -56,6 +56,50 @@
"title": "Epiphany Runtime Event",
"path": "epiphany.runtime.event.schema.json"
},
{
"schemaId": "https://gamecult.dev/epiphany/cultnet/epiphany.openai-adapter-status.schema.json",
"kind": "document_payload",
"wireContracts": [
"cultnet.schema.v0"
],
"schemaVersion": "epiphany.openai_adapter_status.v0",
"documentType": "epiphany.openai_adapter_status.v0",
"title": "Epiphany OpenAI Adapter Status",
"path": "epiphany.openai-adapter-status.schema.json"
},
{
"schemaId": "https://gamecult.dev/epiphany/cultnet/epiphany.openai-model-request.schema.json",
"kind": "document_payload",
"wireContracts": [
"cultnet.schema.v0"
],
"schemaVersion": "epiphany.openai_model_request.v0",
"documentType": "epiphany.openai_model_request.v0",
"title": "Epiphany OpenAI Model Request",
"path": "epiphany.openai-model-request.schema.json"
},
{
"schemaId": "https://gamecult.dev/epiphany/cultnet/epiphany.openai-model-stream-event.schema.json",
"kind": "document_payload",
"wireContracts": [
"cultnet.schema.v0"
],
"schemaVersion": "epiphany.openai_model_stream_event.v0",
"documentType": "epiphany.openai_model_stream_event.v0",
"title": "Epiphany OpenAI Model Stream Event",
"path": "epiphany.openai-model-stream-event.schema.json"
},
{
"schemaId": "https://gamecult.dev/epiphany/cultnet/epiphany.openai-model-receipt.schema.json",
"kind": "document_payload",
"wireContracts": [
"cultnet.schema.v0"
],
"schemaVersion": "epiphany.openai_model_receipt.v0",
"documentType": "epiphany.openai_model_receipt.v0",
"title": "Epiphany OpenAI Model Receipt",
"path": "epiphany.openai-model-receipt.schema.json"
},
{
"schemaId": "https://gamecult.dev/epiphany/cultnet/epiphany.agent.memory.schema.json",
"kind": "document_payload",

Binary file not shown.

View file

@ -279,5 +279,5 @@ rejected_paths:
reason: making Epiphany cleaner inside Codex still preserves the wrong organ; Codex must be starved down to OpenAI subscription auth/model transport, while Epiphany data and subsystem communication move to typed CultCache/CultNet.
current_status:
summary: The Codex starvation pass has five typing receipts, seventeen processor cuts, a real physical extraction in `epiphany-codex-bridge`, a source-grounded Codex auth spine inventory, the pure native `epiphany-openai-adapter` document boundary, and a workspace-verified Codex auth/model wrapper in `epiphany-openai-codex-spine`. The bridge owns Codex-facing Epiphany adapters for scene, jobs, context/planning/graph query, retrieval, result projection, runtime-spine result adaptation, launch/prompt doctrine, mutation document mechanics, live-state projection, pressure/pre-compaction rendering, freshness/reorientation projection, CRRC/coordinator/role-board projection, and acceptance/evidence signal helpers. Vendored app-server now retains only `epiphany_invalidation.rs` as a root Epiphany module because watcher lifecycle is still app-server machinery; `codex_message_processor.rs` still contains route dispatch and mutation orchestration. The processor is about 10,339 lines, `epiphany_mutation_routes.rs` is about 1,071 lines, and `epiphany_state_helpers.rs` is down to a 19-line rollout-state loader. `epiphany-openai-adapter` defines typed request/event/receipt/auth-status documents with no Codex app-server or generic JSON dependency. `epiphany-openai-codex-spine` projects `AuthManager` / `CodexAuth` into typed `EpiphanyOpenAiAdapterStatus`, maps typed `EpiphanyOpenAiModelRequest` into Codex API `ResponsesApiRequest`, resolves auth/provider through keeper Codex crates, opens HTTP Responses streams through `codex-api`, and maps deltas/completion into typed stream events and receipts. MCP itself is correctly JSON at its protocol edge; the target is typed Epiphany intent/result/receipt wrappers around MCP, not replacing MCP JSON.
next_action: Continue Codex starvation by making an Epiphany-native runtime surface call the typed OpenAI adapter/spine directly, then starve the old model-turn path out of `thread/epiphany/*` JSON-RPC and `codex_message_processor.rs`, without adding Codex app-server, apps, skills, marketplace, or Epiphany JSON-RPC dependencies. Do not resume app/skill/marketplace expansion.
summary: The Codex starvation pass has five typing receipts, seventeen processor cuts, a real physical extraction in `epiphany-codex-bridge`, a source-grounded Codex auth spine inventory, the pure native `epiphany-openai-adapter` document boundary, and a workspace-verified Codex auth/model wrapper in `epiphany-openai-codex-spine`. The bridge owns Codex-facing Epiphany adapters for scene, jobs, context/planning/graph query, retrieval, result projection, runtime-spine result adaptation, launch/prompt doctrine, mutation document mechanics, live-state projection, pressure/pre-compaction rendering, freshness/reorientation projection, CRRC/coordinator/role-board projection, and acceptance/evidence signal helpers. Vendored app-server now retains only `epiphany_invalidation.rs` as a root Epiphany module because watcher lifecycle is still app-server machinery; `codex_message_processor.rs` still contains route dispatch and mutation orchestration. The processor is about 10,339 lines, `epiphany_mutation_routes.rs` is about 1,071 lines, and `epiphany_state_helpers.rs` is down to a 19-line rollout-state loader. `epiphany-openai-adapter` defines typed request/event/receipt/auth-status documents with no Codex app-server or generic JSON dependency. `epiphany-openai-codex-spine` projects `AuthManager` / `CodexAuth` into typed `EpiphanyOpenAiAdapterStatus`, maps typed `EpiphanyOpenAiModelRequest` into Codex API `ResponsesApiRequest`, resolves auth/provider through keeper Codex crates, opens HTTP Responses streams through `codex-api`, and maps deltas/completion into typed stream events and receipts. `epiphany-runtime-spine` now advertises OpenAI adapter status/model-request/stream-event/receipt CultNet schemas and mutation contracts, so the next caller can be native instead of JSON-RPC. MCP itself is correctly JSON at its protocol edge; the target is typed Epiphany intent/result/receipt wrappers around MCP, not replacing MCP JSON.
next_action: Continue Codex starvation by making an Epiphany-native runtime surface consume the advertised OpenAI adapter CultNet contract and call the typed spine directly, then starve the old model-turn path out of `thread/epiphany/*` JSON-RPC and `codex_message_processor.rs`, without adding Codex app-server, apps, skills, marketplace, or Epiphany JSON-RPC dependencies. Do not resume app/skill/marketplace expansion.