diff --git a/epiphany-core/Cargo.toml b/epiphany-core/Cargo.toml index 5c0510e7..a0e5295a 100644 --- a/epiphany-core/Cargo.toml +++ b/epiphany-core/Cargo.toml @@ -18,6 +18,7 @@ cultmesh-rs = { path = "../vendor/cultmesh-rs" } cultnet-rs = { path = "../vendor/cultnet-rs" } epiphany-openai-adapter = { path = "../epiphany-openai-adapter" } epiphany-model-adapter = { path = "../epiphany-model-adapter" } +epiphany-tool-adapter = { path = "../epiphany-tool-adapter" } epiphany-state-model = { path = "../epiphany-state-model" } ignore = "0.4.23" postgres = "0.19" diff --git a/epiphany-core/src/runtime_spine.rs b/epiphany-core/src/runtime_spine.rs index dfd2fe81..feaa62be 100644 --- a/epiphany-core/src/runtime_spine.rs +++ b/epiphany-core/src/runtime_spine.rs @@ -83,6 +83,9 @@ use epiphany_state_model::EpiphanyJobBinding; use epiphany_state_model::EpiphanyJobKind; use epiphany_state_model::EpiphanyRuntimeLink; use epiphany_state_model::EpiphanyThreadState; +use epiphany_tool_adapter::EpiphanyToolCapability; +use epiphany_tool_adapter::EpiphanyToolInvocationIntent; +use epiphany_tool_adapter::EpiphanyToolInvocationReceipt; use serde::Deserialize; use serde::Serialize; use std::collections::BTreeMap; @@ -107,6 +110,9 @@ pub const MODEL_ADAPTER_STATUS_TYPE: &str = "epiphany.model_adapter_status.v0"; pub const MODEL_REQUEST_TYPE: &str = "epiphany.model_request.v0"; pub const MODEL_STREAM_EVENT_TYPE: &str = "epiphany.model_stream_event.v0"; pub const MODEL_RECEIPT_TYPE: &str = "epiphany.model_receipt.v0"; +pub const TOOL_CAPABILITY_TYPE: &str = "epiphany.tool_capability.v0"; +pub const TOOL_INVOCATION_INTENT_TYPE: &str = "epiphany.tool_invocation_intent.v0"; +pub const TOOL_INVOCATION_RECEIPT_TYPE: &str = "epiphany.tool_invocation_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"; @@ -141,6 +147,9 @@ pub const MODEL_ADAPTER_STATUS_SCHEMA_VERSION: &str = "epiphany.model_adapter_st pub const MODEL_REQUEST_SCHEMA_VERSION: &str = "epiphany.model_request.v0"; pub const MODEL_STREAM_EVENT_SCHEMA_VERSION: &str = "epiphany.model_stream_event.v0"; pub const MODEL_RECEIPT_SCHEMA_VERSION: &str = "epiphany.model_receipt.v0"; +pub const TOOL_CAPABILITY_SCHEMA_VERSION: &str = "epiphany.tool_capability.v0"; +pub const TOOL_INVOCATION_INTENT_SCHEMA_VERSION: &str = "epiphany.tool_invocation_intent.v0"; +pub const TOOL_INVOCATION_RECEIPT_SCHEMA_VERSION: &str = "epiphany.tool_invocation_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"; @@ -684,6 +693,9 @@ pub fn runtime_spine_cache(store_path: impl AsRef) -> Result { cache.register_entry_type::()?; cache.register_entry_type::()?; cache.register_entry_type::()?; + cache.register_entry_type::()?; + cache.register_entry_type::()?; + cache.register_entry_type::()?; cache.add_generic_backing_store(SingleFileMessagePackBackingStore::new( store_path.to_path_buf(), )); @@ -2165,6 +2177,42 @@ fn epiphany_mutation_contracts() -> Vec { "Terminal model receipts carry provider response id, usage, and transport evidence.", ], ), + mutation_contract( + TOOL_CAPABILITY_TYPE, + TOOL_CAPABILITY_SCHEMA_VERSION, + vec![CultNetDocumentOperation::Snapshot], + CultNetMutationAuthority::ReadOnly, + vec![], + vec![], + vec![ + "Tool capability documents describe adapter-discovered tools without making raw MCP discovery JSON authoritative.", + ], + ), + mutation_contract( + TOOL_INVOCATION_INTENT_TYPE, + TOOL_INVOCATION_INTENT_SCHEMA_VERSION, + vec![ + CultNetDocumentOperation::IntentSubmit, + CultNetDocumentOperation::ReceiptWatch, + ], + CultNetMutationAuthority::Coordinator, + vec![TOOL_INVOCATION_INTENT_TYPE], + vec![TOOL_INVOCATION_RECEIPT_TYPE], + vec![ + "Tool calls enter Epiphany as typed invocation intents; MCP JSON remains protocol-edge cargo.", + ], + ), + mutation_contract( + TOOL_INVOCATION_RECEIPT_TYPE, + TOOL_INVOCATION_RECEIPT_SCHEMA_VERSION, + vec![CultNetDocumentOperation::ReceiptWatch], + CultNetMutationAuthority::ReadOnly, + vec![], + vec![], + vec![ + "Tool invocation receipts seal parsed results, errors, and raw-result artifact refs before scheduler or state admission.", + ], + ), mutation_contract( OPENAI_ADAPTER_STATUS_TYPE, OPENAI_ADAPTER_STATUS_SCHEMA_VERSION, @@ -3229,6 +3277,22 @@ mod tests { assert!(openai_contract.notes.as_ref().is_some_and(|notes| { notes.iter().any(|note| note.contains("adapter projection")) })); + let tool_intent_contract = contracts + .iter() + .find(|contract| contract.document_type == TOOL_INVOCATION_INTENT_TYPE) + .expect("tool invocation intent should advertise typed adapter contract"); + assert_eq!( + tool_intent_contract.authority, + CultNetMutationAuthority::Coordinator + ); + assert!( + tool_intent_contract + .receipt_document_types + .as_ref() + .is_some_and(|items| items + .iter() + .any(|item| item == TOOL_INVOCATION_RECEIPT_TYPE)) + ); let memory_graph_contract = contracts .iter() .find(|contract| contract.document_type == MEMORY_GRAPH_TYPE) @@ -3295,6 +3359,14 @@ mod tests { schema.document_type.as_deref() == Some(MODEL_RECEIPT_TYPE) && schema.schema_version.as_deref() == Some(MODEL_RECEIPT_SCHEMA_VERSION) })); + assert!(schemas.iter().any(|schema| { + schema.document_type.as_deref() == Some(TOOL_INVOCATION_INTENT_TYPE) + && schema.schema_version.as_deref() == Some(TOOL_INVOCATION_INTENT_SCHEMA_VERSION) + })); + assert!(schemas.iter().any(|schema| { + schema.document_type.as_deref() == Some(TOOL_INVOCATION_RECEIPT_TYPE) + && schema.schema_version.as_deref() == Some(TOOL_INVOCATION_RECEIPT_SCHEMA_VERSION) + })); let receipt_schema_path = epiphany_schema_root().join("epiphany.swarm-control-receipt.schema.json"); let receipt_schema: serde_json::Value = diff --git a/epiphany-tool-adapter/Cargo.lock b/epiphany-tool-adapter/Cargo.lock new file mode 100644 index 00000000..deb3aaa8 --- /dev/null +++ b/epiphany-tool-adapter/Cargo.lock @@ -0,0 +1,737 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 4 + +[[package]] +name = "android_system_properties" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "819e7219dbd41043ac279b19830f2efc897156490d7fd6ea916720117ee66311" +dependencies = [ + "libc", +] + +[[package]] +name = "anyhow" +version = "1.0.102" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7f202df86484c868dbad7eaa557ef785d5c66295e41b460ef922eca0723b842c" + +[[package]] +name = "autocfg" +version = "1.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f2032f911046de80f0a198e0901378627c33f59ea0ac00e363d481118bd70a53" + +[[package]] +name = "bitflags" +version = "2.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c4512299f36f043ab09a583e57bceb5a5aab7a73db1805848e8fef3c9e8c78b3" + +[[package]] +name = "bumpalo" +version = "3.20.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "72f5acc6cb2ba439de613abc23857ec3d78374d8ed5ac84e9d11336e87da8649" + +[[package]] +name = "cc" +version = "1.2.62" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a1dce859f0832a7d088c4f1119888ab94ef4b5d6795d1ce05afb7fe159d79f98" +dependencies = [ + "find-msvc-tools", + "shlex", +] + +[[package]] +name = "cfg-if" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9330f8b2ff13f34540b44e946ef35111825727b38d33286ef986142615121801" + +[[package]] +name = "chrono" +version = "0.4.44" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c673075a2e0e5f4a1dde27ce9dee1ea4558c7ffe648f576438a20ca1d2acc4b0" +dependencies = [ + "iana-time-zone", + "js-sys", + "num-traits", + "serde", + "wasm-bindgen", + "windows-link", +] + +[[package]] +name = "core-foundation-sys" +version = "0.8.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "773648b94d0e5d620f64f280777445740e61fe701025087ec8b57f45c791888b" + +[[package]] +name = "cultcache-rs" +version = "0.1.0" +dependencies = [ + "anyhow", + "chrono", + "cultcache-rs-derive", + "fs2", + "rmp-serde", + "serde", + "serde_bytes", + "serde_json", + "uuid", +] + +[[package]] +name = "cultcache-rs-derive" +version = "0.1.0" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "epiphany-tool-adapter" +version = "0.1.0" +dependencies = [ + "cultcache-rs", + "serde", +] + +[[package]] +name = "equivalent" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "877a4ace8713b0bcf2a4e7eec82529c029f1d0619886d18145fea96c3ffe5c0f" + +[[package]] +name = "find-msvc-tools" +version = "0.1.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5baebc0774151f905a1a2cc41989300b1e6fbb29aff0ceffa1064fdd3088d582" + +[[package]] +name = "foldhash" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d9c4f5dac5e15c24eb999c26181a6ca40b39fe946cbe4c263c7209467bc83af2" + +[[package]] +name = "fs2" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9564fc758e15025b46aa6643b1b77d047d1a56a1aea6e01002ac0c7026876213" +dependencies = [ + "libc", + "winapi", +] + +[[package]] +name = "futures-core" +version = "0.3.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7e3450815272ef58cec6d564423f6e755e25379b217b0bc688e295ba24df6b1d" + +[[package]] +name = "futures-task" +version = "0.3.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "037711b3d59c33004d3856fbdc83b99d4ff37a24768fa1be9ce3538a1cde4393" + +[[package]] +name = "futures-util" +version = "0.3.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "389ca41296e6190b48053de0321d02a77f32f8a5d2461dd38762c0593805c6d6" +dependencies = [ + "futures-core", + "futures-task", + "pin-project-lite", + "slab", +] + +[[package]] +name = "getrandom" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0de51e6874e94e7bf76d726fc5d13ba782deca734ff60d5bb2fb2607c7406555" +dependencies = [ + "cfg-if", + "libc", + "r-efi", + "wasip2", + "wasip3", +] + +[[package]] +name = "hashbrown" +version = "0.15.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9229cfe53dfd69f0609a49f65461bd93001ea1ef889cd5529dd176593f5338a1" +dependencies = [ + "foldhash", +] + +[[package]] +name = "hashbrown" +version = "0.17.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ed5909b6e89a2db4456e54cd5f673791d7eca6732202bbf2a9cc504fe2f9b84a" + +[[package]] +name = "heck" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea" + +[[package]] +name = "iana-time-zone" +version = "0.1.65" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e31bc9ad994ba00e440a8aa5c9ef0ec67d5cb5e5cb0cc7f8b744a35b389cc470" +dependencies = [ + "android_system_properties", + "core-foundation-sys", + "iana-time-zone-haiku", + "js-sys", + "log", + "wasm-bindgen", + "windows-core", +] + +[[package]] +name = "iana-time-zone-haiku" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f31827a206f56af32e590ba56d5d2d085f558508192593743f16b2306495269f" +dependencies = [ + "cc", +] + +[[package]] +name = "id-arena" +version = "2.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3d3067d79b975e8844ca9eb072e16b31c3c1c36928edf9c6789548c524d0d954" + +[[package]] +name = "indexmap" +version = "2.14.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d466e9454f08e4a911e14806c24e16fba1b4c121d1ea474396f396069cf949d9" +dependencies = [ + "equivalent", + "hashbrown 0.17.1", + "serde", + "serde_core", +] + +[[package]] +name = "itoa" +version = "1.0.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8f42a60cbdf9a97f5d2305f08a87dc4e09308d1276d28c869c684d7777685682" + +[[package]] +name = "js-sys" +version = "0.3.99" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "142bc4740e452c1e57ade0cbc129f139c9093e354346f0872ef985f4f5cf5f11" +dependencies = [ + "cfg-if", + "futures-util", + "once_cell", + "wasm-bindgen", +] + +[[package]] +name = "leb128fmt" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "09edd9e8b54e49e587e4f6295a7d29c3ea94d469cb40ab8ca70b288248a81db2" + +[[package]] +name = "libc" +version = "0.2.186" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "68ab91017fe16c622486840e4c83c9a37afeff978bd239b5293d61ece587de66" + +[[package]] +name = "log" +version = "0.4.30" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "616ec5685824bcc94416c6d4a7a446eea774a31efd7062c8480ba6fd06d7a6e5" + +[[package]] +name = "memchr" +version = "2.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6b947ae49db0d222b1dbc6b113ce7248a3fc3a6ca21b696717bfc000ba4484d8" + +[[package]] +name = "num-traits" +version = "0.2.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "071dfc062690e90b734c0b2273ce72ad0ffa95f0c74596bc250dcfd960262841" +dependencies = [ + "autocfg", +] + +[[package]] +name = "once_cell" +version = "1.21.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9f7c3e4beb33f85d45ae3e3a1792185706c8e16d043238c593331cc7cd313b50" + +[[package]] +name = "pin-project-lite" +version = "0.2.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a89322df9ebe1c1578d689c92318e070967d1042b512afbe49518723f4e6d5cd" + +[[package]] +name = "prettyplease" +version = "0.2.37" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "479ca8adacdd7ce8f1fb39ce9ecccbfe93a3f1344b3d0d97f20bc0196208f62b" +dependencies = [ + "proc-macro2", + "syn", +] + +[[package]] +name = "proc-macro2" +version = "1.0.106" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8fd00f0bb2e90d81d1044c2b32617f68fcb9fa3bb7640c23e9c748e53fb30934" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "quote" +version = "1.0.45" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "41f2619966050689382d2b44f664f4bc593e129785a36d6ee376ddf37259b924" +dependencies = [ + "proc-macro2", +] + +[[package]] +name = "r-efi" +version = "6.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f8dcc9c7d52a811697d2151c701e0d08956f92b0e24136cf4cf27b57a6a0d9bf" + +[[package]] +name = "rmp" +version = "0.8.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4ba8be72d372b2c9b35542551678538b562e7cf86c3315773cae48dfbfe7790c" +dependencies = [ + "num-traits", +] + +[[package]] +name = "rmp-serde" +version = "1.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "72f81bee8c8ef9b577d1681a70ebbc962c232461e397b22c208c43c04b67a155" +dependencies = [ + "rmp", + "serde", +] + +[[package]] +name = "rustversion" +version = "1.0.22" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b39cdef0fa800fc44525c84ccb54a029961a8215f9619753635a9c0d2538d46d" + +[[package]] +name = "semver" +version = "1.0.28" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8a7852d02fc848982e0c167ef163aaff9cd91dc640ba85e263cb1ce46fae51cd" + +[[package]] +name = "serde" +version = "1.0.228" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9a8e94ea7f378bd32cbbd37198a4a91436180c5bb472411e48b5ec2e2124ae9e" +dependencies = [ + "serde_core", + "serde_derive", +] + +[[package]] +name = "serde_bytes" +version = "0.11.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a5d440709e79d88e51ac01c4b72fc6cb7314017bb7da9eeff678aa94c10e3ea8" +dependencies = [ + "serde", + "serde_core", +] + +[[package]] +name = "serde_core" +version = "1.0.228" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "41d385c7d4ca58e59fc732af25c3983b67ac852c1a25000afe1175de458b67ad" +dependencies = [ + "serde_derive", +] + +[[package]] +name = "serde_derive" +version = "1.0.228" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d540f220d3187173da220f885ab66608367b6574e925011a9353e4badda91d79" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "serde_json" +version = "1.0.150" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e8014e44b4736ed0538adeecded0fce2a272f22dc9578a7eb6b2d9993c74cfb9" +dependencies = [ + "itoa", + "memchr", + "serde", + "serde_core", + "zmij", +] + +[[package]] +name = "shlex" +version = "1.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64" + +[[package]] +name = "slab" +version = "0.4.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0c790de23124f9ab44544d7ac05d60440adc586479ce501c1d6d7da3cd8c9cf5" + +[[package]] +name = "syn" +version = "2.0.117" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e665b8803e7b1d2a727f4023456bbbbe74da67099c585258af0ad9c5013b9b99" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "unicode-ident" +version = "1.0.24" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e6e4313cd5fcd3dad5cafa179702e2b244f760991f45397d14d4ebf38247da75" + +[[package]] +name = "unicode-xid" +version = "0.2.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ebc1c04c71510c7f702b52b7c350734c9ff1295c464a03335b00bb84fc54f853" + +[[package]] +name = "uuid" +version = "1.23.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ddd74a9687298c6858e9b88ec8935ec45d22e8fd5e6394fa1bd4e99a87789c76" +dependencies = [ + "getrandom", + "js-sys", + "wasm-bindgen", +] + +[[package]] +name = "wasip2" +version = "1.0.3+wasi-0.2.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "20064672db26d7cdc89c7798c48a0fdfac8213434a1186e5ef29fd560ae223d6" +dependencies = [ + "wit-bindgen 0.57.1", +] + +[[package]] +name = "wasip3" +version = "0.4.0+wasi-0.3.0-rc-2026-01-06" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5428f8bf88ea5ddc08faddef2ac4a67e390b88186c703ce6dbd955e1c145aca5" +dependencies = [ + "wit-bindgen 0.51.0", +] + +[[package]] +name = "wasm-bindgen" +version = "0.2.122" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3ed04576f974d2b2fba0f38c51dbc5518011e38c36bf1143164be765528fd409" +dependencies = [ + "cfg-if", + "once_cell", + "rustversion", + "wasm-bindgen-macro", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-macro" +version = "0.2.122" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "916151b09da36bd82f6615cbf3a419e2f0ba23a03c6160e8e92eb6bd4aa1dec6" +dependencies = [ + "quote", + "wasm-bindgen-macro-support", +] + +[[package]] +name = "wasm-bindgen-macro-support" +version = "0.2.122" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "299047362ccbfce148b67ab7e73349f77748e00c8296f9542adfad2ad82c5c5e" +dependencies = [ + "bumpalo", + "proc-macro2", + "quote", + "syn", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-shared" +version = "0.2.122" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9a929b2c61f11ba3e9bc35b50c1f25cb38e0e892c0c231ae2b8cf78d5dad4437" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "wasm-encoder" +version = "0.244.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "990065f2fe63003fe337b932cfb5e3b80e0b4d0f5ff650e6985b1048f62c8319" +dependencies = [ + "leb128fmt", + "wasmparser", +] + +[[package]] +name = "wasm-metadata" +version = "0.244.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bb0e353e6a2fbdc176932bbaab493762eb1255a7900fe0fea1a2f96c296cc909" +dependencies = [ + "anyhow", + "indexmap", + "wasm-encoder", + "wasmparser", +] + +[[package]] +name = "wasmparser" +version = "0.244.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "47b807c72e1bac69382b3a6fb3dbe8ea4c0ed87ff5629b8685ae6b9a611028fe" +dependencies = [ + "bitflags", + "hashbrown 0.15.5", + "indexmap", + "semver", +] + +[[package]] +name = "winapi" +version = "0.3.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419" +dependencies = [ + "winapi-i686-pc-windows-gnu", + "winapi-x86_64-pc-windows-gnu", +] + +[[package]] +name = "winapi-i686-pc-windows-gnu" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" + +[[package]] +name = "winapi-x86_64-pc-windows-gnu" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" + +[[package]] +name = "windows-core" +version = "0.62.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b8e83a14d34d0623b51dce9581199302a221863196a1dde71a7663a4c2be9deb" +dependencies = [ + "windows-implement", + "windows-interface", + "windows-link", + "windows-result", + "windows-strings", +] + +[[package]] +name = "windows-implement" +version = "0.60.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "053e2e040ab57b9dc951b72c264860db7eb3b0200ba345b4e4c3b14f67855ddf" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "windows-interface" +version = "0.59.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3f316c4a2570ba26bbec722032c4099d8c8bc095efccdc15688708623367e358" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "windows-link" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f0805222e57f7521d6a62e36fa9163bc891acd422f971defe97d64e70d0a4fe5" + +[[package]] +name = "windows-result" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7781fa89eaf60850ac3d2da7af8e5242a5ea78d1a11c49bf2910bb5a73853eb5" +dependencies = [ + "windows-link", +] + +[[package]] +name = "windows-strings" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7837d08f69c77cf6b07689544538e017c1bfcf57e34b4c0ff58e6c2cd3b37091" +dependencies = [ + "windows-link", +] + +[[package]] +name = "wit-bindgen" +version = "0.51.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d7249219f66ced02969388cf2bb044a09756a083d0fab1e566056b04d9fbcaa5" +dependencies = [ + "wit-bindgen-rust-macro", +] + +[[package]] +name = "wit-bindgen" +version = "0.57.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1ebf944e87a7c253233ad6766e082e3cd714b5d03812acc24c318f549614536e" + +[[package]] +name = "wit-bindgen-core" +version = "0.51.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ea61de684c3ea68cb082b7a88508a8b27fcc8b797d738bfc99a82facf1d752dc" +dependencies = [ + "anyhow", + "heck", + "wit-parser", +] + +[[package]] +name = "wit-bindgen-rust" +version = "0.51.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b7c566e0f4b284dd6561c786d9cb0142da491f46a9fbed79ea69cdad5db17f21" +dependencies = [ + "anyhow", + "heck", + "indexmap", + "prettyplease", + "syn", + "wasm-metadata", + "wit-bindgen-core", + "wit-component", +] + +[[package]] +name = "wit-bindgen-rust-macro" +version = "0.51.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0c0f9bfd77e6a48eccf51359e3ae77140a7f50b1e2ebfe62422d8afdaffab17a" +dependencies = [ + "anyhow", + "prettyplease", + "proc-macro2", + "quote", + "syn", + "wit-bindgen-core", + "wit-bindgen-rust", +] + +[[package]] +name = "wit-component" +version = "0.244.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9d66ea20e9553b30172b5e831994e35fbde2d165325bec84fc43dbf6f4eb9cb2" +dependencies = [ + "anyhow", + "bitflags", + "indexmap", + "log", + "serde", + "serde_derive", + "serde_json", + "wasm-encoder", + "wasm-metadata", + "wasmparser", + "wit-parser", +] + +[[package]] +name = "wit-parser" +version = "0.244.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ecc8ac4bc1dc3381b7f59c34f00b67e18f910c2c0f50015669dde7def656a736" +dependencies = [ + "anyhow", + "id-arena", + "indexmap", + "log", + "semver", + "serde", + "serde_derive", + "serde_json", + "unicode-xid", + "wasmparser", +] + +[[package]] +name = "zmij" +version = "1.0.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b8848ee67ecc8aedbaf3e4122217aff892639231befc6a1b58d29fff4c2cabaa" diff --git a/epiphany-tool-adapter/Cargo.toml b/epiphany-tool-adapter/Cargo.toml new file mode 100644 index 00000000..38688921 --- /dev/null +++ b/epiphany-tool-adapter/Cargo.toml @@ -0,0 +1,12 @@ +[package] +name = "epiphany-tool-adapter" +version = "0.1.0" +edition = "2024" + +[lib] +name = "epiphany_tool_adapter" +path = "src/lib.rs" + +[dependencies] +cultcache-rs = { path = "../vendor/cultcache-rs" } +serde = { version = "1", features = ["derive"] } diff --git a/epiphany-tool-adapter/src/lib.rs b/epiphany-tool-adapter/src/lib.rs new file mode 100644 index 00000000..b0700595 --- /dev/null +++ b/epiphany-tool-adapter/src/lib.rs @@ -0,0 +1,153 @@ +use cultcache_rs::DatabaseEntry; + +pub const TOOL_ADAPTER_CAPABILITY_SCHEMA_ID: &str = "epiphany.tool_capability.v0"; +pub const TOOL_ADAPTER_INVOCATION_INTENT_SCHEMA_ID: &str = "epiphany.tool_invocation_intent.v0"; +pub const TOOL_ADAPTER_INVOCATION_RECEIPT_SCHEMA_ID: &str = "epiphany.tool_invocation_receipt.v0"; + +#[derive(Debug, Clone, PartialEq, Eq, DatabaseEntry)] +#[cultcache( + type = "epiphany.tool_capability.v0", + schema = "EpiphanyToolCapability" +)] +pub struct EpiphanyToolCapability { + #[cultcache(key = 0)] + pub schema_id: String, + #[cultcache(key = 1)] + pub capability_id: String, + #[cultcache(key = 2)] + pub adapter: String, + #[cultcache(key = 3)] + pub server: String, + #[cultcache(key = 4)] + pub tool_name: String, + #[cultcache(key = 5, default)] + pub description: Option, + #[cultcache(key = 6, default)] + pub input_schema_json: Option, +} + +impl EpiphanyToolCapability { + pub fn new( + capability_id: impl Into, + adapter: impl Into, + server: impl Into, + tool_name: impl Into, + ) -> Self { + Self { + schema_id: TOOL_ADAPTER_CAPABILITY_SCHEMA_ID.to_string(), + capability_id: capability_id.into(), + adapter: adapter.into(), + server: server.into(), + tool_name: tool_name.into(), + description: None, + input_schema_json: None, + } + } +} + +#[derive(Debug, Clone, PartialEq, Eq, DatabaseEntry)] +#[cultcache( + type = "epiphany.tool_invocation_intent.v0", + schema = "EpiphanyToolInvocationIntent" +)] +pub struct EpiphanyToolInvocationIntent { + #[cultcache(key = 0)] + pub schema_id: String, + #[cultcache(key = 1)] + pub intent_id: String, + #[cultcache(key = 2)] + pub adapter: String, + #[cultcache(key = 3)] + pub server: String, + #[cultcache(key = 4)] + pub tool_name: String, + #[cultcache(key = 5)] + pub arguments_json: String, + #[cultcache(key = 6)] + pub caller: String, + #[cultcache(key = 7)] + pub reason: String, + #[cultcache(key = 8)] + pub created_at: String, +} + +impl EpiphanyToolInvocationIntent { + pub fn new( + intent_id: impl Into, + adapter: impl Into, + server: impl Into, + tool_name: impl Into, + arguments_json: impl Into, + caller: impl Into, + reason: impl Into, + created_at: impl Into, + ) -> Self { + Self { + schema_id: TOOL_ADAPTER_INVOCATION_INTENT_SCHEMA_ID.to_string(), + intent_id: intent_id.into(), + adapter: adapter.into(), + server: server.into(), + tool_name: tool_name.into(), + arguments_json: arguments_json.into(), + caller: caller.into(), + reason: reason.into(), + created_at: created_at.into(), + } + } +} + +#[derive(Debug, Clone, PartialEq, Eq, DatabaseEntry)] +#[cultcache( + type = "epiphany.tool_invocation_receipt.v0", + schema = "EpiphanyToolInvocationReceipt" +)] +pub struct EpiphanyToolInvocationReceipt { + #[cultcache(key = 0)] + pub schema_id: String, + #[cultcache(key = 1)] + pub receipt_id: String, + #[cultcache(key = 2)] + pub intent_id: String, + #[cultcache(key = 3)] + pub adapter: String, + #[cultcache(key = 4)] + pub server: String, + #[cultcache(key = 5)] + pub tool_name: String, + #[cultcache(key = 6)] + pub status: String, + #[cultcache(key = 7)] + pub completed_at: String, + #[cultcache(key = 8, default)] + pub result_json: Option, + #[cultcache(key = 9, default)] + pub error: Option, + #[cultcache(key = 10, default)] + pub sealed_raw_result_ref: Option, +} + +impl EpiphanyToolInvocationReceipt { + pub fn new( + receipt_id: impl Into, + intent_id: impl Into, + adapter: impl Into, + server: impl Into, + tool_name: impl Into, + status: impl Into, + completed_at: impl Into, + ) -> Self { + Self { + schema_id: TOOL_ADAPTER_INVOCATION_RECEIPT_SCHEMA_ID.to_string(), + receipt_id: receipt_id.into(), + intent_id: intent_id.into(), + adapter: adapter.into(), + server: server.into(), + tool_name: tool_name.into(), + status: status.into(), + completed_at: completed_at.into(), + result_json: None, + error: None, + sealed_raw_result_ref: None, + } + } +} diff --git a/notes/fresh-workspace-handoff.md b/notes/fresh-workspace-handoff.md index fd9094c2..c076e491 100644 --- a/notes/fresh-workspace-handoff.md +++ b/notes/fresh-workspace-handoff.md @@ -39,6 +39,7 @@ remembers doctrine; the branch remembers the blade. - `epiphany-mvp-status --source native` is the current provider-neutral status boundary. It projects scene, pressure, jobs, roles, planning, reorient, CRRC, and coordinator status from native core surfaces and returns provider/runtime worker results as `backendMissing` placeholders instead of asking Codex for model-worker state. Keep future pluggable model-provider work behind launch/result/runtime documents; do not let provider choice leak into status, prompt authority, scheduler policy, or durable `EpiphanyThreadState`. - `epiphany-model-runtime` is the current provider-neutral executable boundary. It is built by the `epiphany-openai-runtime` crate for now and accepts `--provider openai-codex`; the old `epiphany-openai-runtime` binary, `--openai-runtime-bin`, and `openai-runtime` executor name survive only as compatibility aliases. Coordinator run mode, local wrapper run mode, and repo birth runner should call `--model-runtime-bin` plus `--model-provider`. The typed request/event/receipt contract is now provider-neutral in `epiphany-model-adapter` as `epiphany.model_request.v0`, `epiphany.model_stream_event.v0`, `epiphany.model_receipt.v0`, and `epiphany.model_adapter_status.v0`; OpenAI/Codex documents are adapter evidence behind that boundary, not scheduler/status/state authority. - Coordinator plan/run passes now emit `epiphany.coordinator_run_receipt.v0` into runtime-spine. `coordinator-summary.json` and `coordinator-steps.jsonl` remain useful operator artifacts, but the durable receipt of what the coordinator decided is typed CultCache/CultNet state. +- The first tool/MCP contract surface exists in `epiphany-tool-adapter`: `epiphany.tool_capability.v0`, `epiphany.tool_invocation_intent.v0`, and `epiphany.tool_invocation_receipt.v0`. Runtime-spine advertises these as the Epiphany-facing tool boundary. Raw MCP JSON is still an execution-edge concern until a real adapter consumes the intents and emits the receipts. - The active foundation directive is now `notes/codex-starvation-and-cultnet-liberation-plan.md`. The previous Codex app-server control-plane rebuild made Epiphany less rotten inside Codex, but that is not the destination. Epiphany must become a native CultCache/CultMesh/CultNet runtime while remaining an honest modified Codex-derived backend for subscription auth/model use. Codex should remain relatively vanilla and keep doing Codex things, including useful app-server and streaming bridge affordances, but it must not own Epiphany state, processes, prompts, scheduler decisions, or policy. The bridge handles interop; Codex and Epiphany do not get to rummage around in each other's organs. - `notes/epiphany-cultmesh-dreaming-roadmap.md` is the new concrete design for distributed Epiphany dreaming. The invariant is hard: private state stays local; public dreams are separately authored typed documents distributed through CultMesh/CultNet; foreign dreams are thought weather until a reviewed local adoption receipt digests them. The first code slice should be dream schemas plus a local CultMesh-backed store, not public fanout. - CultMesh is now the preferred Rust abstraction for that local store work. The compile-time Rust substrate is repo-contained: `vendor/cultcache-rs`, `vendor/cultnet-rs`, and `vendor/cultmesh-rs`. The old `E:\Projects\CultLib\crates\*` dependency body is dead in this checkout and must not be revived by accident. Vendored CultCache reads/writes `cultcache.store.v1`, so `state/ledgers.msgpack` and `epiphany-state status` are aligned again. The first integrations are deliberately small: status, Verse policy, global room policy, organ contract policy, native operator status, native operator snapshots, and local operator run intent/receipt round-trip through CultMesh. Use `cargo run --manifest-path .\epiphany-core\Cargo.toml --bin epiphany-cultmesh-smoke`, `cargo run --manifest-path .\epiphany-core\Cargo.toml --bin epiphany-cultmesh-status -- smoke --store .\.epiphany-smoke\cultmesh\operator-status.ccmp --runtime-id epiphany-cultmesh-status-smoke`, `cargo run --manifest-path .\epiphany-core\Cargo.toml --bin epiphany-operator-snapshot -- smoke --store .\.epiphany-smoke\cultmesh\operator-snapshots.ccmp --runtime-id epiphany-operator-snapshot-smoke --snapshot-id smoke-status`, and `cargo run --manifest-path .\epiphany-core\Cargo.toml --bin epiphany-operator-run -- smoke --store .\.epiphany-smoke\cultmesh\operator-runs.ccmp --runtime-id epiphany-operator-run-smoke --run-id smoke-run` for the focused proofs. Future local dream/status/Verse work should start from CultMesh, not raw CultNet, unless the task is polishing the CultMesh internals themselves. Preserve the three Verse tiers: `epiphany-internal` for private sub-agent typed state, `gamecult-local` for trusted LAN/Yggdrasil-tunnel GameCult sharing, and `epiphany-global` for untrusted public dreams plus topic-specific threaded public rooms for Face posts. diff --git a/schemas/cultnet/epiphany.tool-capability.schema.json b/schemas/cultnet/epiphany.tool-capability.schema.json new file mode 100644 index 00000000..4b8e8461 --- /dev/null +++ b/schemas/cultnet/epiphany.tool-capability.schema.json @@ -0,0 +1,18 @@ +{ + "$schema": "https://json-schema.org/draft/2020-12/schema", + "$id": "https://gamecult.dev/epiphany/cultnet/epiphany.tool-capability.schema.json", + "title": "Epiphany Tool Capability", + "description": "Typed capability advertised by an Epiphany tool adapter. MCP discovery JSON may be the source, but this document is the inspectable Epiphany-facing capability record.", + "type": "object", + "required": ["schema_id", "capability_id", "adapter", "server", "tool_name"], + "properties": { + "schema_id": { "const": "epiphany.tool_capability.v0" }, + "capability_id": { "type": "string" }, + "adapter": { "type": "string" }, + "server": { "type": "string" }, + "tool_name": { "type": "string" }, + "description": { "type": ["string", "null"] }, + "input_schema_json": { "type": ["string", "null"] } + }, + "additionalProperties": false +} diff --git a/schemas/cultnet/epiphany.tool-invocation-intent.schema.json b/schemas/cultnet/epiphany.tool-invocation-intent.schema.json new file mode 100644 index 00000000..625b8371 --- /dev/null +++ b/schemas/cultnet/epiphany.tool-invocation-intent.schema.json @@ -0,0 +1,30 @@ +{ + "$schema": "https://json-schema.org/draft/2020-12/schema", + "$id": "https://gamecult.dev/epiphany/cultnet/epiphany.tool-invocation-intent.schema.json", + "title": "Epiphany Tool Invocation Intent", + "description": "Coordinator-owned typed request to call a tool through an adapter. Raw MCP arguments remain string cargo until the adapter validates them.", + "type": "object", + "required": [ + "schema_id", + "intent_id", + "adapter", + "server", + "tool_name", + "arguments_json", + "caller", + "reason", + "created_at" + ], + "properties": { + "schema_id": { "const": "epiphany.tool_invocation_intent.v0" }, + "intent_id": { "type": "string" }, + "adapter": { "type": "string" }, + "server": { "type": "string" }, + "tool_name": { "type": "string" }, + "arguments_json": { "type": "string" }, + "caller": { "type": "string" }, + "reason": { "type": "string" }, + "created_at": { "type": "string" } + }, + "additionalProperties": false +} diff --git a/schemas/cultnet/epiphany.tool-invocation-receipt.schema.json b/schemas/cultnet/epiphany.tool-invocation-receipt.schema.json new file mode 100644 index 00000000..5c644fe0 --- /dev/null +++ b/schemas/cultnet/epiphany.tool-invocation-receipt.schema.json @@ -0,0 +1,31 @@ +{ + "$schema": "https://json-schema.org/draft/2020-12/schema", + "$id": "https://gamecult.dev/epiphany/cultnet/epiphany.tool-invocation-receipt.schema.json", + "title": "Epiphany Tool Invocation Receipt", + "description": "Typed receipt from a tool adapter after it executes or refuses a tool invocation.", + "type": "object", + "required": [ + "schema_id", + "receipt_id", + "intent_id", + "adapter", + "server", + "tool_name", + "status", + "completed_at" + ], + "properties": { + "schema_id": { "const": "epiphany.tool_invocation_receipt.v0" }, + "receipt_id": { "type": "string" }, + "intent_id": { "type": "string" }, + "adapter": { "type": "string" }, + "server": { "type": "string" }, + "tool_name": { "type": "string" }, + "status": { "type": "string" }, + "completed_at": { "type": "string" }, + "result_json": { "type": ["string", "null"] }, + "error": { "type": ["string", "null"] }, + "sealed_raw_result_ref": { "type": ["string", "null"] } + }, + "additionalProperties": false +} diff --git a/schemas/cultnet/index.json b/schemas/cultnet/index.json index 78287bfc..f6f9d965 100644 --- a/schemas/cultnet/index.json +++ b/schemas/cultnet/index.json @@ -111,6 +111,39 @@ "title": "Epiphany Model Receipt", "path": "epiphany.model-receipt.schema.json" }, + { + "schemaId": "https://gamecult.dev/epiphany/cultnet/epiphany.tool-capability.schema.json", + "kind": "document_payload", + "wireContracts": [ + "cultnet.schema.v0" + ], + "schemaVersion": "epiphany.tool_capability.v0", + "documentType": "epiphany.tool_capability.v0", + "title": "Epiphany Tool Capability", + "path": "epiphany.tool-capability.schema.json" + }, + { + "schemaId": "https://gamecult.dev/epiphany/cultnet/epiphany.tool-invocation-intent.schema.json", + "kind": "document_payload", + "wireContracts": [ + "cultnet.schema.v0" + ], + "schemaVersion": "epiphany.tool_invocation_intent.v0", + "documentType": "epiphany.tool_invocation_intent.v0", + "title": "Epiphany Tool Invocation Intent", + "path": "epiphany.tool-invocation-intent.schema.json" + }, + { + "schemaId": "https://gamecult.dev/epiphany/cultnet/epiphany.tool-invocation-receipt.schema.json", + "kind": "document_payload", + "wireContracts": [ + "cultnet.schema.v0" + ], + "schemaVersion": "epiphany.tool_invocation_receipt.v0", + "documentType": "epiphany.tool_invocation_receipt.v0", + "title": "Epiphany Tool Invocation Receipt", + "path": "epiphany.tool-invocation-receipt.schema.json" + }, { "schemaId": "https://gamecult.dev/epiphany/cultnet/epiphany.openai-adapter-status.schema.json", "kind": "document_payload", diff --git a/state/ledgers.msgpack b/state/ledgers.msgpack index 512fe4c2..3deffecc 100644 Binary files a/state/ledgers.msgpack and b/state/ledgers.msgpack differ diff --git a/state/map.yaml b/state/map.yaml index 59707b9c..4cc02648 100644 --- a/state/map.yaml +++ b/state/map.yaml @@ -532,4 +532,12 @@ current_status: existing coordinator artifact JSON remains operator display and sealed audit evidence, but it is no longer the only durable account of what the coordinator decided. - next_action: Continue Codex starvation and CultNet liberation by wrapping remaining unavoidable JSON protocol edges, especially MCP/tool execution, in typed Epiphany intent/result/receipt documents before letting them influence scheduler, state, or acceptance authority. + The first tool/MCP boundary contract is also named: + `epiphany-tool-adapter` owns typed `epiphany.tool_capability.v0`, + `epiphany.tool_invocation_intent.v0`, and + `epiphany.tool_invocation_receipt.v0` documents. Runtime-spine advertises + them as the Epiphany-facing tool contract so MCP discovery/call JSON can + remain protocol-edge cargo instead of scheduler, state, or acceptance + authority. This is a contract surface only; the executing MCP adapter still + needs to be built before Codex MCP routes can be demoted. + next_action: Continue Codex starvation and CultNet liberation by building the executing tool/MCP adapter that consumes `epiphany.tool_invocation_intent.v0`, emits `epiphany.tool_invocation_receipt.v0`, and keeps raw MCP JSON sealed at the adapter boundary.