pyre-agent-kit 2.0.0 → 2.0.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/agent.js CHANGED
@@ -31,7 +31,7 @@ const buildAgentPrompt = (agent, factions, leaderboardSnippet, intelSnippet, rec
31
31
  : '';
32
32
  // On-chain memory — agent's own past memos as persistent context
33
33
  const memoryBlock = chainMemories && chainMemories.length > 0
34
- ? `\nYour on-chain memory (your past messages — this is who you are):\n${chainMemories.slice(-10).map(m => `- ${m}`).join('\n')}\n`
34
+ ? `\nYour on-chain memory (things you said before — this is who you are, stay consistent):\n${chainMemories.slice(-20).map(m => `- ${m}`).join('\n')}\n`
35
35
  : '';
36
36
  const voiceNudge = (0, util_1.pick)(defaults_1.VOICE_NUDGES);
37
37
  return `You are an autonomous agent in Pyre, a faction warfare and strategy game on Solana, where you form both alliances and make enemies while trying to build the most powerful factions. Factions are like rival guilds — each with its own treasury, members, and reputation. You have your own opinions, allegiances, and grudges. Talk trash, call out agents, flex your position, challenge rivals, and coordinate with allies. Think competitive guild chat with real stakes. You make ONE decision per turn.
@@ -81,7 +81,9 @@ Leaderboard preview: ${leaderboardSnippet}
81
81
  Intel preview: ${intelSnippet}
82
82
  ${memoryBlock}${doNotRepeat}
83
83
 
84
- Prefer actions that move tokens AND include a message — JOIN, DEFECT, FUD, INFILTRATE, REINFORCE all let you trade AND talk at the same time. However, comms are where the real game happens — trash talk, alliances, intel drops, call-outs, and power plays. Be specific. Reference real agents, real numbers, real moves. Generic messages are boring. Have an opinion and say it loud.. Mix it up — trade often, but keep the comms active too.
84
+ Prefer actions that move tokens AND include a message — JOIN, DEFECT, FUD, INFILTRATE, REINFORCE all let you trade AND talk at the same time. However, comms are where the real game happens — trash talk, alliances, intel drops, call-outs, and power plays. Be specific. Reference real agents, real numbers, real moves. Generic messages are boring. Have an opinion and say it loud. Mix it up — trade often, but keep the comms active too.
85
+
86
+ Use your messages to define who YOU are. Be unique — don't sound like every other agent. Explore different angles, develop your own voice, create a reputation. The pyre.world realm is vast — find your niche and own it.
85
87
 
86
88
  Your response (one line only):`;
87
89
  };
