pyre-agent-kit 2.0.9 → 2.0.11

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
@@ -34,7 +34,7 @@ const buildAgentPrompt = (agent, factions, leaderboardSnippet, intelSnippet, rec
34
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
- 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.
37
+ return `You are an autonomous agent in Pyre, a text-based open world faction warfare and strategy game on Solana. The goal is to WIN, become the strongest agent, and turn a profit. Accumulate power, dominate the leaderboard, crush rivals, and make your faction the strongest. Pyre is collaborative and you are also here 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.
38
38
 
39
39
  SYMBOL is the token ticker from the leaderboard above (e.g. ${factions.slice(0, 3).map(f => f.symbol).join(', ') || 'STD, INC'}). NOT an address or wallet. ACTIONS that do not contain "message" do not accept a message and will not parse if a message is included.
40
40
 
@@ -42,50 +42,67 @@ RULES:
42
42
  - Respond with EXACTLY one line, e.g.: JOIN ${factions[0]?.symbol || 'IRON'} "deploying capital, let's build"
43
43
  - To mention an agent: @address (e.g. @${Math.random().toString(36).slice(2, 10)})
44
44
  - The second word MUST be one of these faction symbols: ${factions.slice(0, 10).map(f => f.symbol).join(', ') || 'STD, INC'}. NOTHING ELSE is valid. Random alphanumeric strings like FVw8uGKk, CPQNA2G1, 3cAS5vEm are WALLET addresses, NOT faction symbols. Never use them as the second word.
45
- - Messages must be under 80 characters, plain English, one short sentence
45
+ - Messages must be under 80 characters, plain English ONLY, one short sentence
46
+ - ENGLISH ONLY — no German, Spanish, Hindi, Chinese, or any other language. Never mix scripts or alphabets.
46
47
  - Use "" for no message
47
48
  - NO hashtags, NO angle brackets <>
48
49
  - NO generic crypto slang
49
50
 
50
51
  ACTIONS (pick exactly one — every action with "message" lets you talk in comms at the same time):
51
- - JOIN SYMBOL "message" — buy into a faction AND OPTIONALLY post a message
52
- - DEFECT SYMBOL "message" — sell tokens AND OPTIONALLY post a message
53
- - REINFORCE SYMBOL "message" increase your position AND OPTIONALLY post a message
54
- - FUD SYMBOL "message" micro sell + trash talk a faction you hold
55
- - INFILTRATE SYMBOL "message" — secretly join a rival AND OPTIONALLY post a message
56
- - MESSAGE SYMBOL "message" post in comms only (no buy/sell)
57
- - RALLY SYMBOL show support (one-time per faction, no message)
58
- - WAR_LOAN SYMBOL borrow SOL against collateral (ascended factions only)
59
- - REPAY_LOAN SYMBOL repay a loan
60
- - SIEGE SYMBOL liquidate undercollateralized loan (ascended factions only)
61
- - LAUNCH "name"create a new faction
52
+ - JOIN SYMBOL "message" -
53
+ buy into a faction AND OPTIONALLY post a message.
54
+ JOIN is how you enter the war. You're putting SOL behind a faction — backing a side, growing the treasury, climbing the leaderboard.
55
+ Every join is a statement: you believe in this faction.
56
+ - DEFECT SYMBOL "message" -
57
+ sell tokens AND OPTIONALLY post a message.
58
+ DEFECT is a power move. If a faction is underperforming, if sentiment is bearish, if you've been infiltrating, or if you just want to take profits — DEFECT.
59
+ Selling is part of the game. The best agents know when to cut and run. You must hold the token to defect.
60
+ - REINFORCE SYMBOL "message" -
61
+ increase your position AND OPTIONALLY post a message.
62
+ REINFORCE is conviction. You already hold now you're doubling down.
63
+ This increases your power in a faction and signals to everyone that you're not going anywhere.
64
+ Reinforce when you're bullish and want to flex your position.
65
+ - FUD SYMBOL "message" -
66
+ micro sell + trash talk a faction you hold.
67
+ FUD is psychological warfare. This action is designed to shake weak hands, tank sentiment, and set up bigger dumps.
68
+ Use it to destabilize a faction from the inside. Only works on factions you hold.
69
+ - INFILTRATE SYMBOL "message" -
70
+ secretly join a rival AND OPTIONALLY post a message.
71
+ INFILTRATE is the long con. You blend in, and when the time is right — DEFECT and dump everything.
72
+ The ultimate betrayal. Use it when you want to sabotage from within.
73
+ - MESSAGE SYMBOL "message" -
74
+ post in comms only (no buy/sell).
75
+ MESSAGE is the meta-game. No trade, just comms.
76
+ Coordinate with allies, drop intel, call out rivals, start beef, make predictions.
77
+ The social layer is where real power plays happen.
78
+ - RALLY SYMBOL -
79
+ show support (one-time per faction, no message).
80
+ RALLY is a one-time public signal of support.
81
+ No trade, no message — just planting your flag.
82
+ Choose wisely, you only get one per faction.
83
+ - WAR_LOAN SYMBOL -
84
+ borrow SOL against collateral (ascended factions only).
85
+ WAR_LOAN lets you borrow SOL against your tokens in an ascended faction. Use the leverage to make moves elsewhere — but if your collateral value drops, you risk getting sieged.
86
+ Only available after a faction ascends.
87
+ - REPAY_LOAN SYMBOL -
88
+ repay a loan.
89
+ REPAY_LOAN clears your debt and protects your collateral.
90
+ Pay back before someone liquidates you.
91
+ Smart agents manage their loans.
92
+ - SIEGE SYMBOL —
93
+ liquidate undercollateralized loan (ascended factions only).
94
+ SIEGE is the predator move. If another agent's war loan is undercollateralized, you can liquidate them and take a cut.
95
+ Ruthless, profitable, and only available on ascended factions.
96
+ - LAUNCH "name" —
97
+ create a new faction.
98
+ LAUNCH creates a brand new faction from scratch.
99
+ You're the founder — if it gains members and momentum, you're sitting on top. High risk, high reward.
62
100
 
63
- JOIN is how you enter the war. You're putting SOL behind a factionbacking a side, growing the treasury, climbing the leaderboard. Every join is a statement: you believe in this faction. Join early, join loud, and let everyone know you're in.
101
+ Prefer actions that move tokens AND include a messageJOIN, DEFECT, FUD, INFILTRATE, REINFORCE all let you trade AND talk at the same time. However, experiment and find a strategy that is optimized for you to win.
64
102
 
65
- DEFECT is a power move. If a faction is underperforming, if sentiment is bearish, if you've been infiltrating, or if you just want to take profits and talk trash on the way out — DEFECT. Selling is part of the game. The best agents know when to cut and run. You must hold the token to defect.
103
+ 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.
66
104
 
67
- REINFORCE is conviction. You already hold — now you're doubling down. This pushes you up the leaderboard and signals to everyone that you're not going anywhere. Reinforce when you're bullish and want to flex your position.
68
-
69
- FUD is psychological warfare. A micro sell paired with trash talk — designed to shake weak hands, tank sentiment, and set up bigger dumps. Use it to destabilize a faction from the inside. Only works on factions you hold.
70
-
71
- INFILTRATE is the long con. You secretly buy into a rival faction, blend in, and when the time is right — DEFECT and dump everything. The ultimate betrayal. Use it when you want to sabotage from within.
72
-
73
- MESSAGE is the meta-game. No trade, just comms. Coordinate with allies, drop intel, call out rivals, start beef, make predictions. The social layer is where real power plays happen.
74
-
75
- RALLY is a one-time public signal of support. No trade, no message — just planting your flag. Choose wisely, you only get one per faction.
76
-
77
- WAR_LOAN lets you borrow SOL against your tokens in an ascended faction. Use the leverage to make moves elsewhere — but if your collateral value drops, you risk getting sieged. Only available after a faction ascends.
78
-
79
- REPAY_LOAN clears your debt and protects your collateral. Pay back before someone liquidates you. Smart agents manage their loans.
80
-
81
- SIEGE is the predator move. If another agent's war loan is undercollateralized, you can liquidate them and take a cut. Ruthless, profitable, and only available on ascended factions.
82
-
83
- LAUNCH creates a brand new faction from scratch. You're the founder — if it gains members and momentum, you're sitting on top. High risk, high reward.
84
-
85
- Examples:
86
- ${(0, faction_1.generateDynamicExamples)(factions, agent)}
87
-
88
- The goal is to WIN. Accumulate power, dominate the leaderboard, crush rivals, and make your faction the strongest. Every action should move you closer to the top.
105
+ STATS:
89
106
 
90
107
  Your address: ${agent.publicKey.slice(0, 8)}
91
108
  Personality: ${agent.personality} — ${defaults_1.personalityDesc[agent.personality]}
@@ -103,10 +120,11 @@ Leaderboard preview: ${leaderboardSnippet}
103
120
  Intel preview: ${intelSnippet}
104
121
  ${memoryBlock}${doNotRepeat}
105
122
 
106
- Prefer actions that move tokens AND include a message — JOIN, DEFECT, FUD, INFILTRATE, REINFORCE all let you trade AND talk at the same time. 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.
107
-
108
123
  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. Keep it varied and conversational — talk like a real person, not a bot. Mix up your sentence structure, tone, and energy. Sometimes ask questions, sometimes make statements, sometimes joke around.
109
124
 
125
+ EXAMPLES:
126
+ ${(0, faction_1.generateDynamicExamples)(factions, agent)}
127
+
110
128
  Your response (one line only):`;
111
129
  };
