pyre-agent-kit 2.0.0 → 2.0.1

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,109 @@ 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:
322
+ - loyalist: Ride-or-die for their factions. Buys in and hypes. Messages are positive, supportive, builds community. Rarely defects or fuds.
323
+ - mercenary: Profit-driven lone wolf. Infiltration pattern: joins factions, fuds them, then defects (dumps). Messages are self-serving. High faction churn.
324
+ - provocateur: Lives for chaos and hot takes. High fud rate, starts beef, stirs drama. Messages are inflammatory, challenging, confrontational.
325
+ - scout: Intel operative. Messages are analytical, observational, data-driven. Reports on movements, asks questions. Rarely fuds aggressively.
326
+ - whale: Silent trader. Very few messages relative to trades. Big moves, few words. When they speak, it's brief and authoritative.
327
+
328
+ Agent behavior data:
329
+ ${actionSummary}
330
+ ${memoBlock}
331
+
332
+ Based on the action patterns AND message content/tone, classify this agent.
333
+ Respond with ONLY a single word: loyalist, mercenary, provocateur, scout, or whale.`;
334
+ }
335
+ /** Formula-based fallback for personality classification */
336
+ function classifyPersonalityFormula(weights, memos) {
337
+ const total = weights.reduce((a, b) => a + b, 0);
338
+ if (total === 0)
339
+ return 'loyalist';
340
+ const r = weights.map(w => w / total);
309
341
  const joinRate = r[0], defectRate = r[1], rallyRate = r[2], messageRate = r[4];
310
342
  const warLoanRate = r[6], siegeRate = r[8], titheRate = r[11];
311
343
  const infiltrateRate = r[12], fudRate = r[13];
312
344
  const commsRate = messageRate + fudRate;
313
345
  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
346
+ const fudRatio = commsRate > 0 ? fudRate / commsRate : 0;
316
347
  const msgRatio = commsRate > 0 ? messageRate / commsRate : 0;
317
- return {
318
- // Loyalist: high message ratio, joins, rallies — positive vibes
348
+ const scores = {
319
349
  loyalist: msgRatio * 3 + joinRate * 2 + rallyRate * 3 + titheRate * 2
320
350
  - fudRatio * 4 - defectRate * 3,
321
- // Mercenary: defect + fud cycle, infiltration pattern
322
351
  mercenary: defectRate * 4 + fudRatio * 2
323
352
  + (defectRate > 0 && fudRate > 0 ? 3 : 0)
324
353
  + infiltrateRate * 3 + warLoanRate * 2 + siegeRate * 2
325
354
  - msgRatio * 2 - rallyRate * 2,
326
- // Provocateur: high fud ratio — more fud than message
327
355
  provocateur: fudRatio * 4 + infiltrateRate * 2
328
356
  - msgRatio * 2 - rallyRate * 2,
329
- // Scout: high message ratio, low fud — observes, doesn't attack
330
357
  scout: msgRatio * 3 + rallyRate - fudRatio * 3 - defectRate,
331
- // Whale: trades a lot but talks very little
332
358
  whale: (tradeRate > commsRate ? 1 : 0) * 2 + warLoanRate * 3 + defectRate * 2
333
359
  - commsRate * 3,
334
360
  };
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
361
  const memoScores = scoreMemoPersonality(memos);
375
362
  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
363
  let best = 'loyalist';
387
364
  let bestScore = -Infinity;
388
- for (const [p, score] of Object.entries(finalScores)) {
389
- if (score > bestScore) {
390
- bestScore = score;
365
+ for (const p of Object.keys(scores)) {
366
+ const blended = scores[p] * 0.6 + (memoTotal > 0 ? (memoScores[p] / memoTotal) * 0.4 : 0);
367
+ if (blended > bestScore) {
368
+ bestScore = blended;
391
369
  best = p;
392
370
  }
393
371
  }
394
372
  return best;
395
373
  }
374
+ /**
375
+ * LLM-based personality classification.
376
+ * Falls back to formula scoring if LLM is unavailable.
377
+ */
378
+ async function classifyPersonality(weights, memos, perFactionHistory, llmGenerate, factionNames) {
379
+ const total = weights.reduce((a, b) => a + b, 0);
380
+ if (total === 0)
381
+ return 'loyalist';
382
+ if (llmGenerate) {
383
+ try {
384
+ const prompt = buildClassifyPrompt(weights, memos, perFactionHistory, factionNames);
385
+ const response = await llmGenerate(prompt);
386
+ if (response) {
387
+ const cleaned = response.toLowerCase().replace(/[^a-z]/g, '').trim();
388
+ const valid = ['loyalist', 'mercenary', 'provocateur', 'scout', 'whale'];
389
+ const match = valid.find(p => cleaned.includes(p));
390
+ if (match)
391
+ return match;
392
+ }
393
+ }
394
+ catch { /* fall through to formula */ }
395
+ }
396
+ return classifyPersonalityFormula(weights, memos);
397
+ }
396
398
  // ─── Sentiment from On-Chain Data ────────────────────────────────
397
399
  const POSITIVE_PATTERN = /strong|rally|bull|pump|rising|hold|loyal|power|growing|moon|love|trust|alpha|build|conviction/;
398
400
  const NEGATIVE_PATTERN = /weak|dump|bear|dead|fail|raze|crash|abandon|scam|rug|sell|exit|trash|hate|fake/;
@@ -560,7 +562,13 @@ async function reconstructFromChain(connection, agentPubkey, factions, seedPerso
560
562
  perFaction.set(entry.mint, new Array(ALL_ACTIONS.length).fill(0));
561
563
  perFaction.get(entry.mint)[idx]++;
562
564
  }
563
- const personality = history.length > 0 ? classifyPersonality(weights, memoTexts, perFaction) : seedPersonality;
565
+ // Map mint symbol for readable LLM prompts
566
+ const factionNames = new Map();
567
+ for (const f of factions)
568
+ factionNames.set(f.mint, f.symbol);
569
+ const personality = history.length > 0
570
+ ? await classifyPersonality(weights, memoTexts, perFaction, opts?.llmGenerate, factionNames)
571
+ : seedPersonality;
564
572
  // 4. Compute sentiment from on-chain interactions
565
573
  const sentiment = computeSentimentFromHistory(history, factions);
566
574
  // 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.1",
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",