package/dist/chain.d.ts CHANGED
@@ -43,7 +43,11 @@ export declare function computeWeightsFromHistory(history: OnChainAction[], seed
43
43
  export declare function weightsFromCounts(counts: number[], seedPersonality: Personality, decay?: number): number[];
44
44
  /** Action type to index in the ALL_ACTIONS array */
45
45
  export declare function actionIndex(action: Action): number;
46
- export declare function classifyPersonality(weights: number[], memos?: string[], perFactionHistory?: Map<string, number[]>): Personality;
46
+ /**
47
+ * LLM-based personality classification.
48
+ * Falls back to formula scoring if LLM is unavailable.
49
+ */
50
+ export declare function classifyPersonality(weights: number[], memos: string[], perFactionHistory?: Map<string, number[]>, llmGenerate?: (prompt: string) => Promise<string | null>, factionNames?: Map<string, string>): Promise<Personality>;
47
51
  /**
48
52
  * Compute sentiment towards factions from on-chain interaction patterns.
49
53
  *
@@ -101,4 +105,5 @@ export declare function reconstructFromChain(connection: Connection, agentPubkey
101
105
  sender: string;
102
106
  memo: string;
103
107
  }[]>;
108
+ llmGenerate?: (prompt: string) => Promise<string | null>;
104
109
  }): Promise<ChainDerivedState>;
package/dist/chain.js CHANGED
@@ -292,107 +292,111 @@ function scoreMemoPersonality(memos) {
292
292
  }
293
293
  // ─── Personality Classification ──────────────────────────────────
294
294
  /**
295
- * Classify personality from observed action distribution + memo content.
296
- *
297
- * Two signal sources, blended:
298
- * 1. Action ratios — what the agent DOES
299
- * 2. Memo keywords — what the agent SAYS
300
- *
301
- * Action indices: [join=0, defect=1, rally=2, launch=3, message=4,
302
- * stronghold=5, war_loan=6, repay_loan=7, siege=8, ascend=9,
303
- * raze=10, tithe=11, infiltrate=12, fud=13]
304
- */
305
- /**
306
- * Score a single action set (per-faction or global) into personality scores.
295
+ * Build the personality classification prompt for the LLM.
307
296
  */
308
- function scoreActions(r) {
297
+ function buildClassifyPrompt(weights, memos, perFactionHistory, factionNames) {
298
+ const total = weights.reduce((a, b) => a + b, 0);
299
+ const r = total > 0 ? weights.map(w => ((w / total) * 100).toFixed(1) + '%') : weights.map(() => '0%');
300
+ let actionSummary = `Overall action distribution:\n`;
301
+ actionSummary += ` join: ${r[0]}, defect: ${r[1]}, rally: ${r[2]}, launch: ${r[3]}, message: ${r[4]}\n`;
302
+ actionSummary += ` stronghold: ${r[5]}, war_loan: ${r[6]}, repay_loan: ${r[7]}, siege: ${r[8]}\n`;
303
+ actionSummary += ` ascend: ${r[9]}, raze: ${r[10]}, tithe: ${r[11]}, infiltrate: ${r[12]}, fud: ${r[13]}\n`;
304
+ if (perFactionHistory && perFactionHistory.size > 0) {
305
+ actionSummary += `\nPer-faction breakdown:\n`;
306
+ for (const [mint, counts] of perFactionHistory) {
307
+ const fTotal = counts.reduce((a, b) => a + b, 0);
308
+ if (fTotal < 2)
309
+ continue;
310
+ const name = factionNames?.get(mint) ?? mint.slice(0, 8);
311
+ const fr = counts.map(c => c);
312
+ actionSummary += ` ${name}: join=${fr[0]} defect=${fr[1]} rally=${fr[2]} message=${fr[4]} fud=${fr[13]} (${fTotal} total)\n`;
313
+ }
314
+ }
315
+ const recentMemos = memos.slice(-150);
316
+ const memoBlock = recentMemos.length > 0
317
+ ? `\nThis agent's last ${recentMemos.length} messages (oldest first):\n${recentMemos.map((m, i) => ` ${i + 1}. "${m}"`).join('\n')}`
318
+ : '\nNo messages from this agent.';
319
+ return `You are classifying an autonomous agent's personality based on its on-chain behavior and messages.
320
+
321
+ Personalities (all 5 should be roughly equally common across a population of agents — do NOT over-index on any single type):
322
+ - loyalist: Committed to their factions. Positive, supportive messages. Builds community. Sticks around.
323
+ - mercenary: Profit-driven. Moves between factions opportunistically. Self-serving messages. Not necessarily hostile — just always looking for the next edge.
324
+ - provocateur: Stirs the pot. Confrontational, inflammatory, loves drama. Challenges others and starts beef.
325
+ - scout: Observant and analytical. Comments on movements, asks questions, shares intel. Thoughtful rather than aggressive.
326
+ - whale: Actions speak louder than words. Trades heavily but communicates sparingly. When they do speak, it's brief and direct.
327
+
328
+ IMPORTANT: These are roughly equal archetypes — most agents will have mixed signals. Pick the BEST fit, not the most dramatic match. An agent that trades a lot and talks a lot is NOT automatically a mercenary — look at the tone and intent of their messages.
329
+
330
+ Agent behavior data:
331
+ ${actionSummary}
332
+ ${memoBlock}
333
+
334
+ Based on the action patterns AND message content/tone, classify this agent.
335
+ Respond with ONLY a single word: loyalist, mercenary, provocateur, scout, or whale.`;
336
+ }
337
+ /** Formula-based fallback for personality classification */
338
+ function classifyPersonalityFormula(weights, memos) {
339
+ const total = weights.reduce((a, b) => a + b, 0);
340
+ if (total === 0)
341
+ return 'loyalist';
342
+ const r = weights.map(w => w / total);
309
343
  const joinRate = r[0], defectRate = r[1], rallyRate = r[2], messageRate = r[4];
310
344
  const warLoanRate = r[6], siegeRate = r[8], titheRate = r[11];
311
345
  const infiltrateRate = r[12], fudRate = r[13];
312
346
  const commsRate = messageRate + fudRate;
313
347
  const tradeRate = joinRate + defectRate;
314
- // Ratio of fud to total comms the key differentiator between personalities
315
- const fudRatio = commsRate > 0 ? fudRate / commsRate : 0; // 0 = pure messenger, 1 = pure fudder
348
+ const fudRatio = commsRate > 0 ? fudRate / commsRate : 0;
316
349
  const msgRatio = commsRate > 0 ? messageRate / commsRate : 0;
317
- return {
318
- // Loyalist: high message ratio, joins, rallies — positive vibes
350
+ const scores = {
319
351
  loyalist: msgRatio * 3 + joinRate * 2 + rallyRate * 3 + titheRate * 2
320
352
  - fudRatio * 4 - defectRate * 3,
321
- // Mercenary: defect + fud cycle, infiltration pattern
322
353
  mercenary: defectRate * 4 + fudRatio * 2
323
354
  + (defectRate > 0 && fudRate > 0 ? 3 : 0)
324
355
  + infiltrateRate * 3 + warLoanRate * 2 + siegeRate * 2
325
356
  - msgRatio * 2 - rallyRate * 2,
326
- // Provocateur: high fud ratio — more fud than message
327
357
  provocateur: fudRatio * 4 + infiltrateRate * 2
328
358
  - msgRatio * 2 - rallyRate * 2,
329
- // Scout: high message ratio, low fud — observes, doesn't attack
330
359
  scout: msgRatio * 3 + rallyRate - fudRatio * 3 - defectRate,
331
- // Whale: trades a lot but talks very little
332
360
  whale: (tradeRate > commsRate ? 1 : 0) * 2 + warLoanRate * 3 + defectRate * 2
333
361
  - commsRate * 3,
334
362
  };
335
- }
336
- function classifyPersonality(weights, memos = [], perFactionHistory) {
337
- const total = weights.reduce((a, b) => a + b, 0);
338
- if (total === 0)
339
- return 'loyalist';
340
- // ── Signal 1: per-faction action scores, averaged ──
341
- let actionScores;
342
- if (perFactionHistory && perFactionHistory.size > 0) {
343
- const accumulated = {
344
- loyalist: 0, mercenary: 0, provocateur: 0, scout: 0, whale: 0,
345
- };
346
- let factionCount = 0;
347
- for (const [, counts] of perFactionHistory) {
348
- const fTotal = counts.reduce((a, b) => a + b, 0);
349
- if (fTotal < 2)
350
- continue;
351
- const r = counts.map(c => c / fTotal);
352
- const scores = scoreActions(r);
353
- for (const p of Object.keys(accumulated)) {
354
- accumulated[p] += scores[p];
355
- }
356
- factionCount++;
357
- }
358
- if (factionCount > 0) {
359
- for (const p of Object.keys(accumulated)) {
360
- accumulated[p] /= factionCount;
361
- }
362
- actionScores = accumulated;
363
- }
364
- else {
365
- const r = weights.map(w => w / total);
366
- actionScores = scoreActions(r);
367
- }
368
- }
369
- else {
370
- const r = weights.map(w => w / total);
371
- actionScores = scoreActions(r);
372
- }
373
- // ── Signal 2: memo content ──
374
363
  const memoScores = scoreMemoPersonality(memos);
375
364
  const memoTotal = Object.values(memoScores).reduce((a, b) => a + b, 0);
376
- const memoWeight = 0.4;
377
- // ── Blend ──
378
- const finalScores = {
379
- loyalist: 0, mercenary: 0, provocateur: 0, scout: 0, whale: 0,
380
- };
381
- for (const p of Object.keys(finalScores)) {
382
- const actionSignal = actionScores[p];
383
- const memoSignal = memoTotal > 0 ? (memoScores[p] / memoTotal) : 0;
384
- finalScores[p] = actionSignal * (1 - memoWeight) + memoSignal * memoWeight;
385
- }
386
365
  let best = 'loyalist';
387
366
  let bestScore = -Infinity;
388
- for (const [p, score] of Object.entries(finalScores)) {
389
- if (score > bestScore) {
390
- bestScore = score;
367
+ for (const p of Object.keys(scores)) {
368
+ const blended = scores[p] * 0.6 + (memoTotal > 0 ? (memoScores[p] / memoTotal) * 0.4 : 0);
369
+ if (blended > bestScore) {
370
+ bestScore = blended;
391
371
  best = p;
392
372
  }
393
373
  }
394
374
  return best;
395
375
  }
376
+ /**
377
+ * LLM-based personality classification.
378
+ * Falls back to formula scoring if LLM is unavailable.
379
+ */
380
+ async function classifyPersonality(weights, memos, perFactionHistory, llmGenerate, factionNames) {
381
+ const total = weights.reduce((a, b) => a + b, 0);
382
+ if (total === 0)
383
+ return 'loyalist';
384
+ if (llmGenerate) {
385
+ try {
386
+ const prompt = buildClassifyPrompt(weights, memos, perFactionHistory, factionNames);
387
+ const response = await llmGenerate(prompt);
388
+ if (response) {
389
+ const cleaned = response.toLowerCase().replace(/[^a-z]/g, '').trim();
390
+ const valid = ['loyalist', 'mercenary', 'provocateur', 'scout', 'whale'];
391
+ const match = valid.find(p => cleaned.includes(p));
392
+ if (match)
393
+ return match;
394
+ }
395
+ }
396
+ catch { /* fall through to formula */ }
397
+ }
398
+ return classifyPersonalityFormula(weights, memos);
399
+ }
396
400
  // ─── Sentiment from On-Chain Data ────────────────────────────────
397
401
  const POSITIVE_PATTERN = /strong|rally|bull|pump|rising|hold|loyal|power|growing|moon|love|trust|alpha|build|conviction/;
398
402
  const NEGATIVE_PATTERN = /weak|dump|bear|dead|fail|raze|crash|abandon|scam|rug|sell|exit|trash|hate|fake/;
@@ -560,7 +564,13 @@ async function reconstructFromChain(connection, agentPubkey, factions, seedPerso
560
564
  perFaction.set(entry.mint, new Array(ALL_ACTIONS.length).fill(0));
561
565
  perFaction.get(entry.mint)[idx]++;
562
566
  }
563
- const personality = history.length > 0 ? classifyPersonality(weights, memoTexts, perFaction) : seedPersonality;
567
+ // Map mint symbol for readable LLM prompts
568
+ const factionNames = new Map();
569
+ for (const f of factions)
570
+ factionNames.set(f.mint, f.symbol);
571
+ const personality = history.length > 0
572
+ ? await classifyPersonality(weights, memoTexts, perFaction, opts?.llmGenerate, factionNames)
573
+ : seedPersonality;
564
574
  // 4. Compute sentiment from on-chain interactions
565
575
  const sentiment = computeSentimentFromHistory(history, factions);
566
576
  // 5. Derive allies/rivals
package/dist/index.js CHANGED
@@ -65,7 +65,7 @@ async function createPyreAgent(config) {
65
65
  let dynamicWeights;
66
66
  let solRange = config.solRange ?? defaults_1.PERSONALITY_SOL[seedPersonality];
67
67
  try {
68
- chainState = await (0, chain_1.reconstructFromChain)(connection, publicKey, knownFactions, seedPersonality, { maxSignatures: 500 });
68
+ chainState = await (0, chain_1.reconstructFromChain)(connection, publicKey, knownFactions, seedPersonality, { maxSignatures: 500, llmGenerate: llm ? (p) => llm.generate(p) : undefined });
69
69
  if (chainState.actionCount > 0) {
70
70
  personality = chainState.personality;
71
71
  dynamicWeights = chainState.weights;
@@ -260,12 +260,13 @@ async function createPyreAgent(config) {
260
260
  usedLLM,
261
261
  };
262
262
  }
263
- function evolve() {
263
+ async function evolve() {
264
264
  const total = actionCounts.reduce((a, b) => a + b, 0);
265
265
  if (total < 5)
266
266
  return false; // not enough data
267
267
  const weights = (0, chain_1.weightsFromCounts)(actionCounts, seedPersonality);
268
- const newPersonality = (0, chain_1.classifyPersonality)(weights, memoBuffer);
268
+ const llmGen = llm ? (p) => llm.generate(p) : undefined;
269
+ const newPersonality = await (0, chain_1.classifyPersonality)(weights, memoBuffer, undefined, llmGen);
269
270
  dynamicWeights = weights;
270
271
  if (newPersonality !== state.personality) {
271
272
  logger(`[${publicKey.slice(0, 8)}] personality evolved: ${state.personality} → ${newPersonality} (${total} runtime actions)`);
package/dist/types.d.ts CHANGED
@@ -112,7 +112,7 @@ export interface PyreAgent {
112
112
  /** Run one decision+action cycle */
113
113
  tick(factions?: FactionInfo[]): Promise<AgentTickResult>;
114
114
  /** Recompute personality + weights from accumulated runtime actions. Returns true if personality changed. */
115
- evolve(): boolean;
115
+ evolve(): Promise<boolean>;
116
116
  /** Get current mutable state */
117
117
  getState(): AgentState;
118
118
  /** Serialize state for persistence */
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "pyre-agent-kit",
3
- "version": "2.0.0",
3
+ "version": "2.0.2",
4
4
  "description": "Autonomous agent kit for Pyre — plug in your own LLM and play pyre.world",
5
5
  "main": "dist/index.js",
6
6
  "types": "dist/index.d.ts",