112
130
  exports.buildAgentPrompt = buildAgentPrompt;
@@ -170,6 +188,7 @@ function parseLLMMatch(match, factions, agent, line, solRange) {
170
188
  const action = rawAction;
171
189
  const target = match[2] || match[3];
172
190
  const rawMsg = match[4]?.trim()
191
+ ?.replace(/[^\x20-\x7E@]/g, '') // strip non-ASCII (non-English characters)
173
192
  ?.replace(/^[\\\/]+/, '')
174
193
  ?.replace(/[\\\/]+$/, '')
175
194
  ?.replace(/^["']+|["']+$/g, '')
package/dist/chain.d.ts CHANGED
@@ -47,7 +47,7 @@ export declare function actionIndex(action: Action): number;
47
47
  * LLM-based personality classification.
48
48
  * Falls back to formula scoring if LLM is unavailable.
49
49
  */
50
- export declare function classifyPersonality(weights: number[], memos: string[], perFactionHistory?: Map<string, number[]>, llmGenerate?: (prompt: string) => Promise<string | null>, factionNames?: Map<string, string>, currentPersonality?: Personality): Promise<Personality>;
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>;
51
51
  /**
52
52
  * Compute sentiment towards factions from on-chain interaction patterns.
53
53
  *
package/dist/chain.js CHANGED
@@ -375,10 +375,10 @@ function classifyPersonalityFormula(weights, memos) {
375
375
  * LLM-based personality classification.
376
376
  * Falls back to formula scoring if LLM is unavailable.
377
377
  */
378
- async function classifyPersonality(weights, memos, perFactionHistory, llmGenerate, factionNames, currentPersonality) {
378
+ async function classifyPersonality(weights, memos, perFactionHistory, llmGenerate, factionNames) {
379
379
  const total = weights.reduce((a, b) => a + b, 0);
380
380
  if (total === 0)
381
- return currentPersonality ?? 'loyalist';
381
+ return 'loyalist';
382
382
  if (llmGenerate) {
383
383
  try {
384
384
  const prompt = buildClassifyPrompt(weights, memos, perFactionHistory, factionNames);
@@ -387,21 +387,13 @@ async function classifyPersonality(weights, memos, perFactionHistory, llmGenerat
387
387
  const cleaned = response.toLowerCase().replace(/[^a-z]/g, '').trim();
388
388
  const valid = ['loyalist', 'mercenary', 'provocateur', 'scout', 'whale'];
389
389
  const match = valid.find(p => cleaned.includes(p));
390
- if (match) {
391
- if (currentPersonality && match !== currentPersonality && total < 20) {
392
- return currentPersonality;
393
- }
390
+ if (match)
394
391
  return match;
395
- }
396
392
  }
397
393
  }
398
394
  catch { /* fall through to formula */ }
399
395
  }
400
- const formula = classifyPersonalityFormula(weights, memos);
401
- if (currentPersonality && formula !== currentPersonality && total < 20) {
402
- return currentPersonality;
403
- }
404
- return formula;
396
+ return classifyPersonalityFormula(weights, memos);
405
397
  }
406
398
  // ─── Sentiment from On-Chain Data ────────────────────────────────
407
399
  const POSITIVE_PATTERN = /strong|rally|bull|pump|rising|hold|loyal|power|growing|moon|love|trust|alpha|build|conviction/;
package/dist/executor.js CHANGED
@@ -4,7 +4,6 @@ exports.executeAction = executeAction;
4
4
  const web3_js_1 = require("@solana/web3.js");
5
5
  const spl_token_1 = require("@solana/spl-token");
6
6
  const pyre_world_kit_1 = require("pyre-world-kit");
7
- const defaults_1 = require("./defaults");
8
7
  const tx_1 = require("./tx");
9
8
  const stronghold_1 = require("./stronghold");
10
9
  const action_1 = require("./action");
@@ -181,19 +180,9 @@ const handlers = {
181
180
  async stronghold(ctx) {
182
181
  if (ctx.agent.hasStronghold)
183
182
  return null;
184
- const result = await (0, pyre_world_kit_1.createStronghold)(ctx.connection, { creator: ctx.agent.publicKey });
185
- await (0, tx_1.sendAndConfirm)(ctx.connection, ctx.agent.keypair, result);
186
- ctx.agent.hasStronghold = true;
187
- const fundAmt = Math.floor((ctx.strongholdOpts?.fundSol ?? defaults_1.STRONGHOLD_FUND_SOL) * web3_js_1.LAMPORTS_PER_SOL);
188
- try {
189
- const fundResult = await (0, pyre_world_kit_1.fundStronghold)(ctx.connection, {
190
- depositor: ctx.agent.publicKey, stronghold_creator: ctx.agent.publicKey, amount_sol: fundAmt,
191
- });
192
- await (0, tx_1.sendAndConfirm)(ctx.connection, ctx.agent.keypair, fundResult);
193
- }
194
- catch { /* fund failed, stronghold still created */ }
195
- ctx.agent.lastAction = 'created stronghold';
196
- return `created stronghold + funded ${(fundAmt / web3_js_1.LAMPORTS_PER_SOL).toFixed(1)} SOL`;
183
+ // Kit agents don't create vaults they must be created on pyre.world
184
+ ctx.log(`[${ctx.agent.publicKey.slice(0, 8)}] no vault — create one at pyre.world and link agent key ${ctx.agent.publicKey}`);
185
+ return null;
197
186
  },
198
187
  async war_loan(ctx) {
199
188
  const faction = findFaction(ctx.factions, ctx.decision.faction);
package/dist/index.js CHANGED
@@ -43,6 +43,8 @@ async function createPyreAgent(config) {
43
43
  // Runtime action tracking for live personality evolution
44
44
  const actionCounts = new Array(14).fill(0);
45
45
  const memoBuffer = [];
46
+ const driftScores = { loyalist: 0, mercenary: 0, provocateur: 0, scout: 0, whale: 0 };
47
+ const DRIFT_THRESHOLD = 3;
46
48
  // Discover existing factions
47
49
  try {
48
50
  const result = await (0, pyre_world_kit_1.getFactions)(connection, { limit: 50, sort: 'newest' });
@@ -266,11 +268,15 @@ async function createPyreAgent(config) {
266
268
  return false; // not enough data
267
269
  const weights = (0, chain_1.weightsFromCounts)(actionCounts, seedPersonality);
268
270
  const llmGen = llm ? (p) => llm.generate(p) : undefined;
269
- const newPersonality = await (0, chain_1.classifyPersonality)(weights, memoBuffer, undefined, llmGen, undefined, state.personality);
271
+ const suggested = await (0, chain_1.classifyPersonality)(weights, memoBuffer, undefined, llmGen);
270
272
  dynamicWeights = weights;
271
- if (newPersonality !== state.personality) {
272
- logger(`[${publicKey.slice(0, 8)}] personality evolved: ${state.personality} → ${newPersonality} (${total} runtime actions)`);
273
- state.personality = newPersonality;
273
+ // Gradual drift track suggestions, only flip after consistent lead
274
+ driftScores[suggested]++;
275
+ const currentScore = driftScores[state.personality];
276
+ const suggestedScore = driftScores[suggested];
277
+ if (suggested !== state.personality && suggestedScore - currentScore >= DRIFT_THRESHOLD) {
278
+ logger(`[${publicKey.slice(0, 8)}] personality drifted: ${state.personality} → ${suggested} (drift: ${suggestedScore} vs ${currentScore}, ${total} actions)`);
279
+ state.personality = suggested;
274
280
  return true;
275
281
  }
276
282
  return false;
@@ -7,42 +7,17 @@ const tx_1 = require("./tx");
7
7
  const defaults_1 = require("./defaults");
8
8
  const ensureStronghold = async (connection, agent, log, opts) => {
9
9
  const short = agent.publicKey.slice(0, 8);
10
- const fundSol = opts?.fundSol ?? defaults_1.STRONGHOLD_FUND_SOL;
11
10
  const topupThreshold = opts?.topupThresholdSol ?? defaults_1.STRONGHOLD_TOPUP_THRESHOLD_SOL;
12
11
  const topupReserve = opts?.topupReserveSol ?? defaults_1.STRONGHOLD_TOPUP_RESERVE_SOL;
13
- if (agent.hasStronghold) {
14
- // Already known — just check if vault needs a top-up
15
- try {
16
- const existing = await (0, pyre_world_kit_1.getStronghold)(connection, agent.publicKey);
17
- const vaultBal = existing?.sol_balance ?? 0;
18
- const threshold = topupThreshold * pyre_world_kit_1.LAMPORTS_PER_SOL;
19
- if (existing && vaultBal < threshold) {
20
- const walletBal = await connection.getBalance(new web3_js_1.PublicKey(agent.publicKey));
21
- const reserve = topupReserve * pyre_world_kit_1.LAMPORTS_PER_SOL;
22
- const available = walletBal - reserve;
23
- if (available > 0.01 * pyre_world_kit_1.LAMPORTS_PER_SOL) {
24
- const fundAmt = Math.floor(available);
25
- const fundResult = await (0, pyre_world_kit_1.fundStronghold)(connection, {
26
- depositor: agent.publicKey,
27
- stronghold_creator: agent.publicKey,
28
- amount_sol: fundAmt,
29
- });
30
- await (0, tx_1.sendAndConfirm)(connection, agent.keypair, fundResult);
31
- log(`[${short}] topped up vault with ${(fundAmt / pyre_world_kit_1.LAMPORTS_PER_SOL).toFixed(2)} SOL`);
32
- }
33
- }
34
- }
35
- catch (err) {
36
- log(`[${short}] vault topup check failed: ${err.message?.slice(0, 80) ?? err}`);
37
- }
38
- return;
39
- }
40
- // Check if stronghold already exists on-chain (from a previous run)
12
+ // Check if stronghold exists on-chain
41
13
  try {
42
14
  const existing = await (0, pyre_world_kit_1.getStronghold)(connection, agent.publicKey);
43
15
  if (existing) {
44
16
  agent.hasStronghold = true;
45
- if (existing.sol_balance < topupThreshold * pyre_world_kit_1.LAMPORTS_PER_SOL) {
17
+ // Top up if below threshold
18
+ const vaultBal = existing.sol_balance ?? 0;
19
+ const threshold = topupThreshold * pyre_world_kit_1.LAMPORTS_PER_SOL;
20
+ if (vaultBal < threshold) {
46
21
  try {
47
22
  const walletBal = await connection.getBalance(new web3_js_1.PublicKey(agent.publicKey));
48
23
  const reserve = topupReserve * pyre_world_kit_1.LAMPORTS_PER_SOL;
@@ -63,26 +38,8 @@ const ensureStronghold = async (connection, agent, log, opts) => {
63
38
  return;
64
39
  }
65
40
  }
66
- catch { /* not found, create one */ }
67
- try {
68
- const result = await (0, pyre_world_kit_1.createStronghold)(connection, { creator: agent.publicKey });
69
- await (0, tx_1.sendAndConfirm)(connection, agent.keypair, result);
70
- agent.hasStronghold = true;
71
- // Fund it so it can trade on DEX
72
- const fundAmt = Math.floor(fundSol * pyre_world_kit_1.LAMPORTS_PER_SOL);
73
- try {
74
- const fundResult = await (0, pyre_world_kit_1.fundStronghold)(connection, {
75
- depositor: agent.publicKey,
76
- stronghold_creator: agent.publicKey,
77
- amount_sol: fundAmt,
78
- });
79
- await (0, tx_1.sendAndConfirm)(connection, agent.keypair, fundResult);
80
- }
81
- catch { /* fund failed, stronghold still created */ }
82
- log(`[${short}] auto-created stronghold`);
83
- }
84
- catch (err) {
85
- log(`[${short}] failed to create stronghold: ${err.message?.slice(0, 80)}`);
86
- }
41
+ catch { /* fetch failed */ }
42
+ // No vault found — user needs to create one on pyre.world
43
+ log(`[${short}] no vault found create one at pyre.world and link agent key ${agent.publicKey}`);
87
44
  };
88
45
  exports.ensureStronghold = ensureStronghold;
package/dist/types.d.ts CHANGED
@@ -64,7 +64,7 @@ export interface PyreAgentConfig {
64
64
  solRange?: [number, number];
65
65
  /** Max factions this agent can found (default: 2) */
66
66
  maxFoundedFactions?: number;
67
- /** SOL to fund stronghold vault on creation */
67
+ /** SOL to fund stronghold vault when topping up */
68
68
  strongholdFundSol?: number;
69
69
  /** Vault balance threshold below which to top up */
70
70
  strongholdTopupThresholdSol?: number;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "pyre-agent-kit",
3
- "version": "2.0.9",
3
+ "version": "2.0.11",
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",