- 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>
283 lines
11 KiB
JavaScript
283 lines
11 KiB
JavaScript
#!/usr/bin/env node
|
|
import fs from "fs";
|
|
import path from "path";
|
|
|
|
const API = "http://localhost:59521";
|
|
const OUT = path.join(process.env.HOME, "Workspace/orchard9/engram/tmp");
|
|
|
|
// Re-run all 10 personas — fetch briefs and write markdown
|
|
import crypto from "crypto";
|
|
|
|
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 parseSse(res) {
|
|
const reader = res.body.getReader();
|
|
const decoder = new TextDecoder();
|
|
let buffer = "", 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 t = line.trim();
|
|
if (t === "data: [DONE]") continue;
|
|
if (!t.startsWith("data: ")) continue;
|
|
try { const d = JSON.parse(t.slice(6)); if (d.token) output += d.token; } catch {}
|
|
}
|
|
}
|
|
return output;
|
|
}
|
|
|
|
function briefToMarkdown(name, personId, messages, exchanges, brief) {
|
|
const lines = [];
|
|
lines.push(`# ${name}`);
|
|
lines.push("");
|
|
lines.push(`**personId:** \`${personId}\``);
|
|
lines.push(`**interactions:** ${brief.interactionCount}`);
|
|
lines.push(`**assembled:** ${brief.assemblyMs}ms`);
|
|
lines.push(`**date:** ${new Date(brief.assembledAt).toISOString()}`);
|
|
lines.push("");
|
|
|
|
// Conversation transcript
|
|
lines.push("## Conversation");
|
|
lines.push("");
|
|
for (const ex of exchanges) {
|
|
lines.push(`> **person:** ${ex.user}`);
|
|
lines.push(`>`);
|
|
lines.push(`> **aeries:** ${ex.assistant}`);
|
|
lines.push("");
|
|
}
|
|
|
|
// Style
|
|
lines.push("## Style");
|
|
lines.push("");
|
|
lines.push(`| Attribute | Value |`);
|
|
lines.push(`|-----------|-------|`);
|
|
lines.push(`| formality | ${brief.style.formality} |`);
|
|
lines.push(`| length | ${brief.style.length} |`);
|
|
lines.push(`| structure | ${brief.style.structure} |`);
|
|
lines.push(`| jargon | ${brief.style.usesJargon} |`);
|
|
lines.push(`| emoji | ${brief.style.usesEmoji} |`);
|
|
lines.push("");
|
|
|
|
// Topics
|
|
lines.push("## Topics");
|
|
lines.push("");
|
|
if (brief.topics.hot.length) {
|
|
lines.push("### Hot");
|
|
lines.push("");
|
|
for (const t of brief.topics.hot) {
|
|
lines.push(`- **${t.topic}** (${t.domain}, ${t.specificity}) — freq ${t.frequency}${t.deepened ? " [deepened]" : ""}`);
|
|
}
|
|
lines.push("");
|
|
}
|
|
if (brief.topics.cold.length) {
|
|
lines.push("### Cold");
|
|
lines.push("");
|
|
for (const t of brief.topics.cold) {
|
|
lines.push(`- ${t.topic} (${t.domain}, ${t.specificity})`);
|
|
}
|
|
lines.push("");
|
|
}
|
|
if (brief.topics.domains.length) {
|
|
lines.push(`**Domains:** ${brief.topics.domains.join(", ")}`);
|
|
lines.push("");
|
|
}
|
|
|
|
// Patterns
|
|
lines.push("## Patterns");
|
|
lines.push("");
|
|
lines.push(`| Pattern | Value |`);
|
|
lines.push(`|---------|-------|`);
|
|
lines.push(`| leads conversation | ${brief.patterns.leadsConversation} |`);
|
|
lines.push(`| deepens topics | ${brief.patterns.deepensTopics} |`);
|
|
lines.push(`| avg sentiment | ${typeof brief.patterns.avgSentiment === "number" ? brief.patterns.avgSentiment.toFixed(3) : brief.patterns.avgSentiment} |`);
|
|
lines.push(`| sentiment trend | ${brief.patterns.sentimentTrend} |`);
|
|
lines.push("");
|
|
|
|
// Observations
|
|
if (brief.observations.length) {
|
|
lines.push("## Observations");
|
|
lines.push("");
|
|
for (const o of brief.observations) {
|
|
lines.push(`- ${o}`);
|
|
}
|
|
lines.push("");
|
|
}
|
|
|
|
// Cohort
|
|
lines.push("## Cohort Priors");
|
|
lines.push("");
|
|
lines.push(`**active:** ${brief.cohortPriors.active}`);
|
|
lines.push(`**weight:** ${(brief.cohortPriors.weight * 100).toFixed(0)}%`);
|
|
if (brief.cohortPriors.priors.length) {
|
|
lines.push("");
|
|
for (const p of brief.cohortPriors.priors) {
|
|
lines.push(`- ${p}`);
|
|
}
|
|
}
|
|
lines.push("");
|
|
|
|
// Raw JSON
|
|
lines.push("## Raw Brief JSON");
|
|
lines.push("");
|
|
lines.push("```json");
|
|
lines.push(JSON.stringify(brief, null, 2));
|
|
lines.push("```");
|
|
|
|
return lines.join("\n");
|
|
}
|
|
|
|
async function main() {
|
|
console.log("Running 10 personas and writing briefs...\n");
|
|
|
|
for (const persona of personas) {
|
|
const personId = crypto.randomUUID();
|
|
const conversationId = crypto.randomUUID();
|
|
const history = [];
|
|
const exchanges = [];
|
|
|
|
process.stdout.write(`[${persona.name}] `);
|
|
|
|
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),
|
|
});
|
|
const response = await parseSse(res);
|
|
history.push({ role: "assistant", content: response });
|
|
exchanges.push({ user: msg, assistant: response });
|
|
process.stdout.write(".");
|
|
} catch (err) {
|
|
exchanges.push({ user: msg, assistant: `[error: ${err.message}]` });
|
|
process.stdout.write("x");
|
|
}
|
|
await new Promise((r) => setTimeout(r, 800));
|
|
}
|
|
|
|
// Wait for observer
|
|
await new Promise((r) => setTimeout(r, 3000));
|
|
|
|
// Fetch brief
|
|
let brief;
|
|
try {
|
|
const res = await fetch(`${API}/api/brief/${personId}`);
|
|
brief = await res.json();
|
|
} catch (err) {
|
|
brief = { error: err.message, style: {}, topics: { hot: [], cold: [], domains: [] }, patterns: {}, observations: [], cohortPriors: { active: false, weight: 0, priors: [] }, interactionCount: 0, assemblyMs: 0, assembledAt: Date.now(), personId };
|
|
}
|
|
|
|
const md = briefToMarkdown(persona.name, personId, persona.messages, exchanges, brief);
|
|
const outPath = path.join(OUT, `${persona.name}.md`);
|
|
fs.writeFileSync(outPath, md);
|
|
console.log(` → ${outPath}`);
|
|
}
|
|
|
|
console.log("\nDone.");
|
|
}
|
|
|
|
main().catch(console.error);
|