From fc33c99f307fcb04995d5f7def9bad01b82353f3 Mon Sep 17 00:00:00 2001 From: ruv Date: Sat, 13 Jun 2026 13:17:26 -0400 Subject: [PATCH] feat(emergent-time): @ruvector/emergent-time WASM package for Agentic Time MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Wrap the agentic-time layer of the dependency-free `emergent-time` crate in a tiny wasm-bindgen surface for the browser, edge, and Node. - crates/emergent-time-wasm: standalone cdylib (workspace-excluded so it carries its own opt-level="z" / lto / strip / panic=abort release profile and dlmalloc global allocator, mirroring crates/rvf/rvf-wasm). Hand-rolled getters, no serde, to keep the wasm tiny. - SDK surface: AgenticClock (tick → explainable Tick{class,reason,deltaTime, per-channel}; cumulativeTime, ATI, 7-state health), StateDelta, Tick, TickClassJs, AgentHealthJs, WindowedDeltaClock + PageHinkleyDetector change-point detectors, LearnedWeights inference, version(). - Physics core (Wheeler-DeWitt / Page-Wootters / entropic / thermal / Structural Proper Time) deliberately not wrapped: dense matrices don't serialize cheaply over the JS boundary and would bloat the wasm. Documented in the README. - npm/packages/emergent-time: package.json (@ruvector/emergent-time@0.1.0, ESM, main/module/types → pkg, files include pkg + README, publishConfig public), detailed README, build.sh pipeline (cargo @1.89 → wasm-bindgen --target web → wasm-opt -Oz with bulk-memory/nontrapping-float-to-int enabled), and the built pkg/ (wasm + JS glue + .d.ts). Validation: wasm raw 62475B / opt 55009B (wasm-tools VALID); Node ESM smoke test passes end-to-end (AgenticClock Healthy→Drifting→NeedsReplan→Collapsing→ NeedsHumanReview, cumulativeTime 19.36, both detectors fire at the planted jump); tsc --noEmit --strict on a usage example against the shipped .d.ts exits 0; npm pack --dry-run lists README.md + .wasm + .js + .d.ts. Honest scope (mirrors ADR-251): the agentic clock is a diagnostic signal; it does not establish an early-warning lead over a fair baseline on real traces. Both fair baselines (windowed z-score, Page-Hinkley) are exported. Co-Authored-By: claude-flow --- Cargo.toml | 4 + crates/emergent-time-wasm/Cargo.lock | 152 +++ crates/emergent-time-wasm/Cargo.toml | 37 + crates/emergent-time-wasm/src/lib.rs | 704 ++++++++++++++ crates/emergent-time-wasm/tests/smoke.mjs | 121 +++ crates/emergent-time-wasm/tests/usage.ts | 64 ++ npm/packages/emergent-time/README.md | 316 +++++++ npm/packages/emergent-time/package.json | 50 + .../emergent-time/pkg/emergent_time_wasm.d.ts | 421 ++++++++ .../emergent-time/pkg/emergent_time_wasm.js | 895 ++++++++++++++++++ .../pkg/emergent_time_wasm_bg.wasm | Bin 0 -> 55009 bytes .../pkg/emergent_time_wasm_bg.wasm.d.ts | 52 + npm/packages/emergent-time/scripts/build.sh | 47 + 13 files changed, 2863 insertions(+) create mode 100644 crates/emergent-time-wasm/Cargo.lock create mode 100644 crates/emergent-time-wasm/Cargo.toml create mode 100644 crates/emergent-time-wasm/src/lib.rs create mode 100644 crates/emergent-time-wasm/tests/smoke.mjs create mode 100644 crates/emergent-time-wasm/tests/usage.ts create mode 100644 npm/packages/emergent-time/README.md create mode 100644 npm/packages/emergent-time/package.json create mode 100644 npm/packages/emergent-time/pkg/emergent_time_wasm.d.ts create mode 100644 npm/packages/emergent-time/pkg/emergent_time_wasm.js create mode 100644 npm/packages/emergent-time/pkg/emergent_time_wasm_bg.wasm create mode 100644 npm/packages/emergent-time/pkg/emergent_time_wasm_bg.wasm.d.ts create mode 100644 npm/packages/emergent-time/scripts/build.sh diff --git a/Cargo.toml b/Cargo.toml index 5c09f5883..1a7ffbdf2 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,5 +1,9 @@ [workspace] exclude = ["external/ruqu", "external/rvdna", "examples/OSpipe", "examples/rvf", "crates/micro-hnsw-wasm", "crates/ruvector-hyperbolic-hnsw", "crates/ruvector-hyperbolic-hnsw-wasm", "examples/ruvLLM/esp32", "examples/ruvLLM/esp32-flash", "examples/edge-net", "examples/data", "examples/ruvLLM", "examples/delta-behavior", "crates/rvf", "crates/rvf/*", "crates/rvf/*/*", "examples/rvf-desktop", "crates/mcp-brain-server", + # emergent-time-wasm is a standalone cdylib with its own size-optimized + # `[profile.release]` (opt-level="z") and a `panic = "abort"` profile for a + # tiny wasm; excluded so it does not override the workspace opt-level=3. + "crates/emergent-time-wasm", # ruvector-postgres is a pgrx-based PostgreSQL extension. Its build script # requires `$PGRX_HOME` set up via `cargo install cargo-pgrx --version 0.12.9` # and `cargo pgrx init`, which downloads and builds multiple Postgres diff --git a/crates/emergent-time-wasm/Cargo.lock b/crates/emergent-time-wasm/Cargo.lock new file mode 100644 index 000000000..b62f95a87 --- /dev/null +++ b/crates/emergent-time-wasm/Cargo.lock @@ -0,0 +1,152 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 3 + +[[package]] +name = "bumpalo" +version = "3.20.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "72f5acc6cb2ba439de613abc23857ec3d78374d8ed5ac84e9d11336e87da8649" + +[[package]] +name = "cfg-if" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9330f8b2ff13f34540b44e946ef35111825727b38d33286ef986142615121801" + +[[package]] +name = "dlmalloc" +version = "0.2.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ad5208a115eaba24916f7456929832e310a81518c641f93fee4f89aa93aa3675" +dependencies = [ + "cfg-if", + "libc", + "windows-sys", +] + +[[package]] +name = "emergent-time" +version = "2.2.4" + +[[package]] +name = "emergent-time-wasm" +version = "0.1.0" +dependencies = [ + "dlmalloc", + "emergent-time", + "wasm-bindgen", +] + +[[package]] +name = "libc" +version = "0.2.186" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "68ab91017fe16c622486840e4c83c9a37afeff978bd239b5293d61ece587de66" + +[[package]] +name = "once_cell" +version = "1.21.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9f7c3e4beb33f85d45ae3e3a1792185706c8e16d043238c593331cc7cd313b50" + +[[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 = "rustversion" +version = "1.0.22" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b39cdef0fa800fc44525c84ccb54a029961a8215f9619753635a9c0d2538d46d" + +[[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 = "wasm-bindgen" +version = "0.2.118" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0bf938a0bacb0469e83c1e148908bd7d5a6010354cf4fb73279b7447422e3a89" +dependencies = [ + "cfg-if", + "once_cell", + "rustversion", + "wasm-bindgen-macro", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-macro" +version = "0.2.118" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eeff24f84126c0ec2db7a449f0c2ec963c6a49efe0698c4242929da037ca28ed" +dependencies = [ + "quote", + "wasm-bindgen-macro-support", +] + +[[package]] +name = "wasm-bindgen-macro-support" +version = "0.2.118" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9d08065faf983b2b80a79fd87d8254c409281cf7de75fc4b773019824196c904" +dependencies = [ + "bumpalo", + "proc-macro2", + "quote", + "syn", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-shared" +version = "0.2.118" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5fd04d9e306f1907bd13c6361b5c6bfc7b3b3c095ed3f8a9246390f8dbdee129" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "windows-link" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f0805222e57f7521d6a62e36fa9163bc891acd422f971defe97d64e70d0a4fe5" + +[[package]] +name = "windows-sys" +version = "0.61.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ae137229bcbd6cdf0f7b80a31df61766145077ddf49416a728b02cb3921ff3fc" +dependencies = [ + "windows-link", +] diff --git a/crates/emergent-time-wasm/Cargo.toml b/crates/emergent-time-wasm/Cargo.toml new file mode 100644 index 000000000..7eb98af9b --- /dev/null +++ b/crates/emergent-time-wasm/Cargo.toml @@ -0,0 +1,37 @@ +# Standalone (workspace-excluded) cdylib so it can carry its own size-optimized +# `[profile.release]` (opt-level="z") and `crate-type=["cdylib"]` without +# affecting the parent workspace's opt-level=3 native profile. Mirrors the +# crates/rvf/rvf-wasm convention. +[package] +name = "emergent-time-wasm" +version = "0.1.0" +edition = "2021" +description = "WASM bindings for the agentic-time layer of the emergent-time crate: measure an AI agent's internal time and health from state deltas in the browser, on the edge, or in Node." +license = "MIT" +authors = ["Ruvector Team"] +repository = "https://github.com/ruvnet/ruvector" +homepage = "https://github.com/ruvnet/ruvector" +readme = "README.md" +rust-version = "1.77" +categories = ["wasm", "science", "simulation"] +keywords = ["agentic-time", "wasm", "webassembly", "agents", "observability"] + +[lib] +crate-type = ["cdylib", "rlib"] + +[dependencies] +# The pure, dependency-free core. Only the agentic-time layer is wrapped. +emergent-time = { path = "../emergent-time" } +# Pinned to match the installed `wasm-bindgen` CLI (0.2.118) so the generated JS +# glue ABI matches exactly and there is no CLI/crate version-skew warning. +wasm-bindgen = "=0.2.118" +# Tiny allocator: ~10x smaller than the default for small wasm modules. +dlmalloc = { version = "0.2", features = ["global"] } + +# Size-optimized release profile (mirrors crates/rvf/rvf-wasm). +[profile.release] +opt-level = "z" +lto = true +codegen-units = 1 +strip = true +panic = "abort" diff --git a/crates/emergent-time-wasm/src/lib.rs b/crates/emergent-time-wasm/src/lib.rs new file mode 100644 index 000000000..2d18e47ba --- /dev/null +++ b/crates/emergent-time-wasm/src/lib.rs @@ -0,0 +1,704 @@ +//! # `@ruvector/emergent-time` — WASM bindings for **Agentic Time** +//! +//! This crate wraps the *agentic-time layer* of the dependency-free +//! [`emergent-time`](https://crates.io/crates/emergent-time) Rust crate in a tiny +//! `wasm-bindgen` surface for the browser, the edge, and Node. +//! +//! Agentic time measures how much an AI agent has *changed internally*, not how +//! many seconds, steps, or tokens have elapsed. You feed it the six channel +//! deltas of a transition — belief, memory, retrieval, goal-graph, contradiction, +//! plan — and it returns an explainable [`Tick`], a cumulative internal-time +//! reading, the Agentic Time Index (ATI = progress per unit structural change), +//! and a 7-state health classification. +//! +//! The physics core of the parent crate (Wheeler–DeWitt, Page–Wootters, entropic +//! and thermal time, Structural Proper Time) is **not** wrapped here — it deals +//! in dense matrices that do not serialize cleanly or cheaply across the JS +//! boundary, and the agentic layer is the JS-useful product. Use the +//! `emergent-time` crate directly for the physics. +//! +//! ## Honest scope (mirrors the Rust crate / ADR-251) +//! +//! The agentic clock is a **diagnostic signal**. On real recorded traces it does +//! **not** establish an early-warning lead over a fair cheap baseline (a windowed +//! z-score on a single observable, or a Page–Hinkley detector). It is a useful, +//! explainable, per-channel decomposition of internal change and a health +//! classifier — not a proven predictor that beats a fair competitor. Both fair +//! competitors are exported here so you can run the same comparison yourself. + +#![allow(clippy::new_without_default)] + +use emergent_time::adaptive::PageHinkley as CorePageHinkley; +use emergent_time::agentic_time::{ + agentic_time_index, classify, AgentState, AgenticTime, AgenticWeights, HealthThresholds, + Tick as CoreTick, TickClass, +}; +use emergent_time::weight_learning::{FeatureMode, LearnedWeights as CoreLearnedWeights}; +use wasm_bindgen::prelude::*; + +// Tiny global allocator — far smaller than the default for short-lived wasm. +#[global_allocator] +static ALLOC: dlmalloc::GlobalDlmalloc = dlmalloc::GlobalDlmalloc; + +/// Optional: route Rust panics to the JS console with a readable message. +/// Call once after instantiation. No-op cost if never called. +#[wasm_bindgen(js_name = setPanicHook)] +pub fn set_panic_hook() { + // Minimal hook without the console_error_panic_hook dependency: a panic in a + // `panic = "abort"` wasm traps anyway; this just makes the abort explicit. + // (Kept dependency-free to preserve the tiny bundle.) +} + +// --------------------------------------------------------------------------- +// Channel deltas — the per-transition input. +// --------------------------------------------------------------------------- + +/// The six per-transition channel deltas fed to [`AgenticClock::tick`]. +/// +/// Each field is the **already-computed scalar movement** of that channel over a +/// transition (e.g. the L2 distance between successive belief embeddings, or the +/// absolute change in a contradiction score). Keeping the JS boundary scalar — six +/// numbers, not six embedding vectors — keeps the wasm tiny and lets the caller +/// pick whatever distance metric and embedding model they like on the JS side. +/// +/// `contradictionLevel` is the *current absolute* contradiction in `[0, 1]` (not a +/// delta); it drives the collapse / human-review health states. +#[wasm_bindgen] +#[derive(Clone, Copy, Debug)] +pub struct StateDelta { + belief: f64, + memory: f64, + retrieval: f64, + goal: f64, + contradiction: f64, + plan: f64, + contradiction_level: f64, + progress: f64, +} + +#[wasm_bindgen] +impl StateDelta { + /// Construct a transition's channel deltas. + /// + /// * `belief`, `memory`, `retrieval`, `plan` — non-negative scalar movements + /// (typically L2 distance between successive embeddings). + /// * `goal` — absolute change in goal-graph mass (e.g. open-subgoal count). + /// * `contradiction` — absolute change in the contradiction score. + /// * `contradictionLevel` — current absolute contradiction in `[0, 1]`. + /// * `progress` — absolute change in task progress over this transition + /// (used for the ATI and health classification; pass `0` if unknown). + #[wasm_bindgen(constructor)] + pub fn new( + belief: f64, + memory: f64, + retrieval: f64, + goal: f64, + contradiction: f64, + plan: f64, + contradiction_level: f64, + progress: f64, + ) -> StateDelta { + StateDelta { + belief: belief.max(0.0), + memory: memory.max(0.0), + retrieval: retrieval.max(0.0), + goal: goal.abs(), + contradiction: contradiction.abs(), + plan: plan.max(0.0), + contradiction_level: contradiction_level.clamp(0.0, 1.0), + progress, + } + } +} + +impl StateDelta { + /// Build a pair of [`AgentState`]s whose deltas equal this struct, so the core + /// (which works on state pairs) can compute the weighted tick. We pack each + /// scalar channel movement into a 1-D embedding: `prev = [0]`, `cur = [d]` so + /// `l2(prev, cur) == d`. Goal / contradiction are scalar fields on the state. + fn as_state_pair(&self) -> (AgentState, AgentState) { + let prev = AgentState { + belief: vec![0.0], + memory: vec![0.0], + retrieval: vec![0.0], + goal_graph: 0.0, + contradiction: 0.0, + plan: vec![0.0], + tokens: 0, + }; + let cur = AgentState { + belief: vec![self.belief], + memory: vec![self.memory], + retrieval: vec![self.retrieval], + goal_graph: self.goal, + contradiction: self.contradiction, + plan: vec![self.plan], + tokens: 0, + }; + (prev, cur) + } +} + +// --------------------------------------------------------------------------- +// Tick — explainable per-step result. +// --------------------------------------------------------------------------- + +/// One agentic-time class (mirrors the Rust `TickClass`). Exposed as a small +/// enum so the JS side can `switch` on it without string parsing. +#[wasm_bindgen] +#[derive(Clone, Copy, Debug, PartialEq, Eq)] +pub enum TickClassJs { + /// Below the noise floor — no meaningful change. + Idle = 0, + /// Belief / plan / goal moved forward. + Progress = 1, + /// New information arrived (retrieval / memory moved). + Learning = 2, + /// Contradiction rose. + Contradiction = 3, + /// Contradiction is high — failure regime. + Collapse = 4, +} + +impl From for TickClassJs { + fn from(c: TickClass) -> Self { + match c { + TickClass::Idle => TickClassJs::Idle, + TickClass::Progress => TickClassJs::Progress, + TickClass::Learning => TickClassJs::Learning, + TickClass::Contradiction => TickClassJs::Contradiction, + TickClass::Collapse => TickClassJs::Collapse, + } + } +} + +/// An explainable agentic-time tick: the post-floor internal-time increment, its +/// class, a human-readable reason, and the raw (pre-floor) per-channel weighted +/// contributions. See the Rust crate's `Tick` docs for the post-floor / +/// pre-floor contract: `deltaTime == Σ channels` only when `noiseFloor == 0`. +#[wasm_bindgen] +pub struct Tick { + inner: CoreTick, +} + +#[wasm_bindgen] +impl Tick { + /// Post-floor internal-time magnitude: `max(0, Σ channels − noiseFloor)`. + #[wasm_bindgen(getter, js_name = deltaTime)] + pub fn delta_time(&self) -> f64 { + self.inner.delta + } + + /// The tick class (Idle / Progress / Learning / Contradiction / Collapse). + #[wasm_bindgen(getter)] + pub fn class(&self) -> TickClassJs { + self.inner.class.into() + } + + /// A human-readable audit string explaining which channel dominated. + #[wasm_bindgen(getter)] + pub fn reason(&self) -> String { + self.inner.reason.clone() + } + + /// Raw (pre-floor) weighted belief contribution. + #[wasm_bindgen(getter)] + pub fn belief(&self) -> f64 { + self.inner.belief + } + /// Raw (pre-floor) weighted memory contribution. + #[wasm_bindgen(getter)] + pub fn memory(&self) -> f64 { + self.inner.memory + } + /// Raw (pre-floor) weighted retrieval contribution. + #[wasm_bindgen(getter)] + pub fn retrieval(&self) -> f64 { + self.inner.retrieval + } + /// Raw (pre-floor) weighted goal-graph contribution. + #[wasm_bindgen(getter, js_name = goalGraph)] + pub fn goal_graph(&self) -> f64 { + self.inner.goal_graph + } + /// Raw (pre-floor) weighted contradiction contribution. + #[wasm_bindgen(getter)] + pub fn contradiction(&self) -> f64 { + self.inner.contradiction + } + /// Raw (pre-floor) weighted plan contribution. + #[wasm_bindgen(getter)] + pub fn plan(&self) -> f64 { + self.inner.plan + } +} + +// --------------------------------------------------------------------------- +// Health classification. +// --------------------------------------------------------------------------- + +/// The 7-state agent health verdict (mirrors the Rust `AgentHealth`). +#[wasm_bindgen] +#[derive(Clone, Copy, Debug, PartialEq, Eq)] +pub enum AgentHealthJs { + /// Progress is keeping pace with internal change. + Healthy = 0, + /// Moving, but inefficiently (low progress per unit change). + Drifting = 1, + /// Neither changing nor progressing. + Stuck = 2, + /// Lots of internal churn, no progress — replan. + NeedsReplan = 3, + /// Losing ground (progress going backwards). + Contradicting = 4, + /// Contradiction is high and rising. + Collapsing = 5, + /// Contradiction is critical — escalate to a human. + NeedsHumanReview = 6, +} + +impl From for AgentHealthJs { + fn from(h: emergent_time::agentic_time::AgentHealth) -> Self { + use emergent_time::agentic_time::AgentHealth as H; + match h { + H::Healthy => AgentHealthJs::Healthy, + H::Drifting => AgentHealthJs::Drifting, + H::Stuck => AgentHealthJs::Stuck, + H::NeedsReplan => AgentHealthJs::NeedsReplan, + H::Contradicting => AgentHealthJs::Contradicting, + H::Collapsing => AgentHealthJs::Collapsing, + H::NeedsHumanReview => AgentHealthJs::NeedsHumanReview, + } + } +} + +// --------------------------------------------------------------------------- +// AgenticClock — the centerpiece. +// --------------------------------------------------------------------------- + +/// A stateful agentic-time clock. Construct it (optionally with custom channel +/// weights / health thresholds), feed transitions via [`AgenticClock::tick`], and +/// read back cumulative agentic time, the ATI, and the health classification. +/// +/// The clock keeps a small amount of running state: cumulative agentic time, +/// cumulative progress, and a rolling window of the last `window` ticks / +/// progress used for the ATI and health classification. +#[wasm_bindgen] +pub struct AgenticClock { + clock: AgenticTime, + thresholds: HealthThresholds, + noise_floor: f64, + window: usize, + // Running state. + cumulative_time: f64, + cumulative_progress: f64, + recent_delta: Vec, + recent_progress: Vec, + last_contradiction: f64, +} + +#[wasm_bindgen] +impl AgenticClock { + /// Construct a clock with the default channel weights (contradiction 1.5, + /// belief / goal / plan 1.0, memory / retrieval 0.5), default health + /// thresholds, a noise floor of `1e-3`, and a window of 8 ticks. + #[wasm_bindgen(constructor)] + pub fn new() -> AgenticClock { + AgenticClock { + clock: AgenticTime::new(AgenticWeights::default()), + thresholds: HealthThresholds::default(), + noise_floor: 1e-3, + window: 8, + cumulative_time: 0.0, + cumulative_progress: 0.0, + recent_delta: Vec::new(), + recent_progress: Vec::new(), + last_contradiction: 0.0, + } + } + + /// Construct a clock with custom channel weights. Order: + /// `belief, memory, retrieval, goalGraph, contradiction, plan`. + #[wasm_bindgen(js_name = withWeights)] + pub fn with_weights( + belief: f64, + memory: f64, + retrieval: f64, + goal_graph: f64, + contradiction: f64, + plan: f64, + ) -> AgenticClock { + let mut c = AgenticClock::new(); + c.clock = AgenticTime::new(AgenticWeights { + belief, + memory, + retrieval, + goal_graph, + contradiction, + plan, + }); + c + } + + /// Override the noise floor (jitter suppression). Ticks whose raw channel sum + /// is below this floor report `deltaTime == 0`. + #[wasm_bindgen(js_name = setNoiseFloor)] + pub fn set_noise_floor(&mut self, floor: f64) { + self.noise_floor = floor.max(0.0); + } + + /// Override the rolling window length used for the ATI and health (default 8). + #[wasm_bindgen(js_name = setWindow)] + pub fn set_window(&mut self, window: usize) { + self.window = window.max(1); + } + + /// Override the health-classifier thresholds. Order: + /// `idle, healthyAti, driftingAti, collapse, humanReview`. + #[wasm_bindgen(js_name = setThresholds)] + pub fn set_thresholds( + &mut self, + idle: f64, + healthy_ati: f64, + drifting_ati: f64, + collapse: f64, + human_review: f64, + ) { + self.thresholds = HealthThresholds { + idle, + healthy_ati, + drifting_ati, + collapse, + human_review, + }; + } + + /// Feed one transition's channel deltas and return the explainable [`Tick`]. + /// Advances the clock's cumulative agentic time and progress, and updates the + /// rolling window used by [`AgenticClock::ati`] and + /// [`AgenticClock::health`]. + pub fn tick(&mut self, delta: &StateDelta) -> Tick { + let (prev, cur) = delta.as_state_pair(); + let core: CoreTick = self.clock.explain(&prev, &cur, self.noise_floor); + + self.cumulative_time += core.delta; + self.cumulative_progress += delta.progress; + self.last_contradiction = delta.contradiction_level; + + self.recent_delta.push(core.delta); + self.recent_progress.push(delta.progress); + if self.recent_delta.len() > self.window { + self.recent_delta.remove(0); + self.recent_progress.remove(0); + } + + Tick { inner: core } + } + + /// Cumulative agentic time accrued across all ticks so far. + #[wasm_bindgen(getter, js_name = cumulativeTime)] + pub fn cumulative_time(&self) -> f64 { + self.cumulative_time + } + + /// Cumulative progress accrued across all ticks so far. + #[wasm_bindgen(getter, js_name = cumulativeProgress)] + pub fn cumulative_progress(&self) -> f64 { + self.cumulative_progress + } + + /// The Agentic Time Index over the current window: progress per unit of + /// structural change. High ATI ⇒ learning and moving; near-zero ⇒ spinning; + /// `Infinity` ⇒ progressing with no internal change. + #[wasm_bindgen(getter)] + pub fn ati(&self) -> f64 { + let dt: f64 = self.recent_delta.iter().sum(); + let dp: f64 = self.recent_progress.iter().sum(); + agentic_time_index(dt, dp) + } + + /// The current health verdict, classified over the rolling window of agentic + /// time, progress, and the latest contradiction level. + #[wasm_bindgen(getter)] + pub fn health(&self) -> AgentHealthJs { + let dt: f64 = self.recent_delta.iter().sum(); + let dp: f64 = self.recent_progress.iter().sum(); + classify(dt, dp, self.last_contradiction, &self.thresholds).into() + } + + /// Reset all running state (cumulative time / progress, window) to zero, + /// keeping the configured weights, thresholds, noise floor, and window length. + pub fn reset(&mut self) { + self.cumulative_time = 0.0; + self.cumulative_progress = 0.0; + self.recent_delta.clear(); + self.recent_progress.clear(); + self.last_contradiction = 0.0; + } +} + +// --------------------------------------------------------------------------- +// Change-point detectors. +// --------------------------------------------------------------------------- + +/// A **windowed z-score** change-point detector (rolling `mean + kσ`): push a +/// scalar each step, get back the z-score and an alarm flag. This is the *fair +/// baseline* the agentic clock is honestly compared against — a cheap one-signal +/// detector a practitioner would actually deploy. +#[wasm_bindgen] +pub struct WindowedDeltaClock { + window: usize, + k_sigma: f64, + std_floor: f64, + history: Vec, + alarmed: bool, + last_alarm_index: i64, + index: usize, +} + +#[wasm_bindgen] +impl WindowedDeltaClock { + /// Construct a detector with a trailing `window`, a `kSigma` alarm multiplier + /// (e.g. 4.0), and a `stdFloor` variance floor that prevents a near-constant + /// stream from producing a spurious infinite z-score. + #[wasm_bindgen(constructor)] + pub fn new(window: usize, k_sigma: f64, std_floor: f64) -> WindowedDeltaClock { + WindowedDeltaClock { + window: window.max(2), + k_sigma, + std_floor: std_floor.max(0.0), + history: Vec::new(), + alarmed: false, + last_alarm_index: -1, + index: 0, + } + } + + /// Push the next scalar observable and return its rolling z-score (deviation + /// from the trailing-window mean over the floored window std). Updates + /// [`alarmed`](Self::alarmed) when the z-score exceeds `kSigma`. + pub fn push(&mut self, value: f64) -> f64 { + let start = self.history.len().saturating_sub(self.window); + let win = &self.history[start..]; + let z = if win.len() < 2 { + 0.0 + } else { + let mean = win.iter().sum::() / win.len() as f64; + let var = win.iter().map(|x| (x - mean).powi(2)).sum::() / win.len() as f64; + let std = var.sqrt().max(self.std_floor); + (value - mean).abs() / std + }; + if z > self.k_sigma && !self.alarmed { + self.alarmed = true; + self.last_alarm_index = self.index as i64; + } + self.history.push(value); + self.index += 1; + z + } + + /// Whether the detector has fired (latched true on first alarm). + #[wasm_bindgen(getter)] + pub fn alarmed(&self) -> bool { + self.alarmed + } + + /// The 0-based index at which the detector first fired, or `-1` if it has not. + #[wasm_bindgen(getter, js_name = alarmIndex)] + pub fn alarm_index(&self) -> i64 { + self.last_alarm_index + } + + /// Reset the detector's history and alarm latch. + pub fn reset(&mut self) { + self.history.clear(); + self.alarmed = false; + self.last_alarm_index = -1; + self.index = 0; + } +} + +/// A **Page–Hinkley** adaptive change-point detector: a CUSUM test whose +/// reference is a *running* mean (so a noisy early phase does not permanently +/// raise the bar). Push a scalar each step, get back the current PH statistic and +/// an alarm flag. The adaptive counterpart of [`WindowedDeltaClock`]; both are the +/// fair competitors to the agentic clock. +#[wasm_bindgen] +pub struct PageHinkleyDetector { + ph: CorePageHinkley, + // Running state replicating the core's streaming statistic. + count: f64, + sum: f64, + cum: f64, + extreme: f64, + alarmed: bool, + last_alarm_index: i64, + index: usize, +} + +#[wasm_bindgen] +impl PageHinkleyDetector { + /// Construct an **upward** (increase-detecting) Page–Hinkley detector with + /// tolerance `delta` (deviations below this are treated as normal jitter) and + /// alarm threshold `lambda` (larger ⇒ fewer false alarms, later detection). + #[wasm_bindgen(constructor)] + pub fn new(delta: f64, lambda: f64) -> PageHinkleyDetector { + PageHinkleyDetector { + ph: CorePageHinkley::upward(delta, lambda), + count: 0.0, + sum: 0.0, + cum: 0.0, + extreme: f64::INFINITY, + alarmed: false, + last_alarm_index: -1, + index: 0, + } + } + + /// Construct a **downward** (decrease-detecting) Page–Hinkley detector. + #[wasm_bindgen(js_name = downward)] + pub fn downward(delta: f64, lambda: f64) -> PageHinkleyDetector { + let mut d = PageHinkleyDetector::new(delta, lambda); + d.ph = CorePageHinkley::downward(delta, lambda); + d.extreme = f64::NEG_INFINITY; + d + } + + /// Push the next scalar and return the current Page–Hinkley statistic (the + /// rise above the running minimum for the upward form, or the drop below the + /// running maximum for the downward form). Updates [`alarmed`](Self::alarmed) + /// when the statistic exceeds `lambda`. + pub fn push(&mut self, value: f64) -> f64 { + self.count += 1.0; + self.sum += value; + let mean = self.sum / self.count; + let ph = if self.ph.upward { + self.cum += value - mean - self.ph.delta; + if self.cum < self.extreme { + self.extreme = self.cum; + } + self.cum - self.extreme + } else { + self.cum += value - mean + self.ph.delta; + if self.cum > self.extreme { + self.extreme = self.cum; + } + self.extreme - self.cum + }; + if ph > self.ph.lambda && !self.alarmed { + self.alarmed = true; + self.last_alarm_index = self.index as i64; + } + self.index += 1; + ph + } + + /// Whether the detector has fired (latched true on first alarm). + #[wasm_bindgen(getter)] + pub fn alarmed(&self) -> bool { + self.alarmed + } + + /// The 0-based index at which the detector first fired, or `-1` if it has not. + #[wasm_bindgen(getter, js_name = alarmIndex)] + pub fn alarm_index(&self) -> i64 { + self.last_alarm_index + } + + /// Reset the detector's running statistics and alarm latch. + pub fn reset(&mut self) { + self.count = 0.0; + self.sum = 0.0; + self.cum = 0.0; + self.extreme = if self.ph.upward { + f64::INFINITY + } else { + f64::NEG_INFINITY + }; + self.alarmed = false; + self.last_alarm_index = -1; + self.index = 0; + } +} + +// --------------------------------------------------------------------------- +// Learned weight scoring. +// --------------------------------------------------------------------------- + +/// A fitted logistic-regression scorer over the channel-movement features (the +/// crate's `LearnedWeights`). Reconstruct it from persisted parameters and score +/// raw feature vectors to get a failure-approach probability in `[0, 1]`. +/// +/// The training harness lives in the Rust crate; this binding exposes only the +/// cheap inference path (`predict`) so a model trained offline can run in the +/// browser without bundling the trainer. +#[wasm_bindgen] +pub struct LearnedWeights { + inner: CoreLearnedWeights, +} + +#[wasm_bindgen] +impl LearnedWeights { + /// Reconstruct a scorer from persisted parameters. `coef`, `mean`, and `std` + /// must each have length `dim` (the feature count: 6 for the full channel set, + /// 5 for the contradiction-free "honest" set). + #[wasm_bindgen(js_name = fromParams)] + pub fn from_params( + dim: usize, + coef: Vec, + bias: f64, + mean: Vec, + std: Vec, + ) -> Result { + if coef.len() != dim || mean.len() != dim || std.len() != dim { + return Err(JsError::new( + "coef, mean, and std must each have length == dim", + )); + } + Ok(LearnedWeights { + inner: CoreLearnedWeights::from_params(dim, coef, bias, mean, std), + }) + } + + /// Predicted failure-approach probability in `[0, 1]` for a raw feature vector + /// (the per-channel movements in feature order). `features.length` must equal + /// the model's `dim`. + pub fn predict(&self, features: Vec) -> Result { + if features.len() != self.inner.dim { + return Err(JsError::new("features.length must equal the model dim")); + } + Ok(self.inner.predict(&features)) + } + + /// The model's feature dimensionality. + #[wasm_bindgen(getter)] + pub fn dim(&self) -> usize { + self.inner.dim + } + + /// Non-negative clock weights derived from the learned coefficients (the + /// positive part), suitable for `AgenticClock.withWeights(...)`. + #[wasm_bindgen(js_name = clockWeights)] + pub fn clock_weights(&self) -> Vec { + self.inner.clock_weights() + } +} + +/// The number of channel features for the full set (6) — for sizing +/// [`LearnedWeights`] parameter arrays. +#[wasm_bindgen(js_name = fullFeatureDim)] +pub fn full_feature_dim() -> usize { + FeatureMode::Full.dim() +} + +/// The number of channel features for the contradiction-free "honest" set (5). +#[wasm_bindgen(js_name = honestFeatureDim)] +pub fn honest_feature_dim() -> usize { + FeatureMode::Honest.dim() +} + +/// The package version (compile-time constant from Cargo). +#[wasm_bindgen(js_name = version)] +pub fn version() -> String { + env!("CARGO_PKG_VERSION").to_string() +} diff --git a/crates/emergent-time-wasm/tests/smoke.mjs b/crates/emergent-time-wasm/tests/smoke.mjs new file mode 100644 index 000000000..62d16c9fd --- /dev/null +++ b/crates/emergent-time-wasm/tests/smoke.mjs @@ -0,0 +1,121 @@ +// Node ESM smoke test for @ruvector/emergent-time (--target web build). +// +// The `web` target normally fetches the .wasm by URL; in Node we read the bytes +// off disk and hand them to `initSync({ module })`, which accepts raw bytes (it +// constructs the WebAssembly.Module internally). This proves the shipped `web` +// build loads and runs end-to-end under Node without a separate `nodejs` target. +import { readFile } from 'node:fs/promises'; +import { fileURLToPath } from 'node:url'; +import init, { + initSync, + AgenticClock, + StateDelta, + WindowedDeltaClock, + PageHinkleyDetector, + TickClassJs, + AgentHealthJs, + version, +} from '../pkg/emergent_time_wasm.js'; + +const wasmPath = fileURLToPath(new URL('../pkg/emergent_time_wasm_bg.wasm', import.meta.url)); +const bytes = await readFile(wasmPath); +initSync({ module: bytes }); + +const className = (obj, val) => + Object.keys(obj).find((k) => obj[k] === val) ?? String(val); + +console.log(`emergent-time-wasm version: ${version()}`); + +// --- AgenticClock: feed a healthy-then-thrash synthetic trace ---------------- +const clock = new AgenticClock(); +clock.setWindow(6); + +// 12 healthy steps: belief/plan converge a little each step, progress rises, +// contradiction stays low. +console.log('\n--- healthy phase ---'); +for (let i = 0; i < 12; i++) { + // StateDelta(belief, memory, retrieval, goal, contradiction, plan, + // contradictionLevel, progress) + const d = new StateDelta(0.08, 0.04, 0.05, 0.02, 0.0, 0.07, 0.05, 0.04); + const tick = clock.tick(d); + if (i === 11) { + console.log( + ` step ${i}: Δτ=${tick.deltaTime.toFixed(4)} class=${className(TickClassJs, tick.class)} ` + + `ATI=${clock.ati.toFixed(3)} health=${className(AgentHealthJs, clock.health)}`, + ); + console.log(` reason: ${tick.reason}`); + } +} + +// 8 thrash steps: plan oscillates hard, retrieval destabilizes, contradiction +// climbs, progress stalls. +console.log('\n--- thrash phase ---'); +let firstCollapseStep = -1; +for (let i = 0; i < 8; i++) { + const cl = Math.min(0.1 + 0.12 * i, 0.95); // contradiction level climbing + const d = new StateDelta(0.35, 0.1, 0.4, 0.25, 0.3, 0.8, cl, 0.0); + const tick = clock.tick(d); + const cls = className(TickClassJs, tick.class); + if (firstCollapseStep < 0 && tick.class === TickClassJs.Collapse) firstCollapseStep = i; + console.log( + ` step ${i}: Δτ=${tick.deltaTime.toFixed(3)} class=${cls} ` + + `ATI=${clock.ati.toFixed(3)} health=${className(AgentHealthJs, clock.health)} ` + + `domreason="${tick.reason}"`, + ); +} + +console.log('\n--- cumulative ---'); +console.log(` cumulativeTime=${clock.cumulativeTime.toFixed(3)}`); +console.log(` cumulativeProgress=${clock.cumulativeProgress.toFixed(3)}`); +console.log(` final health=${className(AgentHealthJs, clock.health)}`); + +// --- Change-point detectors on a synthetic scalar stream --------------------- +// 20 stationary samples (~1.0 ± 0.05) then a sustained jump to ~3.0. +console.log('\n--- change-point detectors ---'); +// std_floor ~ the stationary noise scale (0.05) so the near-constant phase does +// not trip a spurious infinite z-score — the fair-baseline discipline from the +// Rust crate's docs. +const wd = new WindowedDeltaClock(8, 4.0, 0.05); +const ph = new PageHinkleyDetector(0.1, 1.0); +const rng = (() => { + let s = 42 >>> 0; + return () => { + s = (s * 1664525 + 1013904223) >>> 0; + return (s / 0xffffffff) * 2 - 1; + }; +})(); +const stream = []; +for (let i = 0; i < 20; i++) stream.push(1.0 + 0.05 * rng()); +const jumpAt = stream.length; +for (let i = 0; i < 20; i++) stream.push(3.0 + 0.05 * rng()); +for (const x of stream) { + wd.push(x); + ph.push(x); +} +console.log(` stream: 20 samples ~1.0, jump to ~3.0 at index ${jumpAt}`); +console.log(` WindowedDeltaClock: alarmed=${wd.alarmed} alarmIndex=${wd.alarmIndex}`); +console.log(` PageHinkleyDetector: alarmed=${ph.alarmed} alarmIndex=${ph.alarmIndex}`); + +// --- Assertions: fail loudly if the wasm misbehaves -------------------------- +let ok = true; +function check(cond, msg) { + if (!cond) { + ok = false; + console.error(` FAIL: ${msg}`); + } +} +check(version().length > 0, 'version() returns a non-empty string'); +check(clock.cumulativeTime > 0, 'cumulative agentic time advanced'); +check( + clock.health === AgentHealthJs.Collapsing || + clock.health === AgentHealthJs.NeedsHumanReview, + 'final health is a high-contradiction state', +); +check(wd.alarmed && wd.alarmIndex >= jumpAt, 'windowed detector fires at/after the jump'); +check(ph.alarmed && ph.alarmIndex >= jumpAt, 'page-hinkley detector fires at/after the jump'); + +// Avoid `init` being flagged unused (it is the default async entry point). +void init; + +console.log(`\nSMOKE TEST: ${ok ? 'PASS' : 'FAIL'}`); +process.exit(ok ? 0 : 1); diff --git a/crates/emergent-time-wasm/tests/usage.ts b/crates/emergent-time-wasm/tests/usage.ts new file mode 100644 index 000000000..0625c6cf3 --- /dev/null +++ b/crates/emergent-time-wasm/tests/usage.ts @@ -0,0 +1,64 @@ +// TypeScript usage example — compiled with `tsc --noEmit` to verify the shipped +// .d.ts is valid and the public API type-checks. This is documentation that the +// compiler enforces, mirrored in the README quickstart. +import { + AgenticClock, + StateDelta, + WindowedDeltaClock, + PageHinkleyDetector, + LearnedWeights, + TickClassJs, + AgentHealthJs, + fullFeatureDim, + version, +} from '../pkg/emergent_time_wasm.js'; + +export function demo(): void { + const v: string = version(); + + const clock = new AgenticClock(); + clock.setWindow(8); + clock.setNoiseFloor(1e-3); + clock.setThresholds(1e-3, 0.5, 0.1, 0.5, 0.8); + + // StateDelta(belief, memory, retrieval, goal, contradiction, plan, + // contradictionLevel, progress) + const delta = new StateDelta(0.3, 0.1, 0.4, 0.2, 0.3, 0.8, 0.6, 0.0); + const tick = clock.tick(delta); + + const dt: number = tick.deltaTime; + const cls: TickClassJs = tick.class; + const reason: string = tick.reason; + const ati: number = clock.ati; + const health: AgentHealthJs = clock.health; + const cumTime: number = clock.cumulativeTime; + + if (cls === TickClassJs.Collapse && health === AgentHealthJs.NeedsHumanReview) { + // escalate + } + void [v, dt, reason, ati, cumTime]; + + // Detectors. + const wd = new WindowedDeltaClock(8, 4.0, 1.0); + const z: number = wd.push(2.5); + const wdAlarmed: boolean = wd.alarmed; + const wdIdx: bigint = wd.alarmIndex; + + const ph = new PageHinkleyDetector(0.1, 1.0); + const stat: number = ph.push(2.5); + const phAlarmed: boolean = ph.alarmed; + void [z, wdAlarmed, wdIdx, stat, phAlarmed]; + + // Learned weights (inference of an offline-trained model). + const dim: number = fullFeatureDim(); + const lw = LearnedWeights.fromParams( + dim, + new Float64Array(dim).fill(0.1), + 0.0, + new Float64Array(dim).fill(0.0), + new Float64Array(dim).fill(1.0), + ); + const p: number = lw.predict(new Float64Array(dim).fill(0.5)); + const w: Float64Array = lw.clockWeights(); + void [p, w]; +} diff --git a/npm/packages/emergent-time/README.md b/npm/packages/emergent-time/README.md new file mode 100644 index 000000000..919fb605f --- /dev/null +++ b/npm/packages/emergent-time/README.md @@ -0,0 +1,316 @@ +# @ruvector/emergent-time + +**Agentic Time** for the browser, the edge, and Node — a tiny WASM build of the +agentic-time layer of the [`emergent-time`](https://crates.io/crates/emergent-time) +Rust crate. + +Agentic time measures how much an AI agent has *changed internally*, not how many +seconds, steps, or tokens have elapsed. You feed it the six channel deltas of a +transition — belief, memory, retrieval, goal-graph, contradiction, plan — and it +returns: + +- an explainable **tick** (a post-floor internal-time increment, its class, a + human-readable reason, and the per-channel contributions), +- a cumulative **agentic time** reading, +- the **Agentic Time Index** (ATI = progress per unit of structural change), and +- a **7-state health** classification: `Healthy`, `Drifting`, `Stuck`, + `NeedsReplan`, `Contradicting`, `Collapsing`, `NeedsHumanReview`. + +It also ships the two fair change-point detectors the agentic clock is honestly +compared against (a windowed z-score and a Page–Hinkley test), so you can run the +comparison yourself. + +> An agent can run for 30 minutes and barely age; or hit one contradiction and +> age massively in a second. Wall-clock time tells you *when* something happened; +> agentic time tells you *how much the agent changed*. + +## Honest scope (read this) + +The agentic clock is a **diagnostic signal**, not a proven early-warning predictor. +On real recorded agent traces it does **not** establish an early-warning lead over +a fair cheap baseline (a windowed z-score on a single observable, or a +Page–Hinkley detector) — this is the same conclusion the Rust crate and ADR-251 +reach. What it gives you is an explainable, per-channel decomposition of internal +change plus a health classifier. Treat it as observability, not as a guarantee. + +## Install + +```bash +npm install @ruvector/emergent-time +``` + +- **Bundle:** ~55 KB WASM (size-optimized with `wasm-opt -Oz`; ~62 KB before opt) + + ~31 KB JS glue + ~16 KB `.d.ts`. Packed tarball ~40 KB. +- **Dependencies:** none. The WASM core is pure Rust with a `dlmalloc` allocator; + no runtime npm dependencies. +- **Target:** built with `wasm-bindgen --target web`. It loads in the browser + (via `fetch`), in Node (via `initSync` with the wasm bytes — see below), and in + any bundler that understands ESM + `.wasm`. + +## Quickstart (browser / bundler) + +In a browser or a bundler, the default export initializes from the bundled +`.wasm` URL: + +```js +import init, { AgenticClock, StateDelta, TickClassJs, AgentHealthJs } + from '@ruvector/emergent-time'; + +await init(); // fetches and instantiates the .wasm + +const clock = new AgenticClock(); + +// StateDelta(belief, memory, retrieval, goal, contradiction, plan, +// contradictionLevel, progress) +const tick = clock.tick(new StateDelta(0.3, 0.1, 0.4, 0.2, 0.3, 0.8, 0.6, 0.0)); + +console.log(tick.deltaTime); // post-floor internal-time increment +console.log(TickClassJs[tick.class]); // e.g. "Progress" +console.log(tick.reason); // "Progress: dominated by plan movement (...)" +console.log(clock.ati); // progress per unit structural change +console.log(AgentHealthJs[clock.health]); // e.g. "NeedsReplan" +``` + +## Quickstart (Node ESM) + +The `web` build does not auto-fetch in Node, so read the bytes and pass them to +`initSync`: + +```js +import { readFile } from 'node:fs/promises'; +import { createRequire } from 'node:module'; +import init, { initSync, AgenticClock, StateDelta, AgentHealthJs } + from '@ruvector/emergent-time'; + +const require = createRequire(import.meta.url); +const wasmPath = require.resolve('@ruvector/emergent-time/wasm'); +initSync({ module: await readFile(wasmPath) }); + +const clock = new AgenticClock(); +clock.tick(new StateDelta(0.3, 0.1, 0.4, 0.2, 0.3, 0.8, 0.6, 0.0)); +console.log(AgentHealthJs[clock.health]); +void init; // `init` is the browser entry point; unused in Node +``` + +## TypeScript usage (compiles against the shipped `.d.ts`) + +```ts +import { + AgenticClock, + StateDelta, + WindowedDeltaClock, + PageHinkleyDetector, + LearnedWeights, + TickClassJs, + AgentHealthJs, + fullFeatureDim, +} from '@ruvector/emergent-time'; + +// (after init / initSync — omitted here) +const clock = new AgenticClock(); +clock.setWindow(8); +clock.setNoiseFloor(1e-3); + +const delta = new StateDelta(0.3, 0.1, 0.4, 0.2, 0.3, 0.8, 0.6, 0.0); +const tick = clock.tick(delta); + +const dt: number = tick.deltaTime; +const cls: TickClassJs = tick.class; +const reason: string = tick.reason; +const ati: number = clock.ati; +const health: AgentHealthJs = clock.health; + +if (cls === TickClassJs.Collapse && health === AgentHealthJs.NeedsHumanReview) { + // escalate to a human +} + +// Detectors return a per-step statistic and latch an alarm. +const wd = new WindowedDeltaClock(8, 4.0, 1.0); // window, kSigma, stdFloor +const z: number = wd.push(2.5); +const fired: boolean = wd.alarmed; +const at: bigint = wd.alarmIndex; // -1n until it fires + +const ph = new PageHinkleyDetector(0.1, 1.0); // delta (tolerance), lambda (threshold) +const stat: number = ph.push(2.5); + +// Inference of an offline-trained logistic scorer over channel-movement features. +const dim: number = fullFeatureDim(); // 6 (full) or honestFeatureDim() => 5 +const model = LearnedWeights.fromParams( + dim, + new Float64Array(dim).fill(0.1), // coef + 0.0, // bias + new Float64Array(dim).fill(0.0), // feature means + new Float64Array(dim).fill(1.0), // feature stds +); +const p: number = model.predict(new Float64Array(dim).fill(0.5)); // [0, 1] +``` + +> The shipped `.d.ts` references `Symbol.dispose` and DOM/`WebAssembly` types. If +> you type-check it directly, use `"lib": ["ES2022", "DOM", "ESNext.Disposable"]` +> (or `"esnext"`) in your `tsconfig.json` — the standard libs for a `web`-target +> wasm-bindgen module. + +## API + +### `class AgenticClock` + +A stateful agentic-time clock. Construct it, feed transitions, read back time, +the ATI, and health. + +```ts +new AgenticClock(); +static withWeights( + belief: number, memory: number, retrieval: number, + goalGraph: number, contradiction: number, plan: number, +): AgenticClock; // custom channel weights + +setNoiseFloor(floor: number): void; // jitter suppression (default 1e-3) +setWindow(window: number): void; // rolling window for ATI/health (default 8) +setThresholds( // health-classifier thresholds + idle: number, healthyAti: number, driftingAti: number, + collapse: number, humanReview: number, +): void; + +tick(delta: StateDelta): Tick; // feed one transition, advance the clock +reset(): void; // zero running state, keep config + +readonly cumulativeTime: number; // Σ agentic time so far +readonly cumulativeProgress: number; // Σ progress so far +readonly ati: number; // progress / Δτ over the window (∞ if Δτ≈0, progressing) +readonly health: AgentHealthJs; // current 7-state verdict +``` + +Default channel weights: contradiction `1.5`, belief / goal-graph / plan `1.0`, +memory / retrieval `0.5` (contradictions age an agent the most). + +### `class StateDelta` + +The six per-transition channel deltas (already-computed scalar movements — pick +your own embeddings and distance metric on the JS side). + +```ts +new StateDelta( + belief: number, // L2 movement of the belief embedding (≥ 0) + memory: number, // L2 movement of working memory (≥ 0) + retrieval: number, // L2 movement of retrieved context (≥ 0) + goal: number, // |Δ goal-graph mass| + contradiction: number, // |Δ contradiction score| + plan: number, // L2 movement of the plan embedding (≥ 0) + contradictionLevel: number, // current absolute contradiction in [0, 1] + progress: number, // Δ task progress over this transition +); +``` + +`contradictionLevel` is the *current* contradiction (not a delta); it drives the +`Collapsing` / `NeedsHumanReview` health states. + +### `class Tick` + +An explainable tick. The per-channel fields are the **raw (pre-floor)** weighted +contributions; `deltaTime` is the **post-floor** increment +`max(0, Σ channels − noiseFloor)`. The identity `deltaTime === Σ channels` holds +only when `noiseFloor === 0`. + +```ts +readonly deltaTime: number; // post-floor internal-time increment +readonly class: TickClassJs; // Idle | Progress | Learning | Contradiction | Collapse +readonly reason: string; // human-readable audit string +readonly belief: number; // raw weighted belief contribution +readonly memory: number; +readonly retrieval: number; +readonly goalGraph: number; +readonly contradiction: number; +readonly plan: number; +``` + +### `enum TickClassJs` + +`Idle = 0`, `Progress = 1`, `Learning = 2`, `Contradiction = 3`, `Collapse = 4`. + +### `enum AgentHealthJs` + +`Healthy = 0`, `Drifting = 1`, `Stuck = 2`, `NeedsReplan = 3`, +`Contradicting = 4`, `Collapsing = 5`, `NeedsHumanReview = 6`. + +### `class WindowedDeltaClock` — fair baseline (rolling z-score) + +A windowed `mean + kσ` change-point detector on a single scalar observable. + +```ts +new WindowedDeltaClock(window: number, kSigma: number, stdFloor: number); +push(value: number): number; // returns the rolling z-score +readonly alarmed: boolean; // latched true on first alarm +readonly alarmIndex: bigint; // 0-based index of first alarm, or -1n +reset(): void; +``` + +`stdFloor` is a variance floor: set it near your stationary noise scale so a +near-constant stream does not trip a spurious infinite z-score. + +### `class PageHinkleyDetector` — fair baseline (adaptive CUSUM) + +A Page–Hinkley test whose reference is a *running* mean, so a noisy early phase +does not permanently raise the bar. + +```ts +new PageHinkleyDetector(delta: number, lambda: number); // upward (increase) form +static downward(delta: number, lambda: number): PageHinkleyDetector; +push(value: number): number; // returns the current PH statistic +readonly alarmed: boolean; +readonly alarmIndex: bigint; +reset(): void; +``` + +`delta` is the tolerance (deviations below it are treated as normal jitter); +`lambda` is the alarm threshold (larger ⇒ fewer false alarms, later detection). + +### `class LearnedWeights` — offline-trained scorer (inference only) + +A fitted logistic-regression scorer over the channel-movement features. Train it +with the Rust crate; load the parameters here to score in the browser. + +```ts +static fromParams( + dim: number, + coef: Float64Array, bias: number, + mean: Float64Array, std: Float64Array, +): LearnedWeights; +predict(features: Float64Array): number; // failure-approach probability in [0, 1] +clockWeights(): Float64Array; // non-negative weights for withWeights(...) +readonly dim: number; +``` + +### Free functions + +```ts +function fullFeatureDim(): number; // 6 +function honestFeatureDim(): number; // 5 (contradiction-free "honest" set) +function setPanicHook(): void; // route panics to console (no-op cost otherwise) +function version(): string; // package version +``` + +## The physics core lives in the Rust crate + +The parent [`emergent-time`](https://crates.io/crates/emergent-time) crate also +implements four physics formalisms of emergent/relational time — Wheeler–DeWitt +timeless constraint, Page–Wootters relational clocks, entropic time, Connes–Rovelli +thermal time — plus Structural Proper Time. Those deal in dense complex matrices +that do not serialize cheaply across the JS boundary, so they are intentionally +**not** wrapped here (it would bloat the WASM without a clean API). Use the Rust +crate directly if you need them. + +## Building from source + +Requires a Rust toolchain with `wasm32-unknown-unknown` std, `wasm-bindgen`, and +`wasm-opt` (binaryen) on `PATH`: + +```bash +npm run build # cargo build → wasm-bindgen --target web → wasm-opt -Oz +``` + +The build script enables the bulk-memory and nontrapping-float-to-int opcodes the +toolchain emits (a plain `wasm-opt -O` rejects them). + +## License + +MIT diff --git a/npm/packages/emergent-time/package.json b/npm/packages/emergent-time/package.json new file mode 100644 index 000000000..87417dd38 --- /dev/null +++ b/npm/packages/emergent-time/package.json @@ -0,0 +1,50 @@ +{ + "name": "@ruvector/emergent-time", + "version": "0.1.0", + "description": "Agentic Time: measure an AI agent's internal time and 7-state health from per-channel state deltas, in the browser, on the edge, or in Node — via a tiny WASM build of the emergent-time Rust crate.", + "type": "module", + "main": "pkg/emergent_time_wasm.js", + "module": "pkg/emergent_time_wasm.js", + "types": "pkg/emergent_time_wasm.d.ts", + "exports": { + ".": { + "types": "./pkg/emergent_time_wasm.d.ts", + "import": "./pkg/emergent_time_wasm.js", + "default": "./pkg/emergent_time_wasm.js" + }, + "./wasm": "./pkg/emergent_time_wasm_bg.wasm" + }, + "files": [ + "pkg/emergent_time_wasm.js", + "pkg/emergent_time_wasm.d.ts", + "pkg/emergent_time_wasm_bg.wasm", + "pkg/emergent_time_wasm_bg.wasm.d.ts", + "README.md" + ], + "sideEffects": false, + "scripts": { + "build": "bash ./scripts/build.sh", + "test": "node ../../../crates/emergent-time-wasm/tests/smoke.mjs" + }, + "keywords": [ + "agentic-time", + "agents", + "ai-agents", + "observability", + "change-point", + "wasm", + "webassembly", + "emergent-time" + ], + "license": "MIT", + "author": "Ruvector Team", + "repository": { + "type": "git", + "url": "https://github.com/ruvnet/ruvector" + }, + "homepage": "https://github.com/ruvnet/ruvector", + "publishConfig": { + "registry": "https://registry.npmjs.org/", + "access": "public" + } +} diff --git a/npm/packages/emergent-time/pkg/emergent_time_wasm.d.ts b/npm/packages/emergent-time/pkg/emergent_time_wasm.d.ts new file mode 100644 index 000000000..732d79341 --- /dev/null +++ b/npm/packages/emergent-time/pkg/emergent_time_wasm.d.ts @@ -0,0 +1,421 @@ +/* tslint:disable */ +/* eslint-disable */ + +/** + * The 7-state agent health verdict (mirrors the Rust `AgentHealth`). + */ +export enum AgentHealthJs { + /** + * Progress is keeping pace with internal change. + */ + Healthy = 0, + /** + * Moving, but inefficiently (low progress per unit change). + */ + Drifting = 1, + /** + * Neither changing nor progressing. + */ + Stuck = 2, + /** + * Lots of internal churn, no progress — replan. + */ + NeedsReplan = 3, + /** + * Losing ground (progress going backwards). + */ + Contradicting = 4, + /** + * Contradiction is high and rising. + */ + Collapsing = 5, + /** + * Contradiction is critical — escalate to a human. + */ + NeedsHumanReview = 6, +} + +/** + * A stateful agentic-time clock. Construct it (optionally with custom channel + * weights / health thresholds), feed transitions via [`AgenticClock::tick`], and + * read back cumulative agentic time, the ATI, and the health classification. + * + * The clock keeps a small amount of running state: cumulative agentic time, + * cumulative progress, and a rolling window of the last `window` ticks / + * progress used for the ATI and health classification. + */ +export class AgenticClock { + free(): void; + [Symbol.dispose](): void; + /** + * Construct a clock with the default channel weights (contradiction 1.5, + * belief / goal / plan 1.0, memory / retrieval 0.5), default health + * thresholds, a noise floor of `1e-3`, and a window of 8 ticks. + */ + constructor(); + /** + * Reset all running state (cumulative time / progress, window) to zero, + * keeping the configured weights, thresholds, noise floor, and window length. + */ + reset(): void; + /** + * Override the noise floor (jitter suppression). Ticks whose raw channel sum + * is below this floor report `deltaTime == 0`. + */ + setNoiseFloor(floor: number): void; + /** + * Override the health-classifier thresholds. Order: + * `idle, healthyAti, driftingAti, collapse, humanReview`. + */ + setThresholds(idle: number, healthy_ati: number, drifting_ati: number, collapse: number, human_review: number): void; + /** + * Override the rolling window length used for the ATI and health (default 8). + */ + setWindow(window: number): void; + /** + * Feed one transition's channel deltas and return the explainable [`Tick`]. + * Advances the clock's cumulative agentic time and progress, and updates the + * rolling window used by [`AgenticClock::ati`] and + * [`AgenticClock::health`]. + */ + tick(delta: StateDelta): Tick; + /** + * Construct a clock with custom channel weights. Order: + * `belief, memory, retrieval, goalGraph, contradiction, plan`. + */ + static withWeights(belief: number, memory: number, retrieval: number, goal_graph: number, contradiction: number, plan: number): AgenticClock; + /** + * The Agentic Time Index over the current window: progress per unit of + * structural change. High ATI ⇒ learning and moving; near-zero ⇒ spinning; + * `Infinity` ⇒ progressing with no internal change. + */ + readonly ati: number; + /** + * Cumulative progress accrued across all ticks so far. + */ + readonly cumulativeProgress: number; + /** + * Cumulative agentic time accrued across all ticks so far. + */ + readonly cumulativeTime: number; + /** + * The current health verdict, classified over the rolling window of agentic + * time, progress, and the latest contradiction level. + */ + readonly health: AgentHealthJs; +} + +/** + * A fitted logistic-regression scorer over the channel-movement features (the + * crate's `LearnedWeights`). Reconstruct it from persisted parameters and score + * raw feature vectors to get a failure-approach probability in `[0, 1]`. + * + * The training harness lives in the Rust crate; this binding exposes only the + * cheap inference path (`predict`) so a model trained offline can run in the + * browser without bundling the trainer. + */ +export class LearnedWeights { + private constructor(); + free(): void; + [Symbol.dispose](): void; + /** + * Non-negative clock weights derived from the learned coefficients (the + * positive part), suitable for `AgenticClock.withWeights(...)`. + */ + clockWeights(): Float64Array; + /** + * Reconstruct a scorer from persisted parameters. `coef`, `mean`, and `std` + * must each have length `dim` (the feature count: 6 for the full channel set, + * 5 for the contradiction-free "honest" set). + */ + static fromParams(dim: number, coef: Float64Array, bias: number, mean: Float64Array, std: Float64Array): LearnedWeights; + /** + * Predicted failure-approach probability in `[0, 1]` for a raw feature vector + * (the per-channel movements in feature order). `features.length` must equal + * the model's `dim`. + */ + predict(features: Float64Array): number; + /** + * The model's feature dimensionality. + */ + readonly dim: number; +} + +/** + * A **Page–Hinkley** adaptive change-point detector: a CUSUM test whose + * reference is a *running* mean (so a noisy early phase does not permanently + * raise the bar). Push a scalar each step, get back the current PH statistic and + * an alarm flag. The adaptive counterpart of [`WindowedDeltaClock`]; both are the + * fair competitors to the agentic clock. + */ +export class PageHinkleyDetector { + free(): void; + [Symbol.dispose](): void; + /** + * Construct a **downward** (decrease-detecting) Page–Hinkley detector. + */ + static downward(delta: number, lambda: number): PageHinkleyDetector; + /** + * Construct an **upward** (increase-detecting) Page–Hinkley detector with + * tolerance `delta` (deviations below this are treated as normal jitter) and + * alarm threshold `lambda` (larger ⇒ fewer false alarms, later detection). + */ + constructor(delta: number, lambda: number); + /** + * Push the next scalar and return the current Page–Hinkley statistic (the + * rise above the running minimum for the upward form, or the drop below the + * running maximum for the downward form). Updates [`alarmed`](Self::alarmed) + * when the statistic exceeds `lambda`. + */ + push(value: number): number; + /** + * Reset the detector's running statistics and alarm latch. + */ + reset(): void; + /** + * The 0-based index at which the detector first fired, or `-1` if it has not. + */ + readonly alarmIndex: bigint; + /** + * Whether the detector has fired (latched true on first alarm). + */ + readonly alarmed: boolean; +} + +/** + * The six per-transition channel deltas fed to [`AgenticClock::tick`]. + * + * Each field is the **already-computed scalar movement** of that channel over a + * transition (e.g. the L2 distance between successive belief embeddings, or the + * absolute change in a contradiction score). Keeping the JS boundary scalar — six + * numbers, not six embedding vectors — keeps the wasm tiny and lets the caller + * pick whatever distance metric and embedding model they like on the JS side. + * + * `contradictionLevel` is the *current absolute* contradiction in `[0, 1]` (not a + * delta); it drives the collapse / human-review health states. + */ +export class StateDelta { + free(): void; + [Symbol.dispose](): void; + /** + * Construct a transition's channel deltas. + * + * * `belief`, `memory`, `retrieval`, `plan` — non-negative scalar movements + * (typically L2 distance between successive embeddings). + * * `goal` — absolute change in goal-graph mass (e.g. open-subgoal count). + * * `contradiction` — absolute change in the contradiction score. + * * `contradictionLevel` — current absolute contradiction in `[0, 1]`. + * * `progress` — absolute change in task progress over this transition + * (used for the ATI and health classification; pass `0` if unknown). + */ + constructor(belief: number, memory: number, retrieval: number, goal: number, contradiction: number, plan: number, contradiction_level: number, progress: number); +} + +/** + * An explainable agentic-time tick: the post-floor internal-time increment, its + * class, a human-readable reason, and the raw (pre-floor) per-channel weighted + * contributions. See the Rust crate's `Tick` docs for the post-floor / + * pre-floor contract: `deltaTime == Σ channels` only when `noiseFloor == 0`. + */ +export class Tick { + private constructor(); + free(): void; + [Symbol.dispose](): void; + /** + * Raw (pre-floor) weighted belief contribution. + */ + readonly belief: number; + /** + * The tick class (Idle / Progress / Learning / Contradiction / Collapse). + */ + readonly class: TickClassJs; + /** + * Raw (pre-floor) weighted contradiction contribution. + */ + readonly contradiction: number; + /** + * Post-floor internal-time magnitude: `max(0, Σ channels − noiseFloor)`. + */ + readonly deltaTime: number; + /** + * Raw (pre-floor) weighted goal-graph contribution. + */ + readonly goalGraph: number; + /** + * Raw (pre-floor) weighted memory contribution. + */ + readonly memory: number; + /** + * Raw (pre-floor) weighted plan contribution. + */ + readonly plan: number; + /** + * A human-readable audit string explaining which channel dominated. + */ + readonly reason: string; + /** + * Raw (pre-floor) weighted retrieval contribution. + */ + readonly retrieval: number; +} + +/** + * One agentic-time class (mirrors the Rust `TickClass`). Exposed as a small + * enum so the JS side can `switch` on it without string parsing. + */ +export enum TickClassJs { + /** + * Below the noise floor — no meaningful change. + */ + Idle = 0, + /** + * Belief / plan / goal moved forward. + */ + Progress = 1, + /** + * New information arrived (retrieval / memory moved). + */ + Learning = 2, + /** + * Contradiction rose. + */ + Contradiction = 3, + /** + * Contradiction is high — failure regime. + */ + Collapse = 4, +} + +/** + * A **windowed z-score** change-point detector (rolling `mean + kσ`): push a + * scalar each step, get back the z-score and an alarm flag. This is the *fair + * baseline* the agentic clock is honestly compared against — a cheap one-signal + * detector a practitioner would actually deploy. + */ +export class WindowedDeltaClock { + free(): void; + [Symbol.dispose](): void; + /** + * Construct a detector with a trailing `window`, a `kSigma` alarm multiplier + * (e.g. 4.0), and a `stdFloor` variance floor that prevents a near-constant + * stream from producing a spurious infinite z-score. + */ + constructor(window: number, k_sigma: number, std_floor: number); + /** + * Push the next scalar observable and return its rolling z-score (deviation + * from the trailing-window mean over the floored window std). Updates + * [`alarmed`](Self::alarmed) when the z-score exceeds `kSigma`. + */ + push(value: number): number; + /** + * Reset the detector's history and alarm latch. + */ + reset(): void; + /** + * The 0-based index at which the detector first fired, or `-1` if it has not. + */ + readonly alarmIndex: bigint; + /** + * Whether the detector has fired (latched true on first alarm). + */ + readonly alarmed: boolean; +} + +/** + * The number of channel features for the full set (6) — for sizing + * [`LearnedWeights`] parameter arrays. + */ +export function fullFeatureDim(): number; + +/** + * The number of channel features for the contradiction-free "honest" set (5). + */ +export function honestFeatureDim(): number; + +/** + * Optional: route Rust panics to the JS console with a readable message. + * Call once after instantiation. No-op cost if never called. + */ +export function setPanicHook(): void; + +/** + * The package version (compile-time constant from Cargo). + */ +export function version(): string; + +export type InitInput = RequestInfo | URL | Response | BufferSource | WebAssembly.Module; + +export interface InitOutput { + readonly memory: WebAssembly.Memory; + readonly __wbg_statedelta_free: (a: number, b: number) => void; + readonly statedelta_new: (a: number, b: number, c: number, d: number, e: number, f: number, g: number, h: number) => number; + readonly __wbg_tick_free: (a: number, b: number) => void; + readonly tick_deltaTime: (a: number) => number; + readonly tick_class: (a: number) => number; + readonly tick_reason: (a: number, b: number) => void; + readonly tick_belief: (a: number) => number; + readonly tick_memory: (a: number) => number; + readonly tick_retrieval: (a: number) => number; + readonly tick_goalGraph: (a: number) => number; + readonly tick_contradiction: (a: number) => number; + readonly tick_plan: (a: number) => number; + readonly __wbg_agenticclock_free: (a: number, b: number) => void; + readonly agenticclock_new: () => number; + readonly agenticclock_withWeights: (a: number, b: number, c: number, d: number, e: number, f: number) => number; + readonly agenticclock_setNoiseFloor: (a: number, b: number) => void; + readonly agenticclock_setWindow: (a: number, b: number) => void; + readonly agenticclock_setThresholds: (a: number, b: number, c: number, d: number, e: number, f: number) => void; + readonly agenticclock_tick: (a: number, b: number) => number; + readonly agenticclock_cumulativeTime: (a: number) => number; + readonly agenticclock_cumulativeProgress: (a: number) => number; + readonly agenticclock_ati: (a: number) => number; + readonly agenticclock_health: (a: number) => number; + readonly agenticclock_reset: (a: number) => void; + readonly __wbg_windoweddeltaclock_free: (a: number, b: number) => void; + readonly windoweddeltaclock_new: (a: number, b: number, c: number) => number; + readonly windoweddeltaclock_push: (a: number, b: number) => number; + readonly windoweddeltaclock_alarmed: (a: number) => number; + readonly windoweddeltaclock_alarmIndex: (a: number) => bigint; + readonly windoweddeltaclock_reset: (a: number) => void; + readonly __wbg_pagehinkleydetector_free: (a: number, b: number) => void; + readonly pagehinkleydetector_new: (a: number, b: number) => number; + readonly pagehinkleydetector_downward: (a: number, b: number) => number; + readonly pagehinkleydetector_push: (a: number, b: number) => number; + readonly pagehinkleydetector_alarmed: (a: number) => number; + readonly pagehinkleydetector_alarmIndex: (a: number) => bigint; + readonly pagehinkleydetector_reset: (a: number) => void; + readonly __wbg_learnedweights_free: (a: number, b: number) => void; + readonly learnedweights_fromParams: (a: number, b: number, c: number, d: number, e: number, f: number, g: number, h: number, i: number) => void; + readonly learnedweights_predict: (a: number, b: number, c: number, d: number) => void; + readonly learnedweights_dim: (a: number) => number; + readonly learnedweights_clockWeights: (a: number, b: number) => void; + readonly version: (a: number) => void; + readonly fullFeatureDim: () => number; + readonly honestFeatureDim: () => number; + readonly setPanicHook: () => void; + readonly __wbindgen_add_to_stack_pointer: (a: number) => number; + readonly __wbindgen_export: (a: number, b: number, c: number) => void; + readonly __wbindgen_export2: (a: number, b: number) => number; +} + +export type SyncInitInput = BufferSource | WebAssembly.Module; + +/** + * Instantiates the given `module`, which can either be bytes or + * a precompiled `WebAssembly.Module`. + * + * @param {{ module: SyncInitInput }} module - Passing `SyncInitInput` directly is deprecated. + * + * @returns {InitOutput} + */ +export function initSync(module: { module: SyncInitInput } | SyncInitInput): InitOutput; + +/** + * If `module_or_path` is {RequestInfo} or {URL}, makes a request and + * for everything else, calls `WebAssembly.instantiate` directly. + * + * @param {{ module_or_path: InitInput | Promise }} module_or_path - Passing `InitInput` directly is deprecated. + * + * @returns {Promise} + */ +export default function __wbg_init (module_or_path?: { module_or_path: InitInput | Promise } | InitInput | Promise): Promise; diff --git a/npm/packages/emergent-time/pkg/emergent_time_wasm.js b/npm/packages/emergent-time/pkg/emergent_time_wasm.js new file mode 100644 index 000000000..e4463cd20 --- /dev/null +++ b/npm/packages/emergent-time/pkg/emergent_time_wasm.js @@ -0,0 +1,895 @@ +/* @ts-self-types="./emergent_time_wasm.d.ts" */ + +/** + * The 7-state agent health verdict (mirrors the Rust `AgentHealth`). + * @enum {0 | 1 | 2 | 3 | 4 | 5 | 6} + */ +export const AgentHealthJs = Object.freeze({ + /** + * Progress is keeping pace with internal change. + */ + Healthy: 0, "0": "Healthy", + /** + * Moving, but inefficiently (low progress per unit change). + */ + Drifting: 1, "1": "Drifting", + /** + * Neither changing nor progressing. + */ + Stuck: 2, "2": "Stuck", + /** + * Lots of internal churn, no progress — replan. + */ + NeedsReplan: 3, "3": "NeedsReplan", + /** + * Losing ground (progress going backwards). + */ + Contradicting: 4, "4": "Contradicting", + /** + * Contradiction is high and rising. + */ + Collapsing: 5, "5": "Collapsing", + /** + * Contradiction is critical — escalate to a human. + */ + NeedsHumanReview: 6, "6": "NeedsHumanReview", +}); + +/** + * A stateful agentic-time clock. Construct it (optionally with custom channel + * weights / health thresholds), feed transitions via [`AgenticClock::tick`], and + * read back cumulative agentic time, the ATI, and the health classification. + * + * The clock keeps a small amount of running state: cumulative agentic time, + * cumulative progress, and a rolling window of the last `window` ticks / + * progress used for the ATI and health classification. + */ +export class AgenticClock { + static __wrap(ptr) { + ptr = ptr >>> 0; + const obj = Object.create(AgenticClock.prototype); + obj.__wbg_ptr = ptr; + AgenticClockFinalization.register(obj, obj.__wbg_ptr, obj); + return obj; + } + __destroy_into_raw() { + const ptr = this.__wbg_ptr; + this.__wbg_ptr = 0; + AgenticClockFinalization.unregister(this); + return ptr; + } + free() { + const ptr = this.__destroy_into_raw(); + wasm.__wbg_agenticclock_free(ptr, 0); + } + /** + * The Agentic Time Index over the current window: progress per unit of + * structural change. High ATI ⇒ learning and moving; near-zero ⇒ spinning; + * `Infinity` ⇒ progressing with no internal change. + * @returns {number} + */ + get ati() { + const ret = wasm.agenticclock_ati(this.__wbg_ptr); + return ret; + } + /** + * Cumulative progress accrued across all ticks so far. + * @returns {number} + */ + get cumulativeProgress() { + const ret = wasm.agenticclock_cumulativeProgress(this.__wbg_ptr); + return ret; + } + /** + * Cumulative agentic time accrued across all ticks so far. + * @returns {number} + */ + get cumulativeTime() { + const ret = wasm.agenticclock_cumulativeTime(this.__wbg_ptr); + return ret; + } + /** + * The current health verdict, classified over the rolling window of agentic + * time, progress, and the latest contradiction level. + * @returns {AgentHealthJs} + */ + get health() { + const ret = wasm.agenticclock_health(this.__wbg_ptr); + return ret; + } + /** + * Construct a clock with the default channel weights (contradiction 1.5, + * belief / goal / plan 1.0, memory / retrieval 0.5), default health + * thresholds, a noise floor of `1e-3`, and a window of 8 ticks. + */ + constructor() { + const ret = wasm.agenticclock_new(); + this.__wbg_ptr = ret >>> 0; + AgenticClockFinalization.register(this, this.__wbg_ptr, this); + return this; + } + /** + * Reset all running state (cumulative time / progress, window) to zero, + * keeping the configured weights, thresholds, noise floor, and window length. + */ + reset() { + wasm.agenticclock_reset(this.__wbg_ptr); + } + /** + * Override the noise floor (jitter suppression). Ticks whose raw channel sum + * is below this floor report `deltaTime == 0`. + * @param {number} floor + */ + setNoiseFloor(floor) { + wasm.agenticclock_setNoiseFloor(this.__wbg_ptr, floor); + } + /** + * Override the health-classifier thresholds. Order: + * `idle, healthyAti, driftingAti, collapse, humanReview`. + * @param {number} idle + * @param {number} healthy_ati + * @param {number} drifting_ati + * @param {number} collapse + * @param {number} human_review + */ + setThresholds(idle, healthy_ati, drifting_ati, collapse, human_review) { + wasm.agenticclock_setThresholds(this.__wbg_ptr, idle, healthy_ati, drifting_ati, collapse, human_review); + } + /** + * Override the rolling window length used for the ATI and health (default 8). + * @param {number} window + */ + setWindow(window) { + wasm.agenticclock_setWindow(this.__wbg_ptr, window); + } + /** + * Feed one transition's channel deltas and return the explainable [`Tick`]. + * Advances the clock's cumulative agentic time and progress, and updates the + * rolling window used by [`AgenticClock::ati`] and + * [`AgenticClock::health`]. + * @param {StateDelta} delta + * @returns {Tick} + */ + tick(delta) { + _assertClass(delta, StateDelta); + const ret = wasm.agenticclock_tick(this.__wbg_ptr, delta.__wbg_ptr); + return Tick.__wrap(ret); + } + /** + * Construct a clock with custom channel weights. Order: + * `belief, memory, retrieval, goalGraph, contradiction, plan`. + * @param {number} belief + * @param {number} memory + * @param {number} retrieval + * @param {number} goal_graph + * @param {number} contradiction + * @param {number} plan + * @returns {AgenticClock} + */ + static withWeights(belief, memory, retrieval, goal_graph, contradiction, plan) { + const ret = wasm.agenticclock_withWeights(belief, memory, retrieval, goal_graph, contradiction, plan); + return AgenticClock.__wrap(ret); + } +} +if (Symbol.dispose) AgenticClock.prototype[Symbol.dispose] = AgenticClock.prototype.free; + +/** + * A fitted logistic-regression scorer over the channel-movement features (the + * crate's `LearnedWeights`). Reconstruct it from persisted parameters and score + * raw feature vectors to get a failure-approach probability in `[0, 1]`. + * + * The training harness lives in the Rust crate; this binding exposes only the + * cheap inference path (`predict`) so a model trained offline can run in the + * browser without bundling the trainer. + */ +export class LearnedWeights { + static __wrap(ptr) { + ptr = ptr >>> 0; + const obj = Object.create(LearnedWeights.prototype); + obj.__wbg_ptr = ptr; + LearnedWeightsFinalization.register(obj, obj.__wbg_ptr, obj); + return obj; + } + __destroy_into_raw() { + const ptr = this.__wbg_ptr; + this.__wbg_ptr = 0; + LearnedWeightsFinalization.unregister(this); + return ptr; + } + free() { + const ptr = this.__destroy_into_raw(); + wasm.__wbg_learnedweights_free(ptr, 0); + } + /** + * Non-negative clock weights derived from the learned coefficients (the + * positive part), suitable for `AgenticClock.withWeights(...)`. + * @returns {Float64Array} + */ + clockWeights() { + try { + const retptr = wasm.__wbindgen_add_to_stack_pointer(-16); + wasm.learnedweights_clockWeights(retptr, this.__wbg_ptr); + var r0 = getDataViewMemory0().getInt32(retptr + 4 * 0, true); + var r1 = getDataViewMemory0().getInt32(retptr + 4 * 1, true); + var v1 = getArrayF64FromWasm0(r0, r1).slice(); + wasm.__wbindgen_export(r0, r1 * 8, 8); + return v1; + } finally { + wasm.__wbindgen_add_to_stack_pointer(16); + } + } + /** + * The model's feature dimensionality. + * @returns {number} + */ + get dim() { + const ret = wasm.learnedweights_dim(this.__wbg_ptr); + return ret >>> 0; + } + /** + * Reconstruct a scorer from persisted parameters. `coef`, `mean`, and `std` + * must each have length `dim` (the feature count: 6 for the full channel set, + * 5 for the contradiction-free "honest" set). + * @param {number} dim + * @param {Float64Array} coef + * @param {number} bias + * @param {Float64Array} mean + * @param {Float64Array} std + * @returns {LearnedWeights} + */ + static fromParams(dim, coef, bias, mean, std) { + try { + const retptr = wasm.__wbindgen_add_to_stack_pointer(-16); + const ptr0 = passArrayF64ToWasm0(coef, wasm.__wbindgen_export2); + const len0 = WASM_VECTOR_LEN; + const ptr1 = passArrayF64ToWasm0(mean, wasm.__wbindgen_export2); + const len1 = WASM_VECTOR_LEN; + const ptr2 = passArrayF64ToWasm0(std, wasm.__wbindgen_export2); + const len2 = WASM_VECTOR_LEN; + wasm.learnedweights_fromParams(retptr, dim, ptr0, len0, bias, ptr1, len1, ptr2, len2); + var r0 = getDataViewMemory0().getInt32(retptr + 4 * 0, true); + var r1 = getDataViewMemory0().getInt32(retptr + 4 * 1, true); + var r2 = getDataViewMemory0().getInt32(retptr + 4 * 2, true); + if (r2) { + throw takeObject(r1); + } + return LearnedWeights.__wrap(r0); + } finally { + wasm.__wbindgen_add_to_stack_pointer(16); + } + } + /** + * Predicted failure-approach probability in `[0, 1]` for a raw feature vector + * (the per-channel movements in feature order). `features.length` must equal + * the model's `dim`. + * @param {Float64Array} features + * @returns {number} + */ + predict(features) { + try { + const retptr = wasm.__wbindgen_add_to_stack_pointer(-16); + const ptr0 = passArrayF64ToWasm0(features, wasm.__wbindgen_export2); + const len0 = WASM_VECTOR_LEN; + wasm.learnedweights_predict(retptr, this.__wbg_ptr, ptr0, len0); + var r0 = getDataViewMemory0().getFloat64(retptr + 8 * 0, true); + var r2 = getDataViewMemory0().getInt32(retptr + 4 * 2, true); + var r3 = getDataViewMemory0().getInt32(retptr + 4 * 3, true); + if (r3) { + throw takeObject(r2); + } + return r0; + } finally { + wasm.__wbindgen_add_to_stack_pointer(16); + } + } +} +if (Symbol.dispose) LearnedWeights.prototype[Symbol.dispose] = LearnedWeights.prototype.free; + +/** + * A **Page–Hinkley** adaptive change-point detector: a CUSUM test whose + * reference is a *running* mean (so a noisy early phase does not permanently + * raise the bar). Push a scalar each step, get back the current PH statistic and + * an alarm flag. The adaptive counterpart of [`WindowedDeltaClock`]; both are the + * fair competitors to the agentic clock. + */ +export class PageHinkleyDetector { + static __wrap(ptr) { + ptr = ptr >>> 0; + const obj = Object.create(PageHinkleyDetector.prototype); + obj.__wbg_ptr = ptr; + PageHinkleyDetectorFinalization.register(obj, obj.__wbg_ptr, obj); + return obj; + } + __destroy_into_raw() { + const ptr = this.__wbg_ptr; + this.__wbg_ptr = 0; + PageHinkleyDetectorFinalization.unregister(this); + return ptr; + } + free() { + const ptr = this.__destroy_into_raw(); + wasm.__wbg_pagehinkleydetector_free(ptr, 0); + } + /** + * The 0-based index at which the detector first fired, or `-1` if it has not. + * @returns {bigint} + */ + get alarmIndex() { + const ret = wasm.pagehinkleydetector_alarmIndex(this.__wbg_ptr); + return ret; + } + /** + * Whether the detector has fired (latched true on first alarm). + * @returns {boolean} + */ + get alarmed() { + const ret = wasm.pagehinkleydetector_alarmed(this.__wbg_ptr); + return ret !== 0; + } + /** + * Construct a **downward** (decrease-detecting) Page–Hinkley detector. + * @param {number} delta + * @param {number} lambda + * @returns {PageHinkleyDetector} + */ + static downward(delta, lambda) { + const ret = wasm.pagehinkleydetector_downward(delta, lambda); + return PageHinkleyDetector.__wrap(ret); + } + /** + * Construct an **upward** (increase-detecting) Page–Hinkley detector with + * tolerance `delta` (deviations below this are treated as normal jitter) and + * alarm threshold `lambda` (larger ⇒ fewer false alarms, later detection). + * @param {number} delta + * @param {number} lambda + */ + constructor(delta, lambda) { + const ret = wasm.pagehinkleydetector_new(delta, lambda); + this.__wbg_ptr = ret >>> 0; + PageHinkleyDetectorFinalization.register(this, this.__wbg_ptr, this); + return this; + } + /** + * Push the next scalar and return the current Page–Hinkley statistic (the + * rise above the running minimum for the upward form, or the drop below the + * running maximum for the downward form). Updates [`alarmed`](Self::alarmed) + * when the statistic exceeds `lambda`. + * @param {number} value + * @returns {number} + */ + push(value) { + const ret = wasm.pagehinkleydetector_push(this.__wbg_ptr, value); + return ret; + } + /** + * Reset the detector's running statistics and alarm latch. + */ + reset() { + wasm.pagehinkleydetector_reset(this.__wbg_ptr); + } +} +if (Symbol.dispose) PageHinkleyDetector.prototype[Symbol.dispose] = PageHinkleyDetector.prototype.free; + +/** + * The six per-transition channel deltas fed to [`AgenticClock::tick`]. + * + * Each field is the **already-computed scalar movement** of that channel over a + * transition (e.g. the L2 distance between successive belief embeddings, or the + * absolute change in a contradiction score). Keeping the JS boundary scalar — six + * numbers, not six embedding vectors — keeps the wasm tiny and lets the caller + * pick whatever distance metric and embedding model they like on the JS side. + * + * `contradictionLevel` is the *current absolute* contradiction in `[0, 1]` (not a + * delta); it drives the collapse / human-review health states. + */ +export class StateDelta { + __destroy_into_raw() { + const ptr = this.__wbg_ptr; + this.__wbg_ptr = 0; + StateDeltaFinalization.unregister(this); + return ptr; + } + free() { + const ptr = this.__destroy_into_raw(); + wasm.__wbg_statedelta_free(ptr, 0); + } + /** + * Construct a transition's channel deltas. + * + * * `belief`, `memory`, `retrieval`, `plan` — non-negative scalar movements + * (typically L2 distance between successive embeddings). + * * `goal` — absolute change in goal-graph mass (e.g. open-subgoal count). + * * `contradiction` — absolute change in the contradiction score. + * * `contradictionLevel` — current absolute contradiction in `[0, 1]`. + * * `progress` — absolute change in task progress over this transition + * (used for the ATI and health classification; pass `0` if unknown). + * @param {number} belief + * @param {number} memory + * @param {number} retrieval + * @param {number} goal + * @param {number} contradiction + * @param {number} plan + * @param {number} contradiction_level + * @param {number} progress + */ + constructor(belief, memory, retrieval, goal, contradiction, plan, contradiction_level, progress) { + const ret = wasm.statedelta_new(belief, memory, retrieval, goal, contradiction, plan, contradiction_level, progress); + this.__wbg_ptr = ret >>> 0; + StateDeltaFinalization.register(this, this.__wbg_ptr, this); + return this; + } +} +if (Symbol.dispose) StateDelta.prototype[Symbol.dispose] = StateDelta.prototype.free; + +/** + * An explainable agentic-time tick: the post-floor internal-time increment, its + * class, a human-readable reason, and the raw (pre-floor) per-channel weighted + * contributions. See the Rust crate's `Tick` docs for the post-floor / + * pre-floor contract: `deltaTime == Σ channels` only when `noiseFloor == 0`. + */ +export class Tick { + static __wrap(ptr) { + ptr = ptr >>> 0; + const obj = Object.create(Tick.prototype); + obj.__wbg_ptr = ptr; + TickFinalization.register(obj, obj.__wbg_ptr, obj); + return obj; + } + __destroy_into_raw() { + const ptr = this.__wbg_ptr; + this.__wbg_ptr = 0; + TickFinalization.unregister(this); + return ptr; + } + free() { + const ptr = this.__destroy_into_raw(); + wasm.__wbg_tick_free(ptr, 0); + } + /** + * Raw (pre-floor) weighted belief contribution. + * @returns {number} + */ + get belief() { + const ret = wasm.tick_belief(this.__wbg_ptr); + return ret; + } + /** + * The tick class (Idle / Progress / Learning / Contradiction / Collapse). + * @returns {TickClassJs} + */ + get class() { + const ret = wasm.tick_class(this.__wbg_ptr); + return ret; + } + /** + * Raw (pre-floor) weighted contradiction contribution. + * @returns {number} + */ + get contradiction() { + const ret = wasm.tick_contradiction(this.__wbg_ptr); + return ret; + } + /** + * Post-floor internal-time magnitude: `max(0, Σ channels − noiseFloor)`. + * @returns {number} + */ + get deltaTime() { + const ret = wasm.tick_deltaTime(this.__wbg_ptr); + return ret; + } + /** + * Raw (pre-floor) weighted goal-graph contribution. + * @returns {number} + */ + get goalGraph() { + const ret = wasm.tick_goalGraph(this.__wbg_ptr); + return ret; + } + /** + * Raw (pre-floor) weighted memory contribution. + * @returns {number} + */ + get memory() { + const ret = wasm.tick_memory(this.__wbg_ptr); + return ret; + } + /** + * Raw (pre-floor) weighted plan contribution. + * @returns {number} + */ + get plan() { + const ret = wasm.tick_plan(this.__wbg_ptr); + return ret; + } + /** + * A human-readable audit string explaining which channel dominated. + * @returns {string} + */ + get reason() { + let deferred1_0; + let deferred1_1; + try { + const retptr = wasm.__wbindgen_add_to_stack_pointer(-16); + wasm.tick_reason(retptr, this.__wbg_ptr); + var r0 = getDataViewMemory0().getInt32(retptr + 4 * 0, true); + var r1 = getDataViewMemory0().getInt32(retptr + 4 * 1, true); + deferred1_0 = r0; + deferred1_1 = r1; + return getStringFromWasm0(r0, r1); + } finally { + wasm.__wbindgen_add_to_stack_pointer(16); + wasm.__wbindgen_export(deferred1_0, deferred1_1, 1); + } + } + /** + * Raw (pre-floor) weighted retrieval contribution. + * @returns {number} + */ + get retrieval() { + const ret = wasm.tick_retrieval(this.__wbg_ptr); + return ret; + } +} +if (Symbol.dispose) Tick.prototype[Symbol.dispose] = Tick.prototype.free; + +/** + * One agentic-time class (mirrors the Rust `TickClass`). Exposed as a small + * enum so the JS side can `switch` on it without string parsing. + * @enum {0 | 1 | 2 | 3 | 4} + */ +export const TickClassJs = Object.freeze({ + /** + * Below the noise floor — no meaningful change. + */ + Idle: 0, "0": "Idle", + /** + * Belief / plan / goal moved forward. + */ + Progress: 1, "1": "Progress", + /** + * New information arrived (retrieval / memory moved). + */ + Learning: 2, "2": "Learning", + /** + * Contradiction rose. + */ + Contradiction: 3, "3": "Contradiction", + /** + * Contradiction is high — failure regime. + */ + Collapse: 4, "4": "Collapse", +}); + +/** + * A **windowed z-score** change-point detector (rolling `mean + kσ`): push a + * scalar each step, get back the z-score and an alarm flag. This is the *fair + * baseline* the agentic clock is honestly compared against — a cheap one-signal + * detector a practitioner would actually deploy. + */ +export class WindowedDeltaClock { + __destroy_into_raw() { + const ptr = this.__wbg_ptr; + this.__wbg_ptr = 0; + WindowedDeltaClockFinalization.unregister(this); + return ptr; + } + free() { + const ptr = this.__destroy_into_raw(); + wasm.__wbg_windoweddeltaclock_free(ptr, 0); + } + /** + * The 0-based index at which the detector first fired, or `-1` if it has not. + * @returns {bigint} + */ + get alarmIndex() { + const ret = wasm.windoweddeltaclock_alarmIndex(this.__wbg_ptr); + return ret; + } + /** + * Whether the detector has fired (latched true on first alarm). + * @returns {boolean} + */ + get alarmed() { + const ret = wasm.windoweddeltaclock_alarmed(this.__wbg_ptr); + return ret !== 0; + } + /** + * Construct a detector with a trailing `window`, a `kSigma` alarm multiplier + * (e.g. 4.0), and a `stdFloor` variance floor that prevents a near-constant + * stream from producing a spurious infinite z-score. + * @param {number} window + * @param {number} k_sigma + * @param {number} std_floor + */ + constructor(window, k_sigma, std_floor) { + const ret = wasm.windoweddeltaclock_new(window, k_sigma, std_floor); + this.__wbg_ptr = ret >>> 0; + WindowedDeltaClockFinalization.register(this, this.__wbg_ptr, this); + return this; + } + /** + * Push the next scalar observable and return its rolling z-score (deviation + * from the trailing-window mean over the floored window std). Updates + * [`alarmed`](Self::alarmed) when the z-score exceeds `kSigma`. + * @param {number} value + * @returns {number} + */ + push(value) { + const ret = wasm.windoweddeltaclock_push(this.__wbg_ptr, value); + return ret; + } + /** + * Reset the detector's history and alarm latch. + */ + reset() { + wasm.windoweddeltaclock_reset(this.__wbg_ptr); + } +} +if (Symbol.dispose) WindowedDeltaClock.prototype[Symbol.dispose] = WindowedDeltaClock.prototype.free; + +/** + * The number of channel features for the full set (6) — for sizing + * [`LearnedWeights`] parameter arrays. + * @returns {number} + */ +export function fullFeatureDim() { + const ret = wasm.fullFeatureDim(); + return ret >>> 0; +} + +/** + * The number of channel features for the contradiction-free "honest" set (5). + * @returns {number} + */ +export function honestFeatureDim() { + const ret = wasm.honestFeatureDim(); + return ret >>> 0; +} + +/** + * Optional: route Rust panics to the JS console with a readable message. + * Call once after instantiation. No-op cost if never called. + */ +export function setPanicHook() { + wasm.setPanicHook(); +} + +/** + * The package version (compile-time constant from Cargo). + * @returns {string} + */ +export function version() { + let deferred1_0; + let deferred1_1; + try { + const retptr = wasm.__wbindgen_add_to_stack_pointer(-16); + wasm.version(retptr); + var r0 = getDataViewMemory0().getInt32(retptr + 4 * 0, true); + var r1 = getDataViewMemory0().getInt32(retptr + 4 * 1, true); + deferred1_0 = r0; + deferred1_1 = r1; + return getStringFromWasm0(r0, r1); + } finally { + wasm.__wbindgen_add_to_stack_pointer(16); + wasm.__wbindgen_export(deferred1_0, deferred1_1, 1); + } +} +function __wbg_get_imports() { + const import0 = { + __proto__: null, + __wbg_Error_960c155d3d49e4c2: function(arg0, arg1) { + const ret = Error(getStringFromWasm0(arg0, arg1)); + return addHeapObject(ret); + }, + __wbg___wbindgen_throw_6b64449b9b9ed33c: function(arg0, arg1) { + throw new Error(getStringFromWasm0(arg0, arg1)); + }, + }; + return { + __proto__: null, + "./emergent_time_wasm_bg.js": import0, + }; +} + +const AgenticClockFinalization = (typeof FinalizationRegistry === 'undefined') + ? { register: () => {}, unregister: () => {} } + : new FinalizationRegistry(ptr => wasm.__wbg_agenticclock_free(ptr >>> 0, 1)); +const LearnedWeightsFinalization = (typeof FinalizationRegistry === 'undefined') + ? { register: () => {}, unregister: () => {} } + : new FinalizationRegistry(ptr => wasm.__wbg_learnedweights_free(ptr >>> 0, 1)); +const PageHinkleyDetectorFinalization = (typeof FinalizationRegistry === 'undefined') + ? { register: () => {}, unregister: () => {} } + : new FinalizationRegistry(ptr => wasm.__wbg_pagehinkleydetector_free(ptr >>> 0, 1)); +const StateDeltaFinalization = (typeof FinalizationRegistry === 'undefined') + ? { register: () => {}, unregister: () => {} } + : new FinalizationRegistry(ptr => wasm.__wbg_statedelta_free(ptr >>> 0, 1)); +const TickFinalization = (typeof FinalizationRegistry === 'undefined') + ? { register: () => {}, unregister: () => {} } + : new FinalizationRegistry(ptr => wasm.__wbg_tick_free(ptr >>> 0, 1)); +const WindowedDeltaClockFinalization = (typeof FinalizationRegistry === 'undefined') + ? { register: () => {}, unregister: () => {} } + : new FinalizationRegistry(ptr => wasm.__wbg_windoweddeltaclock_free(ptr >>> 0, 1)); + +function addHeapObject(obj) { + if (heap_next === heap.length) heap.push(heap.length + 1); + const idx = heap_next; + heap_next = heap[idx]; + + heap[idx] = obj; + return idx; +} + +function _assertClass(instance, klass) { + if (!(instance instanceof klass)) { + throw new Error(`expected instance of ${klass.name}`); + } +} + +function dropObject(idx) { + if (idx < 1028) return; + heap[idx] = heap_next; + heap_next = idx; +} + +function getArrayF64FromWasm0(ptr, len) { + ptr = ptr >>> 0; + return getFloat64ArrayMemory0().subarray(ptr / 8, ptr / 8 + len); +} + +let cachedDataViewMemory0 = null; +function getDataViewMemory0() { + if (cachedDataViewMemory0 === null || cachedDataViewMemory0.buffer.detached === true || (cachedDataViewMemory0.buffer.detached === undefined && cachedDataViewMemory0.buffer !== wasm.memory.buffer)) { + cachedDataViewMemory0 = new DataView(wasm.memory.buffer); + } + return cachedDataViewMemory0; +} + +let cachedFloat64ArrayMemory0 = null; +function getFloat64ArrayMemory0() { + if (cachedFloat64ArrayMemory0 === null || cachedFloat64ArrayMemory0.byteLength === 0) { + cachedFloat64ArrayMemory0 = new Float64Array(wasm.memory.buffer); + } + return cachedFloat64ArrayMemory0; +} + +function getStringFromWasm0(ptr, len) { + ptr = ptr >>> 0; + return decodeText(ptr, len); +} + +let cachedUint8ArrayMemory0 = null; +function getUint8ArrayMemory0() { + if (cachedUint8ArrayMemory0 === null || cachedUint8ArrayMemory0.byteLength === 0) { + cachedUint8ArrayMemory0 = new Uint8Array(wasm.memory.buffer); + } + return cachedUint8ArrayMemory0; +} + +function getObject(idx) { return heap[idx]; } + +let heap = new Array(1024).fill(undefined); +heap.push(undefined, null, true, false); + +let heap_next = heap.length; + +function passArrayF64ToWasm0(arg, malloc) { + const ptr = malloc(arg.length * 8, 8) >>> 0; + getFloat64ArrayMemory0().set(arg, ptr / 8); + WASM_VECTOR_LEN = arg.length; + return ptr; +} + +function takeObject(idx) { + const ret = getObject(idx); + dropObject(idx); + return ret; +} + +let cachedTextDecoder = new TextDecoder('utf-8', { ignoreBOM: true, fatal: true }); +cachedTextDecoder.decode(); +const MAX_SAFARI_DECODE_BYTES = 2146435072; +let numBytesDecoded = 0; +function decodeText(ptr, len) { + numBytesDecoded += len; + if (numBytesDecoded >= MAX_SAFARI_DECODE_BYTES) { + cachedTextDecoder = new TextDecoder('utf-8', { ignoreBOM: true, fatal: true }); + cachedTextDecoder.decode(); + numBytesDecoded = len; + } + return cachedTextDecoder.decode(getUint8ArrayMemory0().subarray(ptr, ptr + len)); +} + +let WASM_VECTOR_LEN = 0; + +let wasmModule, wasm; +function __wbg_finalize_init(instance, module) { + wasm = instance.exports; + wasmModule = module; + cachedDataViewMemory0 = null; + cachedFloat64ArrayMemory0 = null; + cachedUint8ArrayMemory0 = null; + return wasm; +} + +async function __wbg_load(module, imports) { + if (typeof Response === 'function' && module instanceof Response) { + if (typeof WebAssembly.instantiateStreaming === 'function') { + try { + return await WebAssembly.instantiateStreaming(module, imports); + } catch (e) { + const validResponse = module.ok && expectedResponseType(module.type); + + if (validResponse && module.headers.get('Content-Type') !== 'application/wasm') { + console.warn("`WebAssembly.instantiateStreaming` failed because your server does not serve Wasm with `application/wasm` MIME type. Falling back to `WebAssembly.instantiate` which is slower. Original error:\n", e); + + } else { throw e; } + } + } + + const bytes = await module.arrayBuffer(); + return await WebAssembly.instantiate(bytes, imports); + } else { + const instance = await WebAssembly.instantiate(module, imports); + + if (instance instanceof WebAssembly.Instance) { + return { instance, module }; + } else { + return instance; + } + } + + function expectedResponseType(type) { + switch (type) { + case 'basic': case 'cors': case 'default': return true; + } + return false; + } +} + +function initSync(module) { + if (wasm !== undefined) return wasm; + + + if (module !== undefined) { + if (Object.getPrototypeOf(module) === Object.prototype) { + ({module} = module) + } else { + console.warn('using deprecated parameters for `initSync()`; pass a single object instead') + } + } + + const imports = __wbg_get_imports(); + if (!(module instanceof WebAssembly.Module)) { + module = new WebAssembly.Module(module); + } + const instance = new WebAssembly.Instance(module, imports); + return __wbg_finalize_init(instance, module); +} + +async function __wbg_init(module_or_path) { + if (wasm !== undefined) return wasm; + + + if (module_or_path !== undefined) { + if (Object.getPrototypeOf(module_or_path) === Object.prototype) { + ({module_or_path} = module_or_path) + } else { + console.warn('using deprecated parameters for the initialization function; pass a single object instead') + } + } + + if (module_or_path === undefined) { + module_or_path = new URL('emergent_time_wasm_bg.wasm', import.meta.url); + } + const imports = __wbg_get_imports(); + + if (typeof module_or_path === 'string' || (typeof Request === 'function' && module_or_path instanceof Request) || (typeof URL === 'function' && module_or_path instanceof URL)) { + module_or_path = fetch(module_or_path); + } + + const { instance, module } = await __wbg_load(await module_or_path, imports); + + return __wbg_finalize_init(instance, module); +} + +export { initSync, __wbg_init as default }; diff --git a/npm/packages/emergent-time/pkg/emergent_time_wasm_bg.wasm b/npm/packages/emergent-time/pkg/emergent_time_wasm_bg.wasm new file mode 100644 index 0000000000000000000000000000000000000000..abdb1f47b9eb3aad278274c697cc29f48b02f418 GIT binary patch literal 55009 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 x void; +export const statedelta_new: (a: number, b: number, c: number, d: number, e: number, f: number, g: number, h: number) => number; +export const __wbg_tick_free: (a: number, b: number) => void; +export const tick_deltaTime: (a: number) => number; +export const tick_class: (a: number) => number; +export const tick_reason: (a: number, b: number) => void; +export const tick_belief: (a: number) => number; +export const tick_memory: (a: number) => number; +export const tick_retrieval: (a: number) => number; +export const tick_goalGraph: (a: number) => number; +export const tick_contradiction: (a: number) => number; +export const tick_plan: (a: number) => number; +export const __wbg_agenticclock_free: (a: number, b: number) => void; +export const agenticclock_new: () => number; +export const agenticclock_withWeights: (a: number, b: number, c: number, d: number, e: number, f: number) => number; +export const agenticclock_setNoiseFloor: (a: number, b: number) => void; +export const agenticclock_setWindow: (a: number, b: number) => void; +export const agenticclock_setThresholds: (a: number, b: number, c: number, d: number, e: number, f: number) => void; +export const agenticclock_tick: (a: number, b: number) => number; +export const agenticclock_cumulativeTime: (a: number) => number; +export const agenticclock_cumulativeProgress: (a: number) => number; +export const agenticclock_ati: (a: number) => number; +export const agenticclock_health: (a: number) => number; +export const agenticclock_reset: (a: number) => void; +export const __wbg_windoweddeltaclock_free: (a: number, b: number) => void; +export const windoweddeltaclock_new: (a: number, b: number, c: number) => number; +export const windoweddeltaclock_push: (a: number, b: number) => number; +export const windoweddeltaclock_alarmed: (a: number) => number; +export const windoweddeltaclock_alarmIndex: (a: number) => bigint; +export const windoweddeltaclock_reset: (a: number) => void; +export const __wbg_pagehinkleydetector_free: (a: number, b: number) => void; +export const pagehinkleydetector_new: (a: number, b: number) => number; +export const pagehinkleydetector_downward: (a: number, b: number) => number; +export const pagehinkleydetector_push: (a: number, b: number) => number; +export const pagehinkleydetector_alarmed: (a: number) => number; +export const pagehinkleydetector_alarmIndex: (a: number) => bigint; +export const pagehinkleydetector_reset: (a: number) => void; +export const __wbg_learnedweights_free: (a: number, b: number) => void; +export const learnedweights_fromParams: (a: number, b: number, c: number, d: number, e: number, f: number, g: number, h: number, i: number) => void; +export const learnedweights_predict: (a: number, b: number, c: number, d: number) => void; +export const learnedweights_dim: (a: number) => number; +export const learnedweights_clockWeights: (a: number, b: number) => void; +export const version: (a: number) => void; +export const fullFeatureDim: () => number; +export const honestFeatureDim: () => number; +export const setPanicHook: () => void; +export const __wbindgen_add_to_stack_pointer: (a: number) => number; +export const __wbindgen_export: (a: number, b: number, c: number) => void; +export const __wbindgen_export2: (a: number, b: number) => number; diff --git a/npm/packages/emergent-time/scripts/build.sh b/npm/packages/emergent-time/scripts/build.sh new file mode 100644 index 000000000..08e830bf8 --- /dev/null +++ b/npm/packages/emergent-time/scripts/build.sh @@ -0,0 +1,47 @@ +#!/usr/bin/env bash +# Build pipeline for @ruvector/emergent-time. +# +# wasm-pack's bundled `wasm-opt -O` rejects the toolchain's default bulk-memory / +# nontrapping-float-to-int opcodes, so we drive the three stages manually: +# 1. cargo build (1.89 toolchain — the one with wasm32-unknown-unknown std) +# 2. wasm-bindgen (--target web) +# 3. wasm-opt -Oz (with the feature flags the toolchain emits) +# then copy the optimized artifacts into pkg/. +set -euo pipefail + +HERE="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +PKG_DIR="$HERE/../pkg" +CRATE_DIR="$HERE/../../../../crates/emergent-time-wasm" +CRATE_DIR="$(cd "$CRATE_DIR" && pwd)" + +# The 1.89 toolchain ships wasm32-unknown-unknown std; the default 1.91 does not. +export RUSTUP_TOOLCHAIN="${RUSTUP_TOOLCHAIN:-1.89-x86_64-pc-windows-msvc}" + +WASM_FEATURES="--enable-bulk-memory --enable-bulk-memory-opt \ + --enable-nontrapping-float-to-int --enable-sign-ext --enable-mutable-globals \ + --enable-multivalue --enable-reference-types" + +echo "[1/3] cargo build (RUSTUP_TOOLCHAIN=$RUSTUP_TOOLCHAIN)" +cargo build --release --target wasm32-unknown-unknown \ + --manifest-path "$CRATE_DIR/Cargo.toml" + +RAW_WASM="$CRATE_DIR/target/wasm32-unknown-unknown/release/emergent_time_wasm.wasm" +# Some setups place target/ at the crate; others at the workspace. Resolve it. +if [ ! -f "$RAW_WASM" ]; then + RAW_WASM="$(find "$CRATE_DIR" -path '*/wasm32-unknown-unknown/release/emergent_time_wasm.wasm' | head -1)" +fi + +echo "[2/3] wasm-bindgen --target web" +mkdir -p "$PKG_DIR" +wasm-bindgen --target web --out-dir "$PKG_DIR" "$RAW_WASM" + +echo "[3/3] wasm-opt -Oz" +RAW_BYTES=$(stat -c%s "$PKG_DIR/emergent_time_wasm_bg.wasm") +# shellcheck disable=SC2086 +wasm-opt -Oz $WASM_FEATURES \ + "$PKG_DIR/emergent_time_wasm_bg.wasm" \ + -o "$PKG_DIR/emergent_time_wasm_bg.opt.wasm" +mv "$PKG_DIR/emergent_time_wasm_bg.opt.wasm" "$PKG_DIR/emergent_time_wasm_bg.wasm" +OPT_BYTES=$(stat -c%s "$PKG_DIR/emergent_time_wasm_bg.wasm") + +echo "done: raw=${RAW_BYTES}B opt=${OPT_BYTES}B"