yaab-core 0.1.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,315 @@
1
+ # This file is automatically @generated by Cargo.
2
+ # It is not intended for manual editing.
3
+ version = 4
4
+
5
+ [[package]]
6
+ name = "autocfg"
7
+ version = "1.5.1"
8
+ source = "registry+https://github.com/rust-lang/crates.io-index"
9
+ checksum = "f2032f911046de80f0a198e0901378627c33f59ea0ac00e363d481118bd70a53"
10
+
11
+ [[package]]
12
+ name = "block-buffer"
13
+ version = "0.10.4"
14
+ source = "registry+https://github.com/rust-lang/crates.io-index"
15
+ checksum = "3078c7629b62d3f0439517fa394996acacc5cbc91c5a20d8c658e77abd503a71"
16
+ dependencies = [
17
+ "generic-array",
18
+ ]
19
+
20
+ [[package]]
21
+ name = "cfg-if"
22
+ version = "1.0.4"
23
+ source = "registry+https://github.com/rust-lang/crates.io-index"
24
+ checksum = "9330f8b2ff13f34540b44e946ef35111825727b38d33286ef986142615121801"
25
+
26
+ [[package]]
27
+ name = "cpufeatures"
28
+ version = "0.2.17"
29
+ source = "registry+https://github.com/rust-lang/crates.io-index"
30
+ checksum = "59ed5838eebb26a2bb2e58f6d5b5316989ae9d08bab10e0e6d103e656d1b0280"
31
+ dependencies = [
32
+ "libc",
33
+ ]
34
+
35
+ [[package]]
36
+ name = "crypto-common"
37
+ version = "0.1.7"
38
+ source = "registry+https://github.com/rust-lang/crates.io-index"
39
+ checksum = "78c8292055d1c1df0cce5d180393dc8cce0abec0a7102adb6c7b1eef6016d60a"
40
+ dependencies = [
41
+ "generic-array",
42
+ "typenum",
43
+ ]
44
+
45
+ [[package]]
46
+ name = "digest"
47
+ version = "0.10.7"
48
+ source = "registry+https://github.com/rust-lang/crates.io-index"
49
+ checksum = "9ed9a281f7bc9b7576e61468ba615a66a5c8cfdff42420a70aa82701a3b1e292"
50
+ dependencies = [
51
+ "block-buffer",
52
+ "crypto-common",
53
+ ]
54
+
55
+ [[package]]
56
+ name = "generic-array"
57
+ version = "0.14.7"
58
+ source = "registry+https://github.com/rust-lang/crates.io-index"
59
+ checksum = "85649ca51fd72272d7821adaf274ad91c288277713d9c18820d8499a7ff69e9a"
60
+ dependencies = [
61
+ "typenum",
62
+ "version_check",
63
+ ]
64
+
65
+ [[package]]
66
+ name = "heck"
67
+ version = "0.5.0"
68
+ source = "registry+https://github.com/rust-lang/crates.io-index"
69
+ checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea"
70
+
71
+ [[package]]
72
+ name = "indoc"
73
+ version = "2.0.7"
74
+ source = "registry+https://github.com/rust-lang/crates.io-index"
75
+ checksum = "79cf5c93f93228cf8efb3ba362535fb11199ac548a09ce117c9b1adc3030d706"
76
+ dependencies = [
77
+ "rustversion",
78
+ ]
79
+
80
+ [[package]]
81
+ name = "itoa"
82
+ version = "1.0.18"
83
+ source = "registry+https://github.com/rust-lang/crates.io-index"
84
+ checksum = "8f42a60cbdf9a97f5d2305f08a87dc4e09308d1276d28c869c684d7777685682"
85
+
86
+ [[package]]
87
+ name = "libc"
88
+ version = "0.2.186"
89
+ source = "registry+https://github.com/rust-lang/crates.io-index"
90
+ checksum = "68ab91017fe16c622486840e4c83c9a37afeff978bd239b5293d61ece587de66"
91
+
92
+ [[package]]
93
+ name = "memchr"
94
+ version = "2.8.1"
95
+ source = "registry+https://github.com/rust-lang/crates.io-index"
96
+ checksum = "6b947ae49db0d222b1dbc6b113ce7248a3fc3a6ca21b696717bfc000ba4484d8"
97
+
98
+ [[package]]
99
+ name = "memoffset"
100
+ version = "0.9.1"
101
+ source = "registry+https://github.com/rust-lang/crates.io-index"
102
+ checksum = "488016bfae457b036d996092f6cb448677611ce4449e970ceaf42695203f218a"
103
+ dependencies = [
104
+ "autocfg",
105
+ ]
106
+
107
+ [[package]]
108
+ name = "once_cell"
109
+ version = "1.21.4"
110
+ source = "registry+https://github.com/rust-lang/crates.io-index"
111
+ checksum = "9f7c3e4beb33f85d45ae3e3a1792185706c8e16d043238c593331cc7cd313b50"
112
+
113
+ [[package]]
114
+ name = "portable-atomic"
115
+ version = "1.13.1"
116
+ source = "registry+https://github.com/rust-lang/crates.io-index"
117
+ checksum = "c33a9471896f1c69cecef8d20cbe2f7accd12527ce60845ff44c153bb2a21b49"
118
+
119
+ [[package]]
120
+ name = "proc-macro2"
121
+ version = "1.0.106"
122
+ source = "registry+https://github.com/rust-lang/crates.io-index"
123
+ checksum = "8fd00f0bb2e90d81d1044c2b32617f68fcb9fa3bb7640c23e9c748e53fb30934"
124
+ dependencies = [
125
+ "unicode-ident",
126
+ ]
127
+
128
+ [[package]]
129
+ name = "pyo3"
130
+ version = "0.23.5"
131
+ source = "registry+https://github.com/rust-lang/crates.io-index"
132
+ checksum = "7778bffd85cf38175ac1f545509665d0b9b92a198ca7941f131f85f7a4f9a872"
133
+ dependencies = [
134
+ "cfg-if",
135
+ "indoc",
136
+ "libc",
137
+ "memoffset",
138
+ "once_cell",
139
+ "portable-atomic",
140
+ "pyo3-build-config",
141
+ "pyo3-ffi",
142
+ "pyo3-macros",
143
+ "unindent",
144
+ ]
145
+
146
+ [[package]]
147
+ name = "pyo3-build-config"
148
+ version = "0.23.5"
149
+ source = "registry+https://github.com/rust-lang/crates.io-index"
150
+ checksum = "94f6cbe86ef3bf18998d9df6e0f3fc1050a8c5efa409bf712e661a4366e010fb"
151
+ dependencies = [
152
+ "once_cell",
153
+ "target-lexicon",
154
+ ]
155
+
156
+ [[package]]
157
+ name = "pyo3-ffi"
158
+ version = "0.23.5"
159
+ source = "registry+https://github.com/rust-lang/crates.io-index"
160
+ checksum = "e9f1b4c431c0bb1c8fb0a338709859eed0d030ff6daa34368d3b152a63dfdd8d"
161
+ dependencies = [
162
+ "libc",
163
+ "pyo3-build-config",
164
+ ]
165
+
166
+ [[package]]
167
+ name = "pyo3-macros"
168
+ version = "0.23.5"
169
+ source = "registry+https://github.com/rust-lang/crates.io-index"
170
+ checksum = "fbc2201328f63c4710f68abdf653c89d8dbc2858b88c5d88b0ff38a75288a9da"
171
+ dependencies = [
172
+ "proc-macro2",
173
+ "pyo3-macros-backend",
174
+ "quote",
175
+ "syn",
176
+ ]
177
+
178
+ [[package]]
179
+ name = "pyo3-macros-backend"
180
+ version = "0.23.5"
181
+ source = "registry+https://github.com/rust-lang/crates.io-index"
182
+ checksum = "fca6726ad0f3da9c9de093d6f116a93c1a38e417ed73bf138472cf4064f72028"
183
+ dependencies = [
184
+ "heck",
185
+ "proc-macro2",
186
+ "pyo3-build-config",
187
+ "quote",
188
+ "syn",
189
+ ]
190
+
191
+ [[package]]
192
+ name = "quote"
193
+ version = "1.0.45"
194
+ source = "registry+https://github.com/rust-lang/crates.io-index"
195
+ checksum = "41f2619966050689382d2b44f664f4bc593e129785a36d6ee376ddf37259b924"
196
+ dependencies = [
197
+ "proc-macro2",
198
+ ]
199
+
200
+ [[package]]
201
+ name = "rustversion"
202
+ version = "1.0.22"
203
+ source = "registry+https://github.com/rust-lang/crates.io-index"
204
+ checksum = "b39cdef0fa800fc44525c84ccb54a029961a8215f9619753635a9c0d2538d46d"
205
+
206
+ [[package]]
207
+ name = "serde"
208
+ version = "1.0.228"
209
+ source = "registry+https://github.com/rust-lang/crates.io-index"
210
+ checksum = "9a8e94ea7f378bd32cbbd37198a4a91436180c5bb472411e48b5ec2e2124ae9e"
211
+ dependencies = [
212
+ "serde_core",
213
+ "serde_derive",
214
+ ]
215
+
216
+ [[package]]
217
+ name = "serde_core"
218
+ version = "1.0.228"
219
+ source = "registry+https://github.com/rust-lang/crates.io-index"
220
+ checksum = "41d385c7d4ca58e59fc732af25c3983b67ac852c1a25000afe1175de458b67ad"
221
+ dependencies = [
222
+ "serde_derive",
223
+ ]
224
+
225
+ [[package]]
226
+ name = "serde_derive"
227
+ version = "1.0.228"
228
+ source = "registry+https://github.com/rust-lang/crates.io-index"
229
+ checksum = "d540f220d3187173da220f885ab66608367b6574e925011a9353e4badda91d79"
230
+ dependencies = [
231
+ "proc-macro2",
232
+ "quote",
233
+ "syn",
234
+ ]
235
+
236
+ [[package]]
237
+ name = "serde_json"
238
+ version = "1.0.150"
239
+ source = "registry+https://github.com/rust-lang/crates.io-index"
240
+ checksum = "e8014e44b4736ed0538adeecded0fce2a272f22dc9578a7eb6b2d9993c74cfb9"
241
+ dependencies = [
242
+ "itoa",
243
+ "memchr",
244
+ "serde",
245
+ "serde_core",
246
+ "zmij",
247
+ ]
248
+
249
+ [[package]]
250
+ name = "sha2"
251
+ version = "0.10.9"
252
+ source = "registry+https://github.com/rust-lang/crates.io-index"
253
+ checksum = "a7507d819769d01a365ab707794a4084392c824f54a7a6a7862f8c3d0892b283"
254
+ dependencies = [
255
+ "cfg-if",
256
+ "cpufeatures",
257
+ "digest",
258
+ ]
259
+
260
+ [[package]]
261
+ name = "syn"
262
+ version = "2.0.117"
263
+ source = "registry+https://github.com/rust-lang/crates.io-index"
264
+ checksum = "e665b8803e7b1d2a727f4023456bbbbe74da67099c585258af0ad9c5013b9b99"
265
+ dependencies = [
266
+ "proc-macro2",
267
+ "quote",
268
+ "unicode-ident",
269
+ ]
270
+
271
+ [[package]]
272
+ name = "target-lexicon"
273
+ version = "0.12.16"
274
+ source = "registry+https://github.com/rust-lang/crates.io-index"
275
+ checksum = "61c41af27dd6d1e27b1b16b489db798443478cef1f06a660c96db617ba5de3b1"
276
+
277
+ [[package]]
278
+ name = "typenum"
279
+ version = "1.20.1"
280
+ source = "registry+https://github.com/rust-lang/crates.io-index"
281
+ checksum = "b6f5e870be6c3b371b77fe0ee0bafb859fa4964b4404c27de1d380043c4dda20"
282
+
283
+ [[package]]
284
+ name = "unicode-ident"
285
+ version = "1.0.24"
286
+ source = "registry+https://github.com/rust-lang/crates.io-index"
287
+ checksum = "e6e4313cd5fcd3dad5cafa179702e2b244f760991f45397d14d4ebf38247da75"
288
+
289
+ [[package]]
290
+ name = "unindent"
291
+ version = "0.2.4"
292
+ source = "registry+https://github.com/rust-lang/crates.io-index"
293
+ checksum = "7264e107f553ccae879d21fbea1d6724ac785e8c3bfc762137959b5802826ef3"
294
+
295
+ [[package]]
296
+ name = "version_check"
297
+ version = "0.9.5"
298
+ source = "registry+https://github.com/rust-lang/crates.io-index"
299
+ checksum = "0b928f33d975fc6ad9f86c8f283853ad26bdd5b10b7f1542aa2fa15e2289105a"
300
+
301
+ [[package]]
302
+ name = "yaab-core"
303
+ version = "0.1.0"
304
+ dependencies = [
305
+ "pyo3",
306
+ "serde",
307
+ "serde_json",
308
+ "sha2",
309
+ ]
310
+
311
+ [[package]]
312
+ name = "zmij"
313
+ version = "1.0.21"
314
+ source = "registry+https://github.com/rust-lang/crates.io-index"
315
+ checksum = "b8848ee67ecc8aedbaf3e4122217aff892639231befc6a1b58d29fff4c2cabaa"
@@ -0,0 +1,27 @@
1
+ [package]
2
+ name = "yaab-core"
3
+ version = "0.1.0"
4
+ edition = "2021"
5
+ description = "Rust performance core for the YAAB agent SDK (orchestration, checkpoint serialization, vector ops)."
6
+ license = "MIT"
7
+ repository = "https://github.com/sthitaprajnas/yaab"
8
+ homepage = "https://github.com/sthitaprajnas/yaab"
9
+ readme = "README.md"
10
+
11
+ [lib]
12
+ name = "yaab_core"
13
+ # cdylib for the Python extension module; rlib so the engine can be reused by other bindings.
14
+ crate-type = ["cdylib", "rlib"]
15
+
16
+ [dependencies]
17
+ # abi3-py311: build a single stable-ABI wheel that works on CPython 3.11+ —
18
+ # including future versions (3.13, 3.14, …) — so users never fall back to the
19
+ # pure-Python core just because their interpreter is newer than our wheels.
20
+ pyo3 = { version = "0.23", features = ["extension-module", "abi3-py311"] }
21
+ serde = { version = "1", features = ["derive"] }
22
+ serde_json = "1"
23
+ sha2 = "0.10"
24
+
25
+ [profile.release]
26
+ opt-level = 3
27
+ lto = true
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 YAAB contributors
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
@@ -0,0 +1,32 @@
1
+ Metadata-Version: 2.4
2
+ Name: yaab-core
3
+ Version: 0.1.0
4
+ Classifier: License :: OSI Approved :: MIT License
5
+ Classifier: Programming Language :: Rust
6
+ Classifier: Programming Language :: Python :: 3.11
7
+ License-File: LICENSE
8
+ Summary: Rust performance core for the YAAB agent SDK.
9
+ Home-Page: https://github.com/sthitaprajnas/yaab
10
+ Author: YAAB contributors
11
+ Requires-Python: >=3.11
12
+ Description-Content-Type: text/markdown; charset=UTF-8; variant=GFM
13
+
14
+ # yaab-core
15
+
16
+ The Rust performance core for [YAAB](../README.md), exposed to Python via PyO3.
17
+
18
+ It provides the hot-path primitives the Python layer calls into:
19
+
20
+ - **vector** — cosine similarity + top-k for memory retrieval
21
+ - **checkpoint** — framed (de)serialization of graph state
22
+ - **channels** — state-channel reducers (last-value / append / add)
23
+ - **scheduler** — BSP superstep planning for the orchestration engine
24
+ - **actors** — tamper-evident hash-chaining + cost aggregation for the audit log
25
+
26
+ Every function has a pure-Python fallback in `yaab._core`, so the SDK installs
27
+ and runs without this extension. Build it for the accelerated path:
28
+
29
+ ```bash
30
+ maturin develop -m yaab-core/Cargo.toml --release
31
+ ```
32
+
@@ -0,0 +1,18 @@
1
+ # yaab-core
2
+
3
+ The Rust performance core for [YAAB](../README.md), exposed to Python via PyO3.
4
+
5
+ It provides the hot-path primitives the Python layer calls into:
6
+
7
+ - **vector** — cosine similarity + top-k for memory retrieval
8
+ - **checkpoint** — framed (de)serialization of graph state
9
+ - **channels** — state-channel reducers (last-value / append / add)
10
+ - **scheduler** — BSP superstep planning for the orchestration engine
11
+ - **actors** — tamper-evident hash-chaining + cost aggregation for the audit log
12
+
13
+ Every function has a pure-Python fallback in `yaab._core`, so the SDK installs
14
+ and runs without this extension. Build it for the accelerated path:
15
+
16
+ ```bash
17
+ maturin develop -m yaab-core/Cargo.toml --release
18
+ ```
@@ -0,0 +1,21 @@
1
+ [build-system]
2
+ requires = ["maturin>=1.5,<2.0"]
3
+ build-backend = "maturin"
4
+
5
+ [project]
6
+ name = "yaab-core"
7
+ version = "0.1.0"
8
+ description = "Rust performance core for the YAAB agent SDK."
9
+ readme = "README.md"
10
+ license = { file = "LICENSE" }
11
+ requires-python = ">=3.11"
12
+ authors = [{ name = "YAAB contributors" }]
13
+ classifiers = [
14
+ "License :: OSI Approved :: MIT License",
15
+ "Programming Language :: Rust",
16
+ "Programming Language :: Python :: 3.11",
17
+ ]
18
+
19
+ [tool.maturin]
20
+ # Pure Rust extension module: produces the importable `yaab_core` module.
21
+ module-name = "yaab_core"
@@ -0,0 +1,41 @@
1
+ //! Tamper-evident audit hashing and cost aggregation.
2
+ //!
3
+ //! The audit log is hash-chained: each entry's hash folds in the previous
4
+ //! entry's hash plus the current payload, so any retroactive edit breaks the
5
+ //! chain. Hashing the chain in Rust keeps the audit hot-path cheap.
6
+
7
+ use sha2::{Digest, Sha256};
8
+
9
+ /// Compute the chained hash for an audit entry: `sha256(prev_hash || payload)`.
10
+ pub fn hash_event(prev_hash: &str, payload: &str) -> String {
11
+ let mut hasher = Sha256::new();
12
+ hasher.update(prev_hash.as_bytes());
13
+ hasher.update(b"\x1f"); // unit separator, avoids boundary ambiguity
14
+ hasher.update(payload.as_bytes());
15
+ let digest = hasher.finalize();
16
+ let mut out = String::with_capacity(64);
17
+ for byte in digest {
18
+ out.push_str(&format!("{:02x}", byte));
19
+ }
20
+ out
21
+ }
22
+
23
+ /// Verify a chain of `(payload, recorded_hash)` entries against a genesis hash.
24
+ /// Returns the index of the first broken link, or `None` if the chain is intact.
25
+ pub fn verify_chain(genesis: &str, entries: &[(String, String)]) -> Option<usize> {
26
+ let mut prev = genesis.to_string();
27
+ for (i, (payload, recorded)) in entries.iter().enumerate() {
28
+ let expected = hash_event(&prev, payload);
29
+ if &expected != recorded {
30
+ return Some(i);
31
+ }
32
+ prev = expected;
33
+ }
34
+ None
35
+ }
36
+
37
+ /// Sum a set of per-call costs into a single total. Trivial today, but a
38
+ /// stable seam for moving cost aggregation fully into Rust later.
39
+ pub fn aggregate_cost(values: &[f64]) -> f64 {
40
+ values.iter().sum()
41
+ }
@@ -0,0 +1,94 @@
1
+ //! State channel reducers and whole-superstep state advancement.
2
+ //!
3
+ //! Channels define how a node's write to a key is merged into graph state.
4
+ //! The semantics mirror LangGraph channels: last-value (overwrite),
5
+ //! append (accumulate into a list), and add (numeric aggregation).
6
+ //!
7
+ //! `advance_superstep` applies an entire superstep's node updates against the
8
+ //! current state in a single call — the compute-bound part of graph
9
+ //! orchestration the Python engine can offload to Rust when the developer
10
+ //! opts into the native engine.
11
+
12
+ use serde_json::{Map, Value};
13
+ use std::collections::HashMap;
14
+
15
+ /// Reduce a single `current`/`update` pair at the `Value` level.
16
+ fn reduce_value(reducer: &str, current: &Value, update: Value) -> Value {
17
+ match reducer {
18
+ "append" => {
19
+ let mut list = match current {
20
+ Value::Array(items) => items.clone(),
21
+ Value::Null => Vec::new(),
22
+ other => vec![other.clone()],
23
+ };
24
+ match update {
25
+ Value::Array(items) => list.extend(items),
26
+ other => list.push(other),
27
+ }
28
+ Value::Array(list)
29
+ }
30
+ "add" => {
31
+ let a = current.as_f64().unwrap_or(0.0);
32
+ let b = update.as_f64().unwrap_or(0.0);
33
+ let sum = a + b;
34
+ if sum.fract() == 0.0 {
35
+ Value::from(sum as i64)
36
+ } else {
37
+ Value::from(sum)
38
+ }
39
+ }
40
+ // "last_value" and any unknown reducer overwrite.
41
+ _ => update,
42
+ }
43
+ }
44
+
45
+ /// Reduce `current` with `update` according to `reducer` (JSON-string API).
46
+ pub fn reduce(reducer: &str, current_json: &str, update_json: &str) -> Result<String, String> {
47
+ let update: Value = serde_json::from_str(update_json).map_err(|e| e.to_string())?;
48
+ let current: Value = serde_json::from_str(current_json).map_err(|e| e.to_string())?;
49
+ let result = reduce_value(reducer, &current, update);
50
+ serde_json::to_string(&result).map_err(|e| e.to_string())
51
+ }
52
+
53
+ /// Apply a whole superstep's node updates to `state` in one pass.
54
+ ///
55
+ /// * `state_json` — the current graph state object (JSON object).
56
+ /// * `reducers_json` — a JSON object mapping channel key -> reducer name.
57
+ /// Keys absent from this map use last-value semantics.
58
+ /// * `updates_json` — a JSON array of update objects, one per node that ran in
59
+ /// the superstep, applied in order.
60
+ ///
61
+ /// Returns the new state as a JSON object string. Doing the full fold in Rust
62
+ /// avoids one Python<->Rust round-trip per (key, node) pair.
63
+ pub fn advance_superstep(
64
+ state_json: &str,
65
+ reducers_json: &str,
66
+ updates_json: &str,
67
+ ) -> Result<String, String> {
68
+ let mut state: Map<String, Value> =
69
+ match serde_json::from_str(state_json).map_err(|e| e.to_string())? {
70
+ Value::Object(m) => m,
71
+ _ => return Err("state must be a JSON object".to_string()),
72
+ };
73
+ let reducers: HashMap<String, String> =
74
+ serde_json::from_str(reducers_json).map_err(|e| e.to_string())?;
75
+ let updates: Vec<Value> = serde_json::from_str(updates_json).map_err(|e| e.to_string())?;
76
+
77
+ for update in updates {
78
+ let obj = match update {
79
+ Value::Object(m) => m,
80
+ Value::Null => continue, // a node may return no update
81
+ _ => return Err("each node update must be a JSON object or null".to_string()),
82
+ };
83
+ for (key, value) in obj {
84
+ let reducer = reducers
85
+ .get(&key)
86
+ .map(|s| s.as_str())
87
+ .unwrap_or("last_value");
88
+ let current = state.get(&key).cloned().unwrap_or(Value::Null);
89
+ state.insert(key, reduce_value(reducer, &current, value));
90
+ }
91
+ }
92
+
93
+ serde_json::to_string(&Value::Object(state)).map_err(|e| e.to_string())
94
+ }
@@ -0,0 +1,33 @@
1
+ //! Checkpoint (de)serialization for durable graph execution.
2
+ //!
3
+ //! State snapshots are framed with a small magic header + version byte so the
4
+ //! format can evolve without ambiguity. The payload is compact JSON; the Rust
5
+ //! path validates structure on the way in and out.
6
+
7
+ use serde_json::Value;
8
+
9
+ const MAGIC: &[u8] = b"YAAB";
10
+ const VERSION: u8 = 1;
11
+
12
+ /// Encode a JSON document (passed as a string) into a framed checkpoint blob.
13
+ pub fn encode(json_str: &str) -> Result<Vec<u8>, String> {
14
+ let value: Value = serde_json::from_str(json_str).map_err(|e| e.to_string())?;
15
+ let payload = serde_json::to_vec(&value).map_err(|e| e.to_string())?;
16
+ let mut out = Vec::with_capacity(payload.len() + 5);
17
+ out.extend_from_slice(MAGIC);
18
+ out.push(VERSION);
19
+ out.extend_from_slice(&payload);
20
+ Ok(out)
21
+ }
22
+
23
+ /// Decode a framed checkpoint blob back into a compact JSON string.
24
+ pub fn decode(blob: &[u8]) -> Result<String, String> {
25
+ if blob.len() < 5 || &blob[0..4] != MAGIC {
26
+ return Err("invalid checkpoint: bad magic header".to_string());
27
+ }
28
+ if blob[4] != VERSION {
29
+ return Err(format!("unsupported checkpoint version: {}", blob[4]));
30
+ }
31
+ let value: Value = serde_json::from_slice(&blob[5..]).map_err(|e| e.to_string())?;
32
+ serde_json::to_string(&value).map_err(|e| e.to_string())
33
+ }
@@ -0,0 +1,102 @@
1
+ //! `yaab_core` — the Rust performance core for the YAAB agent SDK.
2
+ //!
3
+ //! This crate exposes a small, stable surface of hot-path primitives to
4
+ //! Python via PyO3: vector similarity for memory retrieval, framed checkpoint
5
+ //! (de)serialization for durable graph execution, channel reducers for graph
6
+ //! state, BSP superstep planning for the orchestration engine, and
7
+ //! tamper-evident hash-chaining for the audit log.
8
+ //!
9
+ //! Every function here has a pure-Python fallback in `yaab._core`, so the SDK
10
+ //! installs and runs even when this extension is unavailable. The Rust path is
11
+ //! an accelerator, never a hard dependency.
12
+
13
+ use pyo3::prelude::*;
14
+
15
+ mod actors;
16
+ mod channels;
17
+ mod checkpoint;
18
+ mod scheduler;
19
+ mod vector;
20
+
21
+ /// Cosine similarity between two equal-length vectors.
22
+ #[pyfunction]
23
+ fn cosine_similarity(a: Vec<f32>, b: Vec<f32>) -> f32 {
24
+ vector::cosine(&a, &b)
25
+ }
26
+
27
+ /// Indices and scores of the `k` rows most similar to `query`.
28
+ #[pyfunction]
29
+ fn top_k(query: Vec<f32>, matrix: Vec<Vec<f32>>, k: usize) -> Vec<(usize, f32)> {
30
+ vector::top_k(&query, &matrix, k)
31
+ }
32
+
33
+ /// Encode a JSON document string into a framed checkpoint blob.
34
+ #[pyfunction]
35
+ fn encode_checkpoint(json_str: &str) -> PyResult<Vec<u8>> {
36
+ checkpoint::encode(json_str).map_err(pyo3::exceptions::PyValueError::new_err)
37
+ }
38
+
39
+ /// Decode a framed checkpoint blob back into a JSON string.
40
+ #[pyfunction]
41
+ fn decode_checkpoint(blob: Vec<u8>) -> PyResult<String> {
42
+ checkpoint::decode(&blob).map_err(pyo3::exceptions::PyValueError::new_err)
43
+ }
44
+
45
+ /// Reduce `current` with `update` under the named channel reducer.
46
+ #[pyfunction]
47
+ fn reduce_channel(reducer: &str, current_json: &str, update_json: &str) -> PyResult<String> {
48
+ channels::reduce(reducer, current_json, update_json)
49
+ .map_err(pyo3::exceptions::PyValueError::new_err)
50
+ }
51
+
52
+ /// Apply a whole superstep's node updates to graph state in one call.
53
+ #[pyfunction]
54
+ fn advance_superstep(
55
+ state_json: &str,
56
+ reducers_json: &str,
57
+ updates_json: &str,
58
+ ) -> PyResult<String> {
59
+ channels::advance_superstep(state_json, reducers_json, updates_json)
60
+ .map_err(pyo3::exceptions::PyValueError::new_err)
61
+ }
62
+
63
+ /// Plan BSP supersteps for the given nodes and edges.
64
+ #[pyfunction]
65
+ fn plan_supersteps(nodes: Vec<String>, edges: Vec<(String, String)>) -> Vec<Vec<String>> {
66
+ scheduler::plan(&nodes, &edges)
67
+ }
68
+
69
+ /// Chained hash for an audit entry: `sha256(prev_hash || payload)`.
70
+ #[pyfunction]
71
+ fn hash_event(prev_hash: &str, payload: &str) -> String {
72
+ actors::hash_event(prev_hash, payload)
73
+ }
74
+
75
+ /// Verify a hash chain; returns the index of the first broken link or `None`.
76
+ #[pyfunction]
77
+ fn verify_chain(genesis: &str, entries: Vec<(String, String)>) -> Option<usize> {
78
+ actors::verify_chain(genesis, &entries)
79
+ }
80
+
81
+ /// Sum per-call costs into a single total.
82
+ #[pyfunction]
83
+ fn aggregate_cost(values: Vec<f64>) -> f64 {
84
+ actors::aggregate_cost(&values)
85
+ }
86
+
87
+ #[pymodule]
88
+ fn yaab_core(m: &Bound<'_, PyModule>) -> PyResult<()> {
89
+ m.add("__version__", env!("CARGO_PKG_VERSION"))?;
90
+ m.add("RUST", true)?;
91
+ m.add_function(wrap_pyfunction!(cosine_similarity, m)?)?;
92
+ m.add_function(wrap_pyfunction!(top_k, m)?)?;
93
+ m.add_function(wrap_pyfunction!(encode_checkpoint, m)?)?;
94
+ m.add_function(wrap_pyfunction!(decode_checkpoint, m)?)?;
95
+ m.add_function(wrap_pyfunction!(reduce_channel, m)?)?;
96
+ m.add_function(wrap_pyfunction!(advance_superstep, m)?)?;
97
+ m.add_function(wrap_pyfunction!(plan_supersteps, m)?)?;
98
+ m.add_function(wrap_pyfunction!(hash_event, m)?)?;
99
+ m.add_function(wrap_pyfunction!(verify_chain, m)?)?;
100
+ m.add_function(wrap_pyfunction!(aggregate_cost, m)?)?;
101
+ Ok(())
102
+ }
@@ -0,0 +1,77 @@
1
+ //! Superstep scheduler (Bulk Synchronous Parallel).
2
+ //!
3
+ //! Given a node set and directed edges, group nodes into supersteps: each
4
+ //! superstep is a set of nodes with no unresolved upstream dependency, so all
5
+ //! nodes in a superstep can execute in parallel. This mirrors the Pregel /
6
+ //! Apache Beam execution model LangGraph is built on.
7
+ //!
8
+ //! Cycles are expected (graphs support loops), so any nodes that remain after
9
+ //! the acyclic layering are emitted as a final superstep rather than treated
10
+ //! as an error — the runtime drives cycles via conditional edges at run time.
11
+
12
+ use std::collections::{HashMap, HashSet, VecDeque};
13
+
14
+ /// Plan the supersteps for a graph.
15
+ pub fn plan(nodes: &[String], edges: &[(String, String)]) -> Vec<Vec<String>> {
16
+ let node_set: HashSet<&String> = nodes.iter().collect();
17
+
18
+ let mut indegree: HashMap<&String, usize> = nodes.iter().map(|n| (n, 0usize)).collect();
19
+ let mut adj: HashMap<&String, Vec<&String>> = HashMap::new();
20
+ for (src, dst) in edges {
21
+ if !node_set.contains(src) || !node_set.contains(dst) || src == dst {
22
+ continue;
23
+ }
24
+ adj.entry(src).or_default().push(dst);
25
+ *indegree.entry(dst).or_insert(0) += 1;
26
+ }
27
+
28
+ let mut layers: Vec<Vec<String>> = Vec::new();
29
+ let mut ready: VecDeque<&String> = indegree
30
+ .iter()
31
+ .filter(|(_, &d)| d == 0)
32
+ .map(|(&n, _)| n)
33
+ .collect();
34
+ let mut visited: HashSet<&String> = HashSet::new();
35
+
36
+ while !ready.is_empty() {
37
+ let mut layer: Vec<String> = Vec::new();
38
+ let current: Vec<&String> = ready.drain(..).collect();
39
+ for node in &current {
40
+ if visited.contains(node) {
41
+ continue;
42
+ }
43
+ visited.insert(node);
44
+ layer.push((*node).clone());
45
+ }
46
+ layer.sort();
47
+ let mut next: Vec<&String> = Vec::new();
48
+ for node in &current {
49
+ if let Some(children) = adj.get(node) {
50
+ for child in children {
51
+ if let Some(d) = indegree.get_mut(*child) {
52
+ *d = d.saturating_sub(1);
53
+ if *d == 0 && !visited.contains(*child) {
54
+ next.push(child);
55
+ }
56
+ }
57
+ }
58
+ }
59
+ }
60
+ if !layer.is_empty() {
61
+ layers.push(layer);
62
+ }
63
+ ready.extend(next);
64
+ }
65
+
66
+ // Any nodes left participate in a cycle; emit them as a trailing superstep.
67
+ let mut remaining: Vec<String> = nodes
68
+ .iter()
69
+ .filter(|n| !visited.contains(n))
70
+ .cloned()
71
+ .collect();
72
+ if !remaining.is_empty() {
73
+ remaining.sort();
74
+ layers.push(remaining);
75
+ }
76
+ layers
77
+ }
@@ -0,0 +1,39 @@
1
+ //! Vector similarity operations used by `MemoryService` retrieval.
2
+ //!
3
+ //! These run outside the GIL on the Python side and operate on plain
4
+ //! `f32` slices, which keeps memory-retrieval cheap even for large stores.
5
+
6
+ /// Cosine similarity between two equal-length vectors.
7
+ ///
8
+ /// Returns 0.0 for mismatched lengths or zero-magnitude vectors so callers
9
+ /// never have to special-case degenerate input.
10
+ pub fn cosine(a: &[f32], b: &[f32]) -> f32 {
11
+ if a.len() != b.len() || a.is_empty() {
12
+ return 0.0;
13
+ }
14
+ let mut dot = 0.0f32;
15
+ let mut na = 0.0f32;
16
+ let mut nb = 0.0f32;
17
+ for i in 0..a.len() {
18
+ dot += a[i] * b[i];
19
+ na += a[i] * a[i];
20
+ nb += b[i] * b[i];
21
+ }
22
+ if na == 0.0 || nb == 0.0 {
23
+ return 0.0;
24
+ }
25
+ dot / (na.sqrt() * nb.sqrt())
26
+ }
27
+
28
+ /// Return the indices and scores of the `k` rows most similar to `query`,
29
+ /// sorted by descending cosine similarity.
30
+ pub fn top_k(query: &[f32], matrix: &[Vec<f32>], k: usize) -> Vec<(usize, f32)> {
31
+ let mut scored: Vec<(usize, f32)> = matrix
32
+ .iter()
33
+ .enumerate()
34
+ .map(|(i, row)| (i, cosine(query, row)))
35
+ .collect();
36
+ scored.sort_by(|a, b| b.1.partial_cmp(&a.1).unwrap_or(std::cmp::Ordering::Equal));
37
+ scored.truncate(k);
38
+ scored
39
+ }