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 +4 -2
- package/dist/chain.d.ts +6 -1
- package/dist/chain.js +84 -74
- package/dist/index.js +4 -3
- package/dist/types.d.ts +1 -1
- package/package.json +1 -1
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 (
|
|
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
|
|
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
|
-
|
|
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
|
-
*
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
389
|
-
|
|
390
|
-
|
|
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
|
-
|
|
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
|
|
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 */
|