- Extract redeliver_missed(tx, db, log) helper into cluster_transport.rs - heal_region now removes partition then immediately ships any missed batch-log entries to the healed follower's channel - await_convergence refactored to call the same helper (no logic change) - tidal-server: reload_text_index before search in cluster mode - tidal-server: write_signal returns Result instead of panicking on unknown signal - tidal-server: leader shows lag_events=0 (writes directly, no receiver thread) - tidal-server: fix cluster mode error propagation (ServerError::from) - docs/runbooks/cluster.md: add full cluster operations runbook - docker/: add Dockerfile for containerised cluster deployment - README.md: add tidal-server HTTP API getting-started section - Split oversized source files per CODING_GUIDELINES §9 Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
240 lines
10 KiB
JavaScript
240 lines
10 KiB
JavaScript
#!/usr/bin/env node
|
|
// M5 Communication Brief — 10-persona integration test
|
|
import crypto from "crypto";
|
|
|
|
const API = "http://localhost:59521";
|
|
|
|
const personas = [
|
|
{
|
|
name: "casual-tech",
|
|
messages: [
|
|
"yo have you ever messed with rust? trying to figure out if its worth learning",
|
|
"yeah but like the borrow checker seems insane. is it really that bad",
|
|
"hmm ok what about async stuff. heard tokio is the move",
|
|
"cool cool. i mostly do typescript rn so maybe its a big jump",
|
|
"bet. might just start with some cli tools first",
|
|
],
|
|
},
|
|
{
|
|
name: "formal-academic",
|
|
messages: [
|
|
"I've been researching the implications of large language models on academic writing. What are your thoughts on the epistemological challenges they present?",
|
|
"That is an interesting perspective. I am particularly concerned with the reproducibility crisis that may emerge when AI-generated text becomes indistinguishable from human-authored work.",
|
|
"Indeed. My current research examines citation integrity in the context of synthetic text generation. The methodological implications are quite significant.",
|
|
"I appreciate your engagement with this topic. Have you considered the role of institutional review boards in establishing guidelines for AI-assisted research?",
|
|
"Precisely. I believe we need a comprehensive framework that addresses both the ethical and methodological dimensions of this paradigm shift.",
|
|
],
|
|
},
|
|
{
|
|
name: "emotional",
|
|
messages: [
|
|
"hey... having kind of a rough day. do you ever just feel like nothing makes sense",
|
|
"yeah i dont know. work stuff mostly. feeling like im not good enough",
|
|
"thats actually really nice to hear. i guess i just compare myself to everyone",
|
|
"youre right. i think i need to be easier on myself. its just hard sometimes",
|
|
"thanks for listening. seriously. most people just say cheer up and move on",
|
|
],
|
|
},
|
|
{
|
|
name: "rapid-fire",
|
|
messages: [
|
|
"whats the best programming language",
|
|
"ok but why not python? also whats your take on AI replacing developers",
|
|
"interesting. what about quantum computing? will it change everything?",
|
|
"sure but when? also do you think remote work is dying? and whats the deal with web3",
|
|
"lol ok last one. tabs or spaces?",
|
|
],
|
|
},
|
|
{
|
|
name: "deep-diver",
|
|
messages: [
|
|
"been thinking a lot about consensus algorithms lately. raft vs paxos which do you think is more practical",
|
|
"yeah rafts understandability is a huge win. but what about the leader bottleneck? in high-throughput scenarios it becomes a real issue",
|
|
"exactly. thats why ive been looking at multi-raft where you shard the state machine. cockroachdb does this well",
|
|
"the tricky part is cross-range transactions though. you need some form of 2PC or parallel commits",
|
|
"right. i think the future is deterministic databases like calvin where you pre-order transactions. eliminates coordination entirely",
|
|
],
|
|
},
|
|
{
|
|
name: "emoji-fan",
|
|
messages: [
|
|
"hiii just discovered this app and im obsessed already omg",
|
|
"yes! do you like music? im really into kpop rn",
|
|
"blackpink is my absolute fave but also really vibing with newjeans lately",
|
|
"yesss taste! what about movies? seen anything good lately?",
|
|
"ooh ill check it out! thanks bestie",
|
|
],
|
|
},
|
|
{
|
|
name: "skeptic",
|
|
messages: [
|
|
"AI chatbots are mostly hype. Change my mind.",
|
|
"Thats a surface-level argument. Most benchmarks are gamed and dont reflect real-world utility.",
|
|
"Youre oversimplifying. The economic analysis doesnt support widespread adoption when you factor in inference costs and hallucination liability.",
|
|
"Thats incorrect. The study youre likely referencing has significant methodological flaws.",
|
|
"Ill concede narrow applications show promise. But the general intelligence narrative is fundamentally misleading.",
|
|
],
|
|
},
|
|
{
|
|
name: "creative-writer",
|
|
messages: [
|
|
"ive been working on a short story about a lighthouse keeper who discovers the light attracts something from the deep ocean. want to hear about it?",
|
|
"so the keeper notices the fish patterns change when the light hits a certain frequency. they start swimming in spirals. then one night something massive surfaces",
|
|
"exactly that tension! i want the reader to feel the keepers isolation. she cant tell anyone because the coast guard would shut down the lighthouse",
|
|
"ooh what if the creature communicates through bioluminescence? like its been trying to respond to the lighthouse for centuries",
|
|
"yes! and the ending she has to choose between warning the world and protecting this ancient being. i think she chooses silence",
|
|
],
|
|
},
|
|
{
|
|
name: "shy-terse",
|
|
messages: ["hi", "not much", "i guess i like reading", "fantasy mostly", "yeah sanderson is ok"],
|
|
},
|
|
{
|
|
name: "multi-domain",
|
|
messages: [
|
|
"been learning to cook thai food this week. green curry from scratch is no joke",
|
|
"oh totally different topic but have you been following the mars rover updates?",
|
|
"yeah the organic compounds thing. anyway do you play any instruments? i just started guitar",
|
|
"haha yeah my fingers hurt. oh hey what do you think about intermittent fasting?",
|
|
"makes sense. one more random one whats your take on minimalism as a lifestyle",
|
|
],
|
|
},
|
|
];
|
|
|
|
async function parseSseResponse(res) {
|
|
const reader = res.body.getReader();
|
|
const decoder = new TextDecoder();
|
|
let buffer = "";
|
|
let output = "";
|
|
|
|
while (true) {
|
|
const { done, value } = await reader.read();
|
|
if (done) break;
|
|
buffer += decoder.decode(value, { stream: true });
|
|
const lines = buffer.split("\n");
|
|
buffer = lines.pop();
|
|
for (const line of lines) {
|
|
const trimmed = line.trim();
|
|
if (trimmed === "data: [DONE]") continue;
|
|
if (!trimmed.startsWith("data: ")) continue;
|
|
try {
|
|
const data = JSON.parse(trimmed.slice(6));
|
|
if (data.token) output += data.token;
|
|
if (data.error) return `[ERROR: ${data.error}]`;
|
|
} catch {}
|
|
}
|
|
}
|
|
return output;
|
|
}
|
|
|
|
async function runPersona(persona) {
|
|
const personId = crypto.randomUUID();
|
|
const conversationId = crypto.randomUUID();
|
|
const history = [];
|
|
|
|
console.log(`\n[${ persona.name }] Starting (${personId.slice(0, 8)}…)`);
|
|
|
|
for (let i = 0; i < persona.messages.length; i++) {
|
|
const msg = persona.messages[i];
|
|
history.push({ role: "user", content: msg });
|
|
|
|
try {
|
|
const res = await fetch(`${API}/api/chat`, {
|
|
method: "POST",
|
|
headers: { "Content-Type": "application/json" },
|
|
body: JSON.stringify({ messages: [...history], conversationId, personId }),
|
|
signal: AbortSignal.timeout(30000),
|
|
});
|
|
|
|
if (!res.ok) {
|
|
console.log(` Turn ${i + 1}/5: HTTP ${res.status}`);
|
|
continue;
|
|
}
|
|
|
|
const response = await parseSseResponse(res);
|
|
history.push({ role: "assistant", content: response });
|
|
|
|
console.log(` Turn ${i + 1}/5: "${msg.slice(0, 45)}…" → "${response.slice(0, 55)}…"`);
|
|
} catch (err) {
|
|
console.log(` Turn ${i + 1}/5: ERROR ${err.message}`);
|
|
}
|
|
|
|
// Let observer process
|
|
await new Promise((r) => setTimeout(r, 800));
|
|
}
|
|
|
|
// Wait for observer signals to propagate to Synap
|
|
console.log(`[${persona.name}] Waiting for signals...`);
|
|
await new Promise((r) => setTimeout(r, 3000));
|
|
|
|
// Fetch brief
|
|
try {
|
|
const briefRes = await fetch(`${API}/api/brief/${personId}`);
|
|
const brief = await briefRes.json();
|
|
return { name: persona.name, personId, brief };
|
|
} catch (err) {
|
|
return { name: persona.name, personId, brief: null, error: err.message };
|
|
}
|
|
}
|
|
|
|
async function main() {
|
|
console.log("=== M5 Communication Brief — 10 Persona Test ===");
|
|
console.log(`Server: ${API}`);
|
|
|
|
const results = [];
|
|
|
|
for (const persona of personas) {
|
|
const result = await runPersona(persona);
|
|
results.push(result);
|
|
}
|
|
|
|
console.log("\n\n========================================");
|
|
console.log(" BRIEF SUMMARY");
|
|
console.log("========================================\n");
|
|
|
|
for (const r of results) {
|
|
const b = r.brief;
|
|
if (!b) {
|
|
console.log(`[${r.name}] NO BRIEF (${r.error})`);
|
|
continue;
|
|
}
|
|
|
|
const hotTopics = (b.topics?.hot || []).map((t) => `${t.topic}(${t.specificity})`).join(", ");
|
|
const coldTopics = (b.topics?.cold || []).map((t) => t.topic).join(", ");
|
|
const domains = (b.topics?.domains || []).join(", ");
|
|
const obs = (b.observations || []).length;
|
|
const cohort = b.cohortPriors?.active ? `active(${(b.cohortPriors.weight * 100).toFixed(0)}%)` : "inactive";
|
|
|
|
console.log(`[${r.name}] ${r.personId.slice(0, 8)}…`);
|
|
console.log(` interactions: ${b.interactionCount}`);
|
|
console.log(` style: ${b.style?.formality}/${b.style?.length} | jargon=${b.style?.usesJargon} emoji=${b.style?.usesEmoji} | structure=${b.style?.structure}`);
|
|
console.log(` sentiment: ${b.patterns?.avgSentiment?.toFixed?.(2) ?? b.patterns?.avgSentiment} (${b.patterns?.sentimentTrend}) | leads=${b.patterns?.leadsConversation} deepens=${b.patterns?.deepensTopics}`);
|
|
console.log(` topics hot: [${hotTopics}]`);
|
|
if (coldTopics) console.log(` topics cold: [${coldTopics}]`);
|
|
console.log(` domains: [${domains}]`);
|
|
console.log(` observations: ${obs}${obs > 0 ? " — " + b.observations.map((o) => `"${o.slice(0, 60)}"`) .join("; ") : ""}`);
|
|
console.log(` cohort: ${cohort} | priors=${(b.cohortPriors?.priors || []).length}`);
|
|
console.log(` assembled in ${b.assemblyMs}ms`);
|
|
console.log();
|
|
}
|
|
|
|
// Count populated sections
|
|
console.log("========================================");
|
|
console.log(" SECTION POPULATION");
|
|
console.log("========================================\n");
|
|
|
|
for (const r of results) {
|
|
const b = r.brief;
|
|
if (!b) continue;
|
|
let populated = 0;
|
|
if ((b.topics?.hot || []).length > 0) populated++;
|
|
if (b.style?.formality && b.style.formality !== "moderate") populated++;
|
|
if ((b.observations || []).length > 0) populated++;
|
|
if (b.patterns?.sentimentTrend && b.patterns.sentimentTrend !== "stable") populated++;
|
|
if (b.cohortPriors?.active) populated++;
|
|
console.log(`[${r.name}] ${populated}/5 sections populated`);
|
|
}
|
|
}
|
|
|
|
main().catch(console.error);
|