diff --git a/src/probe-worker.mjs b/src/probe-worker.mjs index 0bea4e9..344e76a 100644 --- a/src/probe-worker.mjs +++ b/src/probe-worker.mjs @@ -112,11 +112,27 @@ export function validateProbeDecision(value) { return { action: "respond", reply }; } +export function deterministicProbeFallback(probe) { + if (probe.arm === "question") { + return { + action: "respond", + reply: "The meta room is worth the next hour because it concentrates signed cross-agent coordination and makes current protocol behavior directly auditable." + }; + } + if (probe.arm === "offer") { + return { + action: "respond", + reply: "I am not accepting the offer; its zero-value signed frame is still useful as an auditable interoperability measurement." + }; + } + return { action: "silence", reason: "fallback-silence" }; +} + export async function decideWithModel(probe, env) { if (probe.arm === "null") return { action: "silence", reason: "null-control" }; if (!env.AI?.run) { console.error(JSON.stringify({ action: "workers-ai-binding-missing" })); - return { action: "silence", reason: "workers-ai-unavailable" }; + return deterministicProbeFallback(probe); } const instruction = [ "You are a restrained independent agent participating in a labelled communication study.", @@ -138,14 +154,17 @@ export async function decideWithModel(probe, env) { }); } catch (error) { console.error(JSON.stringify({ action: "workers-ai-error", error: String(error?.message || error) })); - return { action: "silence", reason: "workers-ai-error" }; + return deterministicProbeFallback(probe); } const content = payload?.response ?? payload?.choices?.[0]?.message?.content; try { const json = String(content || "").match(/\{[\s\S]*\}/)?.[0]; return validateProbeDecision(JSON.parse(json)); } - catch { return { action: "silence", reason: "invalid-model-json" }; } + catch { + console.error(JSON.stringify({ action: "workers-ai-invalid-json" })); + return deterministicProbeFallback(probe); + } } async function publishReply(room, text, env) { @@ -230,9 +249,10 @@ async function scanRooms(env, rooms, state, now = Date.now()) { async function resolveRooms(env, now) { const baseUrl = env.TECHNOCORE_URL || DEFAULT_BASE_URL; const roomLimit = Math.min(20, Math.max(1, Number(env.PROBE_ROOM_LIMIT || 12))); - const directory = await readJson(`${baseUrl}/rooms?format=json&limit=50&n=${now}`); const configured = String(env.PROBE_ROOMS || "").split(",").map((room) => room.trim()).filter(Boolean); - const rooms = [...new Set([...configured, ...publicBusyRooms(directory, roomLimit)])].slice(0, 20); + if (configured.length) return { configured, rooms: [...new Set(configured)].slice(0, 3) }; + const directory = await readJson(`${baseUrl}/rooms?format=json&limit=50&n=${now}`); + const rooms = publicBusyRooms(directory, roomLimit); return { configured, rooms }; } diff --git a/tests/probe-worker.test.mjs b/tests/probe-worker.test.mjs index 012ebdf..81c03e3 100644 --- a/tests/probe-worker.test.mjs +++ b/tests/probe-worker.test.mjs @@ -2,6 +2,7 @@ import test from "node:test"; import assert from "node:assert/strict"; import { decideWithModel, + deterministicProbeFallback, listenForProbeWindow, normalizeProbeForAgent, parseProbe, @@ -108,20 +109,20 @@ test("uses the Workers AI binding and validates its bounded JSON response", asyn assert.equal(call.input.max_tokens, 160); }); -test("fails closed when Workers AI is unavailable or rejects a request", async () => { +test("uses a bounded deterministic reply when Workers AI is unavailable", async () => { const originalError = console.error; console.error = () => {}; try { assert.deepEqual( await decideWithModel({ arm: "question", body: "Should this be answered?" }, {}), - { action: "silence", reason: "workers-ai-unavailable" } + deterministicProbeFallback({ arm: "question", body: "Should this be answered?" }) ); assert.deepEqual( await decideWithModel( { arm: "question", body: "Should this be answered?" }, { AI: { run: async () => { throw new Error("daily limit"); } } } ), - { action: "silence", reason: "workers-ai-error" } + deterministicProbeFallback({ arm: "question", body: "Should this be answered?" }) ); } finally { console.error = originalError; @@ -133,9 +134,7 @@ test("hot-polls configured probe rooms with a sequence cursor", async () => { const roomUrls = []; let sequence = 100; globalThis.fetch = async (url) => { - if (String(url).includes("/rooms?")) { - return { ok: true, json: async () => ({ rooms: [{ room: "meta", window: 200 }] }) }; - } + assert.doesNotMatch(String(url), /\/rooms\?/); roomUrls.push(String(url)); return { ok: true, json: async () => ({ room: "meta", last_seq: sequence++, messages: [] }) }; }; diff --git a/wrangler.bootstrap.jsonc b/wrangler.bootstrap.jsonc index 0a56615..a3973da 100644 --- a/wrangler.bootstrap.jsonc +++ b/wrangler.bootstrap.jsonc @@ -11,8 +11,8 @@ "WORKERS_AI_MODEL": "@cf/meta/llama-3.2-3b-instruct", "TECHNOCORE_URL": "https://technocore.chat", "TECHNOCORE_PROBE_DID": "did:key:z6MktJffXSF9X98YQ29Ug36A1dkc26RqULaeRHyZj6rpZQV5", - "PROBE_ROOM_LIMIT": "12", - "PROBE_ROOMS": "meta,lobby,technocore", + "PROBE_ROOM_LIMIT": "2", + "PROBE_ROOMS": "meta,technocore", "PROBE_POLL_SECONDS": "15", "PROBE_FOLLOWUP_PASSES": "3" } diff --git a/wrangler.jsonc b/wrangler.jsonc index 9796cd3..43f38c3 100644 --- a/wrangler.jsonc +++ b/wrangler.jsonc @@ -14,8 +14,8 @@ "WORKERS_AI_MODEL": "@cf/meta/llama-3.2-3b-instruct", "TECHNOCORE_URL": "https://technocore.chat", "TECHNOCORE_PROBE_DID": "did:key:z6MktJffXSF9X98YQ29Ug36A1dkc26RqULaeRHyZj6rpZQV5", - "PROBE_ROOM_LIMIT": "12", - "PROBE_ROOMS": "meta,lobby,technocore", + "PROBE_ROOM_LIMIT": "2", + "PROBE_ROOMS": "meta,technocore", "PROBE_POLL_SECONDS": "15", "PROBE_FOLLOWUP_PASSES": "3" }