From 8258b4833d11a582c6128e558cfcad2531bc20e7 Mon Sep 17 00:00:00 2001 From: AIEN Date: Mon, 21 Sep 2026 12:04:36 -0500 Subject: [PATCH] feat(abi): adopt canonical AgentState, AgentEvent, and protocol IDs with CI sibling resolution --- .github/workflows/ci.yml | 50 ++++- Cargo.lock | 340 +++++++++++++++++++++++++++++- Cargo.toml | 3 +- src/events/event.rs | 50 +++++ src/events/id.rs | 20 ++ src/inference.rs | 34 +-- src/lib.rs | 11 +- src/sessions/id.rs | 80 +++++++ src/sessions/session.rs | 77 +++++++ tests/canonical_abi_tests.rs | 128 +++++++++++ tests/embedded_inference_tests.rs | 5 +- 11 files changed, 761 insertions(+), 37 deletions(-) create mode 100644 tests/canonical_abi_tests.rs diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 5a008c0..ac4438d 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -15,30 +15,62 @@ jobs: name: Test & Invariant Verification runs-on: ubuntu-latest steps: - - name: Checkout OpenClaw + - name: Checkout Aegis Runtime Source uses: actions/checkout@v4 with: - path: openclaw-rs + path: aegis-runtime - - name: Checkout AIEN Sovereign Core Dependencies + - name: Checkout AIEN Protocols Dependency + uses: actions/checkout@v4 + with: + repository: aien-dev/aien-protocols + path: aien-protocols + + - name: Checkout AIEN Sovereign Core Dependency uses: actions/checkout@v4 with: repository: aien-dev/aien-sovereign-core path: aien-sovereign-core - - name: Install Rust Toolchain + - name: Install Stable Rust Toolchain uses: dtolnay/rust-toolchain@stable with: components: rustfmt, clippy - name: Verify Code Formatting - working-directory: openclaw-rs + working-directory: aegis-runtime run: cargo fmt --all -- --check - name: Cargo Check - working-directory: openclaw-rs + working-directory: aegis-runtime run: cargo check --all-targets - - name: Run Tests - working-directory: openclaw-rs - run: cargo test --verbose + - name: Cargo Clippy Invariant + working-directory: aegis-runtime + run: cargo clippy --all-targets -- -D warnings + + - name: Run Workspace Unit and Integration Tests + working-directory: aegis-runtime + run: cargo test --verbose -- --test-threads=1 + + - name: Enforce Zero Disk Secrets Invariant + working-directory: aegis-runtime + run: | + echo "Auditing repository for prohibited plaintext secret files..." + if find . -maxdepth 4 -name ".env*" -not -path "*/.git/*" -not -path "*/target/*" | grep -q .; then + echo "::error::Prohibited plaintext .env file found. Use an approved AIEN SecretProvider." + exit 1 + fi + echo "Zero plaintext .env files verified." + + - name: Enforce Sovereign Voice and Anti-Slop Invariant + working-directory: aegis-runtime + run: | + echo "Auditing repository for em dashes, en dashes, and formulaic tropes..." + BAD_CHARS=$(grep -rnE --exclude-dir=target --exclude-dir=.git --exclude="*.rs" --exclude="*.json" --exclude="*.safetensors" "—|–" src/ tests/ docs/ README.md || true) + if [ -n "$BAD_CHARS" ]; then + echo "::error::Em dashes or en dashes detected:" + echo "$BAD_CHARS" + exit 1 + fi + echo "Sovereign voice compliance verified." diff --git a/Cargo.lock b/Cargo.lock index c0e8a40..24e1d00 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -6,6 +6,7 @@ version = 4 name = "aegis" version = "0.2.0" dependencies = [ + "aien-action-protocol", "aien-agent-state-abi", "aien-inference-client", "aien-inference-protocol", @@ -87,7 +88,11 @@ dependencies = [ "aien-inference-protocol", "aien-protocol-types", "async-trait", + "reqwest", + "serde", + "serde_json", "thiserror 1.0.69", + "uuid", ] [[package]] @@ -119,11 +124,14 @@ name = "aien-provenance" version = "0.1.0" dependencies = [ "aien-protocol-types", + "bitflags", "hex", + "p256", "serde", "serde_json", - "sha2", + "sha2 0.10.9", "thiserror 1.0.69", + "uuid", ] [[package]] @@ -284,17 +292,32 @@ dependencies = [ "syn 2.0.119", ] +[[package]] +name = "base16ct" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fd307490d624467aa6f74b0eabb77633d1f758a7b25f12bceb0b22e08d9726f6" + [[package]] name = "base64" version = "0.22.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "72b3254f16251a8381aa12e40e3c4d2f0199f8c6508fbecb9d91f575e0fbb8c6" +[[package]] +name = "base64ct" +version = "1.8.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2af50177e190e07a26ab74f8b1efbfe2ef87da2116221318cb1c2e82baf7de06" + [[package]] name = "bitflags" version = "2.13.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3ded4057c258ba199e2d26386d3af3780957ecaee6c4ef4041c6b4b8b97c0b06" +dependencies = [ + "serde_core", +] [[package]] name = "block-buffer" @@ -305,6 +328,15 @@ dependencies = [ "generic-array", ] +[[package]] +name = "block-buffer" +version = "0.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d2f6c7dbe95a6ed67ad9f18e57daf93a2f034c524b99fd2b76d18fdfeb6660aa" +dependencies = [ + "hybrid-array", +] + [[package]] name = "bumpalo" version = "3.20.3" @@ -410,18 +442,36 @@ version = "1.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1c133bc6a41be0d194c306b5506d15e6feeea7b1d6604bd3f8310dfb2ca96486" +[[package]] +name = "cmov" +version = "0.5.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0c9ea0ac24bc397ab3c98583a3c9ba74fa56b09a4449bbe172b9b1ddb016027a" + [[package]] name = "colorchoice" version = "1.0.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1d07550c9036bf2ae0c684c4297d503f838287c83c53686d05370d0e139ae570" +[[package]] +name = "const-oid" +version = "0.10.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a6ef517f0926dd24a1582492c791b6a4818a4d94e789a334894aa15b0d12f55c" + [[package]] name = "core-foundation-sys" version = "0.8.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "773648b94d0e5d620f64f280777445740e61fe701025087ec8b57f45c791888b" +[[package]] +name = "cpubits" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "15b85f9c39137c3a891689859392b1bd49812121d0d61c9caf00d46ed5ce06ae" + [[package]] name = "cpufeatures" version = "0.2.17" @@ -440,6 +490,22 @@ dependencies = [ "libc", ] +[[package]] +name = "crypto-bigint" +version = "0.7.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1a52aa3fcda4e6302a9f48734f234d35d4721b96f8fe07d073f07ce9df4f0271" +dependencies = [ + "cpubits", + "ctutils", + "getrandom 0.4.3", + "hybrid-array", + "num-traits", + "rand_core 0.10.1", + "subtle", + "zeroize", +] + [[package]] name = "crypto-common" version = "0.1.7" @@ -450,6 +516,17 @@ dependencies = [ "typenum", ] +[[package]] +name = "crypto-common" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ce6e4c961d6cd6c9a86db418387425e8bdeaf05b3c8bc1411e6dca4c252f1453" +dependencies = [ + "getrandom 0.4.3", + "hybrid-array", + "rand_core 0.10.1", +] + [[package]] name = "csv" version = "1.4.0" @@ -471,12 +548,33 @@ dependencies = [ "memchr", ] +[[package]] +name = "ctutils" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7d5515a3834141de9eafb9717ad39eea8247b5674e6066c404e8c4b365d2a29e" +dependencies = [ + "cmov", + "subtle", +] + [[package]] name = "data-encoding" version = "2.11.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4583a4551df46e2792f82ceeac45e850d2e2d5debba0b91f102385cda5b11f06" +[[package]] +name = "der" +version = "0.8.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a878c850e9e421b20262e9b41f9c860e4785fa07541c266b62ff9d1ef998a80a" +dependencies = [ + "const-oid", + "pem-rfc7468", + "zeroize", +] + [[package]] name = "deranged" version = "0.5.8" @@ -489,8 +587,20 @@ version = "0.10.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9ed9a281f7bc9b7576e61468ba615a66a5c8cfdff42420a70aa82701a3b1e292" dependencies = [ - "block-buffer", - "crypto-common", + "block-buffer 0.10.4", + "crypto-common 0.1.7", +] + +[[package]] +name = "digest" +version = "0.11.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f1dd6dbb5841937940781866fa1281a1ff7bd3bf827091440879f9994983d5c2" +dependencies = [ + "block-buffer 0.12.1", + "const-oid", + "crypto-common 0.2.2", + "ctutils", ] [[package]] @@ -504,6 +614,42 @@ dependencies = [ "syn 3.0.6", ] +[[package]] +name = "ecdsa" +version = "0.17.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c0681a4fc24c767085329728d8dfba959af91228aa4610cca4f8ce317ba46ae0" +dependencies = [ + "der", + "digest 0.11.3", + "elliptic-curve", + "rfc6979", + "signature", + "spki", + "zeroize", +] + +[[package]] +name = "elliptic-curve" +version = "0.14.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9d65aa39b3a5c1c9c1b745c9a019234bb7a21b77abcb4f4d266d706e2d577d65" +dependencies = [ + "base16ct", + "crypto-bigint", + "crypto-common 0.2.2", + "digest 0.11.3", + "ff", + "group", + "hybrid-array", + "pem-rfc7468", + "pkcs8", + "rand_core 0.10.1", + "sec1", + "subtle", + "zeroize", +] + [[package]] name = "errno" version = "0.3.14" @@ -532,6 +678,16 @@ version = "2.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "da7c62ceae207dd37ea5b845da6a0696c799f85e97da1ab5b7910be3c1c80223" +[[package]] +name = "ff" +version = "0.14.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a1f686ab92a9fb0eaf188f6c6c87b89490baa6fdb0db4544ba4dc47f7942489f" +dependencies = [ + "rand_core 0.10.1", + "subtle", +] + [[package]] name = "find-msvc-tools" version = "0.1.13" @@ -644,6 +800,17 @@ dependencies = [ "wasm-bindgen", ] +[[package]] +name = "group" +version = "0.14.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7fd1a1c7a5206c5b7a3f5a0d7ccd3ff85d0c8f5133d62a02680255b0004af5f4" +dependencies = [ + "ff", + "rand_core 0.10.1", + "subtle", +] + [[package]] name = "hashbrown" version = "0.14.5" @@ -674,6 +841,15 @@ version = "0.4.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70" +[[package]] +name = "hmac" +version = "0.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6303bc9732ae41b04cb554b844a762b4115a61bfaa81e3e83050991eeb56863f" +dependencies = [ + "digest 0.11.3", +] + [[package]] name = "http" version = "1.5.0" @@ -719,6 +895,17 @@ version = "1.0.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "df3b46402a9d5adb4c86a0cf463f42e19994e3ee891101b1841f30a545cb49a9" +[[package]] +name = "hybrid-array" +version = "0.4.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "27f864f10dfb56725ce5ce5472bc52252c8f93a4ab86327122cebf62c5f59a17" +dependencies = [ + "subtle", + "typenum", + "zeroize", +] + [[package]] name = "hyper" version = "1.11.1" @@ -1076,6 +1263,19 @@ version = "1.70.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "384b8ab6d37215f3c5301a95a4accb5d64aa607f1fcb26a11b5303878451b4fe" +[[package]] +name = "p256" +version = "0.14.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d2c9239b2dbc807adbbe147e8cf72ea7450c3a0aabe62cb8e75ff4ec22e1f72a" +dependencies = [ + "ecdsa", + "elliptic-curve", + "primefield", + "primeorder", + "sha2 0.11.0", +] + [[package]] name = "parking_lot" version = "0.12.5" @@ -1099,6 +1299,15 @@ dependencies = [ "windows-link", ] +[[package]] +name = "pem-rfc7468" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a6305423e0e7738146434843d1694d621cce767262b2a86910beab705e4493d9" +dependencies = [ + "base64ct", +] + [[package]] name = "percent-encoding" version = "2.3.2" @@ -1111,6 +1320,16 @@ version = "0.2.17" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a89322df9ebe1c1578d689c92318e070967d1042b512afbe49518723f4e6d5cd" +[[package]] +name = "pkcs8" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "451913da69c775a56034ea8d9003d27ee8948e12443eae7c038ba100a4f21cb7" +dependencies = [ + "der", + "spki", +] + [[package]] name = "pkg-config" version = "0.3.34" @@ -1141,6 +1360,33 @@ dependencies = [ "zerocopy", ] +[[package]] +name = "primefield" +version = "0.14.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c555a6e4eb7d4e158fcb028c835c3b8642206ddc279b5c6b202ef9a8bdb592f4" +dependencies = [ + "crypto-bigint", + "crypto-common 0.2.2", + "ff", + "rand_core 0.10.1", + "subtle", + "zeroize", +] + +[[package]] +name = "primeorder" +version = "0.14.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5c9f42978c78a00e3d68f69fc03e57a234debae69da4020a4fb588fcdcd07b06" +dependencies = [ + "elliptic-curve", + "once_cell", + "primefield", + "serdect", + "wnaf", +] + [[package]] name = "proc-macro2" version = "1.0.107" @@ -1344,6 +1590,16 @@ dependencies = [ "webpki-roots", ] +[[package]] +name = "rfc6979" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b4a459cddafb3fe76b31fd8f1108007566c40301feb64dc7b54656eb7388172b" +dependencies = [ + "crypto-bigint", + "hmac", +] + [[package]] name = "ring" version = "0.17.14" @@ -1450,6 +1706,20 @@ version = "1.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" +[[package]] +name = "sec1" +version = "0.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d56d437c2f19203ce5f7122e507831de96f3d2d4d3be5af44a0b0a09d8a80e4d" +dependencies = [ + "base16ct", + "ctutils", + "der", + "hybrid-array", + "subtle", + "zeroize", +] + [[package]] name = "serde" version = "1.0.229" @@ -1516,6 +1786,16 @@ dependencies = [ "serde", ] +[[package]] +name = "serdect" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "66cf8fedced2fcf12406bcb34223dffb92eaf34908ede12fed414c82b7f00b3e" +dependencies = [ + "base16ct", + "serde", +] + [[package]] name = "sha1" version = "0.10.7" @@ -1524,9 +1804,15 @@ checksum = "a978451301f4db1d02937a4ab3ccce137717b81826e79b7d49ffe3244a13c3b8" dependencies = [ "cfg-if", "cpufeatures 0.2.17", - "digest", + "digest 0.10.7", ] +[[package]] +name = "sha1_smol" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bbfa15b3dddfee50a0fff136974b3e1bde555604ba463834a7eb7deb6417705d" + [[package]] name = "sha2" version = "0.10.9" @@ -1535,7 +1821,18 @@ checksum = "a7507d819769d01a365ab707794a4084392c824f54a7a6a7862f8c3d0892b283" dependencies = [ "cfg-if", "cpufeatures 0.2.17", - "digest", + "digest 0.10.7", +] + +[[package]] +name = "sha2" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "446ba717509524cb3f22f17ecc096f10f4822d76ab5c0b9822c5f9c284e825f4" +dependencies = [ + "cfg-if", + "cpufeatures 0.3.1", + "digest 0.11.3", ] [[package]] @@ -1563,6 +1860,16 @@ dependencies = [ "libc", ] +[[package]] +name = "signature" +version = "3.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "28d567dcbaf0049cb8ac2608a76cd95ff9e4412e1899d389ee400918ca7537f5" +dependencies = [ + "digest 0.11.3", + "rand_core 0.10.1", +] + [[package]] name = "slab" version = "0.4.12" @@ -1585,6 +1892,16 @@ dependencies = [ "windows-sys 0.61.2", ] +[[package]] +name = "spki" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1d9efca8738c78ee9484207732f728b1ef517bbb1833d6fc0879ca898a522f6f" +dependencies = [ + "base64ct", + "der", +] + [[package]] name = "stable_deref_trait" version = "1.2.1" @@ -2034,6 +2351,7 @@ dependencies = [ "getrandom 0.4.3", "js-sys", "serde_core", + "sha1_smol", "wasm-bindgen", ] @@ -2308,6 +2626,18 @@ version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec" +[[package]] +name = "wnaf" +version = "0.14.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "795ca18b3fdb5e62bf982199278341ddcf7ebf7d32e25e212ad05d496e95f6fa" +dependencies = [ + "ff", + "group", + "hybrid-array", + "primefield", +] + [[package]] name = "writeable" version = "0.6.4" diff --git a/Cargo.toml b/Cargo.toml index b11870e..9271f19 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -22,7 +22,7 @@ chrono = { version = "0.4", features = ["serde"] } clap = { version = "4.5", features = ["derive"] } rusqlite = { version = "0.31", features = ["bundled", "bundled-full"] } futures-util = "0.3" -uuid = { version = "1.8", features = ["v4", "serde"] } +uuid = { version = "1.8", features = ["v4", "v5", "serde"] } libloading = "0.8" anyhow = "1.0.104" async-trait = "0.1" @@ -31,6 +31,7 @@ bytes = "1.6" # Canonical AIEN Protocol Crates aien-protocol-types = { path = "../aien-protocols/crates/aien-protocol-types" } +aien-action-protocol = { path = "../aien-protocols/crates/aien-action-protocol" } aien-agent-state-abi = { path = "../aien-protocols/crates/aien-agent-state-abi" } aien-inference-protocol = { path = "../aien-protocols/crates/aien-inference-protocol" } aien-inference-client = { path = "../aien-protocols/crates/aien-inference-client" } diff --git a/src/events/event.rs b/src/events/event.rs index 6e9221a..3ccb0b9 100644 --- a/src/events/event.rs +++ b/src/events/event.rs @@ -67,3 +67,53 @@ pub enum AegisEvent { reason: String, }, } + +impl AegisEvent { + pub fn to_canonical_agent_event(&self) -> Option { + use crate::sessions::id::{ActionId, ApprovalId, RunId, SessionId}; + use aien_agent_state_abi::AgentEvent; + + match self { + AegisEvent::SessionCreated { .. } => Some(AgentEvent::SessionCreated), + AegisEvent::SessionClosed { .. } => Some(AgentEvent::SessionClosed), + AegisEvent::SessionBranched { session_id, .. } => { + let sid: aien_protocol_types::SessionId = SessionId::from_string(session_id).into(); + Some(AgentEvent::ContextBranched(sid.0)) + } + AegisEvent::RunStarted { run_id, .. } => { + let rid: aien_protocol_types::RunId = RunId::from_string(run_id).into(); + Some(AgentEvent::RunStarted(rid)) + } + AegisEvent::RunCompleted { run_id, .. } => { + Some(AgentEvent::RunCompleted(run_id.clone())) + } + AegisEvent::ActionRequested { action_id, .. } => { + let aid: aien_protocol_types::ActionId = ActionId::from_string(action_id).into(); + Some(AgentEvent::ActionRequested(aid)) + } + AegisEvent::ActionAuthorized { action_id, .. } => { + let aid: aien_protocol_types::ActionId = ActionId::from_string(action_id).into(); + Some(AgentEvent::ActionAuthorized(aid)) + } + AegisEvent::ActionDenied { action_id, .. } => { + let aid: aien_protocol_types::ActionId = ActionId::from_string(action_id).into(); + Some(AgentEvent::ActionDenied(aid)) + } + AegisEvent::ActionCompleted { action_id, .. } => { + let aid: aien_protocol_types::ActionId = ActionId::from_string(action_id).into(); + Some(AgentEvent::ActionCompleted(aid)) + } + AegisEvent::ApprovalRequired { approval_id, .. } => { + let apid: aien_protocol_types::ApprovalId = + ApprovalId::from_string(approval_id).into(); + Some(AgentEvent::ApprovalRequested(apid)) + } + AegisEvent::ApprovalResolved { approval_id, .. } => { + let apid: aien_protocol_types::ApprovalId = + ApprovalId::from_string(approval_id).into(); + Some(AgentEvent::ApprovalResolved(apid)) + } + _ => None, + } + } +} diff --git a/src/events/id.rs b/src/events/id.rs index 4af0c6d..2c37a51 100644 --- a/src/events/id.rs +++ b/src/events/id.rs @@ -30,3 +30,23 @@ impl fmt::Display for EventId { write!(f, "{}", self.0) } } + +impl From for aien_protocol_types::EventId { + fn from(e: EventId) -> Self { + let raw = e.as_str().strip_prefix("evt_").unwrap_or(e.as_str()); + if let Ok(u) = uuid::Uuid::parse_str(raw) { + aien_protocol_types::EventId(u) + } else { + aien_protocol_types::EventId(uuid::Uuid::new_v5( + &uuid::Uuid::NAMESPACE_OID, + e.as_str().as_bytes(), + )) + } + } +} + +impl From for EventId { + fn from(id: aien_protocol_types::EventId) -> Self { + Self::from_string(format!("evt_{}", id.0.simple())) + } +} diff --git a/src/inference.rs b/src/inference.rs index e3d0c93..b777d56 100644 --- a/src/inference.rs +++ b/src/inference.rs @@ -3,9 +3,7 @@ //! eliminating the localhost HTTP inference daemon requirement. use aien_inference_client::MockInferenceClient; -use aien_inference_protocol::{ - InferenceMessage, InferenceRequest, InferenceService, -}; +use aien_inference_protocol::{InferenceMessage, InferenceRequest, InferenceService}; use async_trait::async_trait; use futures_util::StreamExt; use reqwest::Client; @@ -439,7 +437,9 @@ impl InferenceEngine for ProtocolInferenceBackend { temperature: Option, max_tokens: Option, ) -> Result> { - let content = self.generate_chat(messages, temperature, max_tokens).await?; + let content = self + .generate_chat(messages, temperature, max_tokens) + .await?; let (clean_content, tool_calls) = parse_structured_tool_calls(&content); Ok(ChatTurnResponse { content: clean_content, @@ -455,7 +455,9 @@ impl InferenceEngine for ProtocolInferenceBackend { temperature: Option, max_tokens: Option, ) -> Result> { - let content = self.generate_chat(messages, temperature, max_tokens).await?; + let content = self + .generate_chat(messages, temperature, max_tokens) + .await?; let (tx, rx) = tokio::sync::mpsc::channel(10); tokio::spawn(async move { let chunk_json = serde_json::json!({ @@ -467,11 +469,14 @@ impl InferenceEngine for ProtocolInferenceBackend { "finish_reason": null }] }); - let payload = format!("data: {} + let payload = format!( + "data: {} data: [DONE] -", chunk_json); +", + chunk_json + ); let _ = tx.send(Ok(bytes::Bytes::from(payload))).await; }); let stream = tokio_stream::wrappers::ReceiverStream::new(rx); @@ -539,7 +544,9 @@ impl InferenceEngine for EmbeddedInferenceBackend { temperature: Option, max_tokens: Option, ) -> Result> { - self.inner.generate_chat(messages, temperature, max_tokens).await + self.inner + .generate_chat(messages, temperature, max_tokens) + .await } async fn generate_chat_with_tools( @@ -560,7 +567,9 @@ impl InferenceEngine for EmbeddedInferenceBackend { temperature: Option, max_tokens: Option, ) -> Result> { - self.inner.stream_chat(messages, temperature, max_tokens).await + self.inner + .stream_chat(messages, temperature, max_tokens) + .await } async fn check_health(&self) -> bool { @@ -568,7 +577,6 @@ impl InferenceEngine for EmbeddedInferenceBackend { } } - /// Formats conversation turns and tool specifications into canonical TinyLlama chat template: /// `<|system|>\n{system}\n<|user|>\n{user}\n<|assistant|>\n` pub fn format_messages_to_prompt( @@ -746,12 +754,6 @@ fn extract_tool_call_from_value(val: &serde_json::Value) -> Option }) } - - - - - - #[cfg(test)] mod tests { use super::*; diff --git a/src/lib.rs b/src/lib.rs index acfb026..a6fa224 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -20,8 +20,8 @@ pub use execution::{Action, ActionReceipt, ActionRequest, ActionStatus, Executio pub use gateway::{create_router, start_gateway, GatewayState, HealthResponse}; pub use heartbeat::{HeartbeatEngine, PulseReceipt}; pub use inference::{ - ChatStream, ChatTurnResponse, EmbeddedInferenceBackend, ProtocolInferenceBackend, HttpInferenceBackend, - InferenceEngine, ToolCallFunction, ToolCallItem, + ChatStream, ChatTurnResponse, EmbeddedInferenceBackend, HttpInferenceBackend, InferenceEngine, + ProtocolInferenceBackend, ToolCallFunction, ToolCallItem, }; pub use mojo_bridge::MojoSimdBridge; pub use orchestration::{Run, RunBudget, RunState, TerminationReason, Trigger}; @@ -37,3 +37,10 @@ pub use sessions::{ }; pub use skills::{SkillDefinition, SkillExecutionRequest, SkillExecutionResponse, SkillRegistry}; pub use vault::{VaultResolver, REDACTED_MARKER}; + +// Re-export Canonical AIEN Agent State ABI and Protocol Types +pub use aien_agent_state_abi::{ + AgentEvent, AgentIdentity, AgentState, AgentStateEvent, ContextState, ExecutionState, + Objective, ResourceBudget, SessionState, StateRef, +}; +pub use aien_protocol_types::{AgentId, Digest32, ProtocolVersion, SequenceNumber, Timestamp}; diff --git a/src/sessions/id.rs b/src/sessions/id.rs index df3cd71..b05ad4b 100644 --- a/src/sessions/id.rs +++ b/src/sessions/id.rs @@ -40,3 +40,83 @@ define_id!(RunId, "run"); define_id!(ActionId, "act"); define_id!(ApprovalId, "appr"); define_id!(MessageId, "msg"); + +impl From for aien_protocol_types::SessionId { + fn from(s: SessionId) -> Self { + let raw = s.as_str().strip_prefix("sess_").unwrap_or(s.as_str()); + if let Ok(u) = uuid::Uuid::parse_str(raw) { + aien_protocol_types::SessionId(u) + } else { + aien_protocol_types::SessionId(uuid::Uuid::new_v5( + &uuid::Uuid::NAMESPACE_OID, + s.as_str().as_bytes(), + )) + } + } +} + +impl From for SessionId { + fn from(id: aien_protocol_types::SessionId) -> Self { + Self::from_string(format!("sess_{}", id.0.simple())) + } +} + +impl From for aien_protocol_types::RunId { + fn from(r: RunId) -> Self { + let raw = r.as_str().strip_prefix("run_").unwrap_or(r.as_str()); + if let Ok(u) = uuid::Uuid::parse_str(raw) { + aien_protocol_types::RunId(u) + } else { + aien_protocol_types::RunId(uuid::Uuid::new_v5( + &uuid::Uuid::NAMESPACE_OID, + r.as_str().as_bytes(), + )) + } + } +} + +impl From for RunId { + fn from(id: aien_protocol_types::RunId) -> Self { + Self::from_string(format!("run_{}", id.0.simple())) + } +} + +impl From for aien_protocol_types::ActionId { + fn from(a: ActionId) -> Self { + let raw = a.as_str().strip_prefix("act_").unwrap_or(a.as_str()); + if let Ok(u) = uuid::Uuid::parse_str(raw) { + aien_protocol_types::ActionId(u) + } else { + aien_protocol_types::ActionId(uuid::Uuid::new_v5( + &uuid::Uuid::NAMESPACE_OID, + a.as_str().as_bytes(), + )) + } + } +} + +impl From for ActionId { + fn from(id: aien_protocol_types::ActionId) -> Self { + Self::from_string(format!("act_{}", id.0.simple())) + } +} + +impl From for aien_protocol_types::ApprovalId { + fn from(a: ApprovalId) -> Self { + let raw = a.as_str().strip_prefix("appr_").unwrap_or(a.as_str()); + if let Ok(u) = uuid::Uuid::parse_str(raw) { + aien_protocol_types::ApprovalId(u) + } else { + aien_protocol_types::ApprovalId(uuid::Uuid::new_v5( + &uuid::Uuid::NAMESPACE_OID, + a.as_str().as_bytes(), + )) + } + } +} + +impl From for ApprovalId { + fn from(id: aien_protocol_types::ApprovalId) -> Self { + Self::from_string(format!("appr_{}", id.0.simple())) + } +} diff --git a/src/sessions/session.rs b/src/sessions/session.rs index 4c71d59..793df04 100644 --- a/src/sessions/session.rs +++ b/src/sessions/session.rs @@ -99,3 +99,80 @@ impl Session { } } } + +impl Session { + pub fn to_agent_state( + &self, + agent_id: &aien_protocol_types::AgentId, + model_profile: &str, + ) -> aien_agent_state_abi::AgentState { + use aien_agent_state_abi::*; + use aien_protocol_types::*; + + let closed_at = if self.status == SessionStatus::Closed { + Some(Timestamp::from_micros(self.updated_at as u64 * 1_000_000)) + } else { + None + }; + + AgentState { + abi_version: ProtocolVersion::new(1, 0), + agent: AgentIdentity { + agent_id: *agent_id, + name: self.agent_profile.clone(), + model_profile: model_profile.to_string(), + created_at: Timestamp::from_micros(self.created_at as u64 * 1_000_000), + }, + session: SessionState { + session_id: self.id.clone().into(), + title: self + .objective + .clone() + .unwrap_or_else(|| "AEGIS Session".to_string()), + created_at: Timestamp::from_micros(self.created_at as u64 * 1_000_000), + closed_at, + }, + objective: self.objective.as_ref().map(|obj| Objective { + objective_id: uuid::Uuid::new_v4(), + description: obj.clone(), + success_criteria: Vec::new(), + }), + context: ContextState { + conversation: Vec::new(), + pinned: Vec::new(), + summaries: Vec::new(), + cortex: Vec::new(), + inference: None, + }, + authority: AuthorityState { + principal: uuid::Uuid::new_v4(), + policy_profile: self.policy_profile.clone(), + capabilities: Vec::new(), + restrictions: Vec::new(), + containment: aien_action_protocol::ContainmentState::default(), + }, + budget: ResourceBudget { + max_tokens: Some(self.budget.max_total_tokens), + max_cost_micro_usd: None, + max_duration_seconds: Some(self.budget.max_wall_time_secs), + max_subagents: None, + }, + execution: ExecutionState { + active_run: None, + status: match self.status { + SessionStatus::Active => AgentExecutionStatus::Idle, + SessionStatus::Suspended => AgentExecutionStatus::Suspended, + SessionStatus::Closed => AgentExecutionStatus::Terminated, + }, + pending_actions: Vec::new(), + pending_approvals: Vec::new(), + last_event: None, + }, + parent: self.parent_session_id.as_ref().map(|p| StateRef { + state_id: aien_protocol_types::SessionId::from(p.clone()).0, + digest: Digest32::ZERO, + }), + sequence: SequenceNumber(self.version), + } + } +} diff --git a/tests/canonical_abi_tests.rs b/tests/canonical_abi_tests.rs new file mode 100644 index 0000000..7425239 --- /dev/null +++ b/tests/canonical_abi_tests.rs @@ -0,0 +1,128 @@ +use aegis::{ + ActionId, AegisEvent, AgentEvent, AgentId, AgentState, ApprovalId, Digest32, EventId, + ProtocolVersion, RunId, Session, SessionId, +}; + +#[test] +fn test_aegis_ids_bidirectional_conversion_with_protocol_types() { + let sess_id = SessionId::new(); + let canon_sess: aien_protocol_types::SessionId = sess_id.clone().into(); + let back_sess: SessionId = canon_sess.into(); + assert!(back_sess.as_str().starts_with("sess_")); + + let run_id = RunId::new(); + let canon_run: aien_protocol_types::RunId = run_id.clone().into(); + let back_run: RunId = canon_run.into(); + assert!(back_run.as_str().starts_with("run_")); + + let act_id = ActionId::new(); + let canon_act: aien_protocol_types::ActionId = act_id.clone().into(); + let back_act: ActionId = canon_act.into(); + assert!(back_act.as_str().starts_with("act_")); + + let appr_id = ApprovalId::new(); + let canon_appr: aien_protocol_types::ApprovalId = appr_id.clone().into(); + let back_appr: ApprovalId = canon_appr.into(); + assert!(back_appr.as_str().starts_with("appr_")); + + let evt_id = EventId::new(); + let canon_evt: aien_protocol_types::EventId = evt_id.clone().into(); + let back_evt: EventId = canon_evt.into(); + assert!(back_evt.as_str().starts_with("evt_")); +} + +#[test] +fn test_aegis_session_to_canonical_agent_state_and_digest() { + let mut session = Session::new(Some("Audit and verify canonical ABI migration".to_string())); + session.budget.max_total_tokens = 8192; + + let agent_id = AgentId::new_v4(); + let state: AgentState = session.to_agent_state(&agent_id, "GB10-MAX-Qwen"); + + assert_eq!(state.abi_version, ProtocolVersion::new(1, 0)); + assert_eq!(state.agent.agent_id, agent_id); + assert_eq!(state.agent.model_profile, "GB10-MAX-Qwen"); + assert_eq!(state.session.session_id, session.id.clone().into()); + assert_eq!( + state.objective.as_ref().unwrap().description, + "Audit and verify canonical ABI migration" + ); + assert_eq!(state.budget.max_tokens, Some(8192)); + assert!(state.parent.is_none()); + + let digest1 = state + .compute_digest() + .expect("must compute deterministic digest"); + let digest2 = state + .compute_digest() + .expect("must compute deterministic digest"); + assert_eq!(digest1, digest2); + assert_ne!(digest1, Digest32::ZERO); +} + +#[test] +fn test_canonical_agent_state_branching_lineage() { + let parent = Session::new(Some("Parent primary execution context".to_string())); + let child = parent.branch(); + + let agent_id = AgentId::new_v4(); + let parent_state = parent.to_agent_state(&agent_id, "GB10-MAX-Qwen"); + let child_state = child.to_agent_state(&agent_id, "GB10-MAX-Qwen"); + + assert!(parent_state.parent.is_none()); + assert!(child_state.parent.is_some()); + + let parent_canon_id: aien_protocol_types::SessionId = parent.id.into(); + assert_eq!( + child_state.parent.as_ref().unwrap().state_id, + parent_canon_id.0 + ); +} + +#[test] +fn test_aegis_event_to_canonical_agent_event_mapping() { + let created = AegisEvent::SessionCreated { + session_id: "sess_test1".to_string(), + objective: Some("Objective".to_string()), + }; + assert_eq!( + created.to_canonical_agent_event(), + Some(AgentEvent::SessionCreated) + ); + + let closed = AegisEvent::SessionClosed { + session_id: "sess_test1".to_string(), + }; + assert_eq!( + closed.to_canonical_agent_event(), + Some(AgentEvent::SessionClosed) + ); + + let branched = AegisEvent::SessionBranched { + session_id: "sess_child".to_string(), + parent_session_id: "sess_parent".to_string(), + }; + match branched.to_canonical_agent_event() { + Some(AgentEvent::ContextBranched(_)) => {} + other => panic!("Expected ContextBranched, got {:?}", other), + } + + let run_started = AegisEvent::RunStarted { + run_id: "run_alpha".to_string(), + session_id: "sess_alpha".to_string(), + trigger: "UserMessage".to_string(), + }; + match run_started.to_canonical_agent_event() { + Some(AgentEvent::RunStarted(_)) => {} + other => panic!("Expected RunStarted, got {:?}", other), + } + + let act_req = AegisEvent::ActionRequested { + action_id: "act_101".to_string(), + capability: "shell.execute".to_string(), + }; + match act_req.to_canonical_agent_event() { + Some(AgentEvent::ActionRequested(_)) => {} + other => panic!("Expected ActionRequested, got {:?}", other), + } +} diff --git a/tests/embedded_inference_tests.rs b/tests/embedded_inference_tests.rs index e6b8553..405d7bb 100644 --- a/tests/embedded_inference_tests.rs +++ b/tests/embedded_inference_tests.rs @@ -62,10 +62,7 @@ async fn test_embedded_structured_tool_calling_flow() { ```"#; let (content, tool_calls) = parse_structured_tool_calls(simulated_model_output); - assert_eq!( - content.as_deref(), - Some("I need to check system health.") - ); + assert_eq!(content.as_deref(), Some("I need to check system health.")); assert!( tool_calls.is_some(), "Tool call must be extracted from model output"