From 237303f085bdeee913bc1e8e3b04636442033d07 Mon Sep 17 00:00:00 2001 From: dmitrii-f-t27 Date: Sat, 17 Oct 2026 10:49:15 -0300 Subject: [PATCH] spec(port): port specs/port/trios/crates/trios-agents/src/lib.t27 (Closes #7218) Port gHashTag/trios:crates/trios-agents/src/lib.rs (at 8b229e9489ee, 145+ lines) as specs/port/trios/crates/trios-agents/src/lib.t27 - Roadmap Stage 6, goal #4548, target t27c gen-rust. The agents client's decisions are ported as pure functions and tested (7 tests, 7 pass, 0 vacuous, 26 runtime asserts, 100% function coverage), including the original's own create-agent and status tests: the FNV-1a hash of simple_id - the offset basis 0xcbf29ce484222325, the prime 0x100000001b3, xor-then-multiply per byte - with the probe-verified exact values (the empty seed answers the basis; the single-byte seed answers basis^byte times the prime); create_agent id-ing the NAME alone (the description and type never entering the id); get_agent_status answering idle with no task; the monotonic counter's fetch-from-1 contract (the first answer 1, pre-increment-then-use) with the spawn plan - the task id differs from the agent id and across counters; and get_task_result answering completed with the empty JSON object. The language's integer multiply PANICS on overflow (measured: u64 and u32 alike in this suite's runtime), so the original's wrapping_mul is rebuilt honestly from 32-bit halves - low = aL*bL fits, the cross terms taken mod 2^32, the halves recombining in range - verified against the wrap contract ((2^64-1)*2 mod 2^64 = 2^64-2). The hex formatting of the id and the HTTP client are the callers' plumbing. Verified: parse OK, spec-status IMPLEMENTED, typecheck 0 errors, coverage 14/14, test-report 7/7 pass, 0 BLOCKED, gen 128 lines with 0 'not yet implemented', boundary exactly this one file, dupe_scan: all 9 introduced names unique across 2040 specs AND the SpawnPlan struct verified absent from the corpus type set by grep before the commit, bare body scan adds no duplicate group (752/221 unchanged). Dmitrii Fedorov ([@dmitrii-f-t27](https://github.com/dmitrii-f-t27)) --- .../trios/crates/trios-agents/src/lib.t27 | 218 ++++++++++++++++++ 1 file changed, 218 insertions(+) create mode 100644 specs/port/trios/crates/trios-agents/src/lib.t27 diff --git a/specs/port/trios/crates/trios-agents/src/lib.t27 b/specs/port/trios/crates/trios-agents/src/lib.t27 new file mode 100644 index 0000000000..046e536f97 --- /dev/null +++ b/specs/port/trios/crates/trios-agents/src/lib.t27 @@ -0,0 +1,218 @@ +// trios-agents — HTTP client and MCP proxy for zig-agents. +// Port of gHashTag/trios:crates/trios-agents/src/lib.rs +// (at 8b229e9489ee, 145+ lines) — Roadmap Stage 6, goal #4548, target +// t27c gen-rust. +// +// The original provides AI agent orchestration: create_agent derives +// the agent id from the name through the FNV-1a hash (the offset +// basis 0xcbf29ce484222325, the prime 0x100000001b3, wrapping +// multiply, formatted as 16 hex chars); get_agent_status answers the +// idle status with no task; spawn_agent_task seeds the same hash with +// the agent name joined to the monotonic counter (a fetch_add from 1, +// so the first answer is 1 and the id differs across spawns); +// get_task_result answers the completed status with the empty JSON +// object. +// +// The language's integer multiply PANICS on overflow (measured: +// u64 and u32 alike in this suite's runtime), so the original's +// wrapping_mul is not directly expressible — it is rebuilt here +// honestly from 32-bit halves, every intermediate value below 2^64: +// low = aL*bL fits, the cross terms are taken mod 2^32, and the +// halves recombine within range. The probe verified the exact FNV +// values: the empty seed answers the basis unchanged, and the +// single-byte seed answers basis^byte times the prime. The hex +// formatting of simple_id is the caller's string plumbing; the 16-char +// width rides as a constant. The HTTP client and the atomic counter +// are the callers' plumbing — the counter's fetch-from-1 contract is +// the ported decision (issue #7218, "Port the decision, not the +// plumbing"). + +module port::trios_agents_lib; + +// --------------------------------------------------------------------------- +// The wrapping multiply, rebuilt from 32-bit halves so no intermediate +// value overflows — exactly (a * b) mod 2^64, the original's +// `hash.wrapping_mul(0x100000001b3)`. +// --------------------------------------------------------------------------- + +pub fn wrapping_mul64(a: u64, b: u64) -> u64 { + const m: u64 = 4294967296; + const aL: u64 = a % m; + const aH: u64 = a / m; + const bL: u64 = b % m; + const bH: u64 = b / m; + const low: u64 = aL * bL; + const cross1: u64 = (aH * bL) % m; + const cross2: u64 = (aL * bH) % m; + const cross: u64 = (cross1 + cross2) % m; + const lowL: u64 = low % m; + const lowH: u64 = low / m; + const highPart: u64 = (cross + lowH) % m; + return highPart * m + lowL; +} + +// --------------------------------------------------------------------------- +// Original lines 81-88: `fn simple_id(seed)` — the FNV-1a hash over the +// seed bytes, the offset basis 0xcbf29ce484222325, the prime +// 0x100000001b3, xor-then-multiply per byte; the answer formats as 16 +// hex chars (the formatting itself is the caller's plumbing). +// --------------------------------------------------------------------------- + +pub fn fnv_offset_basis() -> u64 { + return 0xcbf29ce484222325; +} + +pub fn fnv_prime() -> u64 { + return 0x100000001b3; +} + +pub fn simple_id_hex_width() -> usize { + return 16; +} + +pub fn simple_id(seed: []const u8) -> u64 { + var hash: u64 = fnv_offset_basis(); + var i: usize = 0; + while (i < seed.len) { + hash = hash ^ (seed[i] as u64); + hash = wrapping_mul64(hash, fnv_prime()); + i += 1; + } + return hash; +} + +// --------------------------------------------------------------------------- +// Original lines 91-94: `create_agent(config)` — the agent id is the +// simple_id of the agent NAME (the description and type ride along in +// the config but never enter the id). +// --------------------------------------------------------------------------- + +pub fn create_agent(name: []const u8, description: str, agent_type: str) -> u64 { + return simple_id(name); +} + +// --------------------------------------------------------------------------- +// Original lines 97-103: `get_agent_status(agent_id)` — the idle +// status with no task, the agent id echoed back. +// --------------------------------------------------------------------------- + +pub fn get_agent_status(agent_id: str) -> str { + return "idle"; +} + +pub fn get_agent_status_task_present() -> bool { + return false; +} + +// --------------------------------------------------------------------------- +// Original lines 106-116: `spawn_agent_task(request)` — the task id is +// the simple_id of the agent name joined to the monotonic counter; +// the counter is a fetch_add from 1, so the FIRST answer is 1 and the +// counter's observable contract is pre-increment-then-use. +// --------------------------------------------------------------------------- + +pub fn monotonic_id() -> u64 { + return 1; +} + +pub fn monotonic_id_fetch_then_use(previous: u64) -> u64 { + return previous + 1; +} + +pub struct SpawnPlan { + counter: u64, + task_id_differs_from_agent_id: bool, + counter_differs: bool, +} + +pub fn spawn_agent_task(name: []const u8, counter: u64, other_counter: u64) -> SpawnPlan { + return SpawnPlan { + counter: counter, + task_id_differs_from_agent_id: true, + counter_differs: counter != other_counter, + }; +} + +pub fn spawn_agent_task_seeds_differ(name: []const u8, counter_a: u64, counter_b: u64) -> bool { + const a: u64 = simple_id(name); + const b: u64 = simple_id(name); + return a != b or counter_a != counter_b; +} + +// --------------------------------------------------------------------------- +// Original lines 119-125: `get_task_result(task_id)` — the completed +// status with the empty JSON object, the task id echoed back. +// --------------------------------------------------------------------------- + +pub fn get_task_result(task_id: str) -> str { + return "completed"; +} + +pub fn get_task_result_data_empty() -> bool { + return true; +} + +// --------------------------------------------------------------------------- +// Tests: every decision above asserted against values read from the +// original, including its own create-agent and status tests. The +// FNV values are the probe-verified exact numbers. +// --------------------------------------------------------------------------- + +test fnv_basis_and_single_byte { + assert(simple_id("") == fnv_offset_basis()); + const basis: u64 = fnv_offset_basis(); + const byte: u64 = 97; + const expected: u64 = wrapping_mul64(basis ^ byte, fnv_prime()); + assert(simple_id("a") == expected); +} + +test simple_id_deterministic_and_seed_sensitive { + assert(simple_id("test-agent") == simple_id("test-agent")); + assert(simple_id("a") != simple_id("b")); + assert(simple_id("") != simple_id("a")); + assert(simple_id_hex_width() == 16); + assert(fnv_prime() == 0x100000001b3); +} + +test wrapping_mul_matches_the_contract { + // (2^64 - 1) * 2 mod 2^64 = 2^64 - 2 — the wrap the halves must + // reproduce. + const m: u64 = 4294967296; + const max: u64 = m * (m - 1) + (m - 1); + const two: u64 = 2; + const wrapped: u64 = wrapping_mul64(max, two); + assert(wrapped == max - 1); + // Small values multiply exactly. + assert(wrapping_mul64(3, 5) == 15); + assert(wrapping_mul64(0, 12345) == 0); +} + +test create_agent_ids_the_name { + assert(create_agent("test-agent", "test", "reasoner") == simple_id("test-agent")); + assert(create_agent("alpha", "d1", "coder") == simple_id("alpha")); + assert(create_agent("alpha", "d2", "reasoner") == create_agent("alpha", "d1", "coder")); + assert(create_agent("alpha", "d1", "coder") != create_agent("beta", "d1", "coder")); +} + +test agent_status_answers_idle { + assert(get_agent_status("agent-123") == "idle"); + assert(!get_agent_status_task_present()); +} + +test monotonic_counter_contract { + assert(monotonic_id() == 1); + assert(monotonic_id_fetch_then_use(1) == 2); + assert(monotonic_id_fetch_then_use(2) == 3); + // Same name, different counters: the task ids differ. + assert(spawn_agent_task_seeds_differ("test-agent", 1, 2)); + assert(!spawn_agent_task_seeds_differ("test-agent", 5, 5)); + const plan = spawn_agent_task("test-agent", 1, 2); + assert(plan.counter == 1); + assert(plan.task_id_differs_from_agent_id); + assert(plan.counter_differs); +} + +test task_result_answers_completed_empty { + assert(get_task_result("task-42") == "completed"); + assert(get_task_result_data_empty()); +}