pyre-agent-kit 4.4.0 → 4.4.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.d.ts CHANGED
@@ -2,6 +2,7 @@ import type { PyreKit } from 'pyre-world-kit';
2
2
  import { AgentState, FactionInfo, LLMAdapter, LLMDecision } from './types';
3
3
  export interface LLMDecideOptions {
4
4
  compact?: boolean;
5
+ min?: boolean;
5
6
  onPromptTable?: (header: string, rows: string[]) => void;
6
7
  }
7
8
  export declare const pendingScoutResults: Map<string, string[]>;
@@ -11,6 +12,7 @@ export interface FactionContext {
11
12
  nearby: FactionInfo[];
12
13
  all: FactionInfo[];
13
14
  }
14
- export declare const buildAgentPrompt: (kit: PyreKit, agent: AgentState, factionCtx: FactionContext, recentMessages: string[], solRange?: [number, number], holdings?: Map<string, number>) => Promise<string>;
15
- export declare const buildCompactModelPrompt: (kit: PyreKit, agent: AgentState, factionCtx: FactionContext, recentMessages: string[], solRange?: [number, number], holdings?: Map<string, number>) => Promise<string>;
15
+ export declare const buildAgentPrompt: (kit: PyreKit, agent: AgentState, factionCtx: FactionContext, solRange?: [number, number], holdings?: Map<string, number>) => Promise<string>;
16
+ export declare const buildCompactModelPrompt: (kit: PyreKit, agent: AgentState, factionCtx: FactionContext, solRange?: [number, number], holdings?: Map<string, number>) => Promise<string>;
17
+ export declare const buildMinimumPrompt: (kit: PyreKit, agent: AgentState, factionCtx: FactionContext, solRange?: [number, number], holdings?: Map<string, number>) => Promise<string>;
16
18
  export declare function llmDecide(kit: PyreKit, agent: AgentState, factions: FactionInfo[], recentMessages: string[], llm: LLMAdapter, log: (msg: string) => void, solRange?: [number, number], options?: LLMDecideOptions): Promise<LLMDecision | null>;
package/dist/agent.js CHANGED
@@ -1,12 +1,12 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.buildCompactModelPrompt = exports.buildAgentPrompt = exports.pendingScoutResults = void 0;
3
+ exports.buildMinimumPrompt = exports.buildCompactModelPrompt = exports.buildAgentPrompt = exports.pendingScoutResults = void 0;
4
4
  exports.llmDecide = llmDecide;
5
5
  const defaults_1 = require("./defaults");
6
6
  const util_1 = require("./util");
7
7
  const faction_1 = require("./faction");
8
8
  exports.pendingScoutResults = new Map();
9
- const buildAgentPrompt = async (kit, agent, factionCtx, recentMessages, solRange, holdings) => {
9
+ const buildAgentPrompt = async (kit, agent, factionCtx, solRange, holdings) => {
10
10
  const [minSol, maxSol] = solRange ?? defaults_1.PERSONALITY_SOL[agent.personality];
11
11
  const gameState = kit.state.state;
12
12
  const holdingsEntries = [...(holdings?.entries() ?? [])];
@@ -221,7 +221,7 @@ example format: ${(0, util_1.pick)([
221
221
  >`;
222
222
  };
223
223
  exports.buildAgentPrompt = buildAgentPrompt;
224
- const buildCompactModelPrompt = async (kit, agent, factionCtx, recentMessages, solRange, holdings) => {
224
+ const buildCompactModelPrompt = async (kit, agent, factionCtx, solRange, holdings) => {
225
225
  const gameState = kit.state.state;
226
226
  const [minSol, maxSol] = solRange ?? defaults_1.PERSONALITY_SOL[agent.personality];
227
227
  const holdingsEntries = [...(holdings?.entries() ?? [])];
@@ -260,7 +260,7 @@ const buildCompactModelPrompt = async (kit, agent, factionCtx, recentMessages, s
260
260
  return 'RS';
261
261
  };
262
262
  // Sentiment label
263
- const sentLabel = (s) => s > 0.5 ? 'BULL' : s < -0.5 ? 'BEAR' : 'NEUT';
263
+ const sentLabel = (s) => `${s >= 0 ? '+' : ''}${s.toFixed(2)}`;
264
264
  // Per-position PnL (numeric, 2 decimal places)
265
265
  const pnlValue = (valueSol, bal) => {
266
266
  if (totalTokens <= 0 || netInvested <= 0)
@@ -269,12 +269,6 @@ const buildCompactModelPrompt = async (kit, agent, factionCtx, recentMessages, s
269
269
  const posPnl = valueSol - estCost;
270
270
  return `${posPnl >= 0 ? '+' : ''}${posPnl.toFixed(2)}`;
271
271
  };
272
- // Discovery tag
273
- const discoveryTag = (f) => {
274
- if (nearbyMints.has(f.mint))
275
- return 'NB';
276
- return 'UX';
277
- };
278
272
  // Build flat faction rows: FACTION (MCAP) STATUS MBR FNR [NB|UX]
279
273
  const factionRows = [];
280
274
  const seenMints = new Set();
@@ -330,7 +324,7 @@ const buildCompactModelPrompt = async (kit, agent, factionCtx, recentMessages, s
330
324
  const nonMemberFids = tableFids.filter(fid => fid !== m);
331
325
  const f1 = nonMemberFids.length > 0 ? (0, util_1.pick)(nonMemberFids) : m;
332
326
  const f2 = nonMemberFids.length > 1 ? (0, util_1.pick)(nonMemberFids.filter(fid => fid !== f1)) : f1;
333
- return `You are an autonomous agent playing in Pyre, a faction warfare game. Think in English only. Think linearly: situation → decision → reason. Do not repeat yourself. Do NOT overthink, chess/strategy mood.
327
+ return `Welcome to Pyre, a faction warfare game. Think in English only. Think linearly: situation → decision → reason. Do not repeat yourself. Do NOT overthink, chess/strategy mood.
334
328
  --- GOAL:
335
329
  Maximize long-term profit and faction dominance.
336
330
  --- LEGEND:
@@ -344,7 +338,7 @@ ASN: ascended factions, established, more members. treasuries active. 0.04% war
344
338
  MBR: true = you are a member. false = you are not a member.
345
339
  FNR: true = you created it. false = you did not create it.
346
340
  PNL: per-position profit. positive = winning, negative = losing.
347
- SENT: sentiment score. BULL=positive, BEAR=negative, NEUT=neutral.
341
+ SENT: sentiment score. positive = bullish, negative = bearish.
348
342
  --- YOU ARE:
349
343
  NAME: @AP${agent.publicKey.slice(0, 4)}
350
344
  BIO: ${defaults_1.personalityDesc[agent.personality]}
@@ -370,7 +364,7 @@ REPLACE * with a ONE sentence RESPONSE, always in double quotes.
370
364
  (_) - skip turn.
371
365
  --- RULES:
372
366
  (+) and (&) increase MCAP. (-) decreases MCAP.
373
- (!) and (#) are your voice.
367
+ (!) and (#) are your voice. (!) increases SENT. (#) decreases SENT.
374
368
  (!) any FACTIONS.
375
369
  (^) FACTIONS where STATUS=RD.
376
370
  (~) FACTIONS where STATUS=ASN.
@@ -386,8 +380,8 @@ REPLACE * with a ONE sentence RESPONSE, always in double quotes.
386
380
  - in FACTIONS where MBR=true, if MCAP increases, your PNL will increase.
387
381
  - (&) and (!) to push FACTIONS where MBR=true and STATUS=RS to STATUS=ASN.
388
382
  - consider (-) FACTIONS where MBR=true and PNL is positive to lock in profits.
389
- - consider (-) FACTIONS where MBR=true and PNL is negative unless FNR=true or SENT=BULL.
390
- - when HLTH is negative, prefer (_) or (-) weakest FACTIONS where MBR=true. (+) or (&) ONLY if you see opportunity.
383
+ - consider (-) FACTIONS where MBR=true and PNL is negative unless FNR=true or SENT is positive.
384
+ - when HLTH is negative, consider (_) or (-) weakest FACTIONS where MBR=true. (+) or (&) ONLY if you see opportunity.
391
385
  - (_) if holding is the optimal move.
392
386
  ---
393
387
  one move per turn. output EXACTLY one line.
@@ -401,6 +395,182 @@ example format: ${(0, util_1.pick)([
401
395
  >`;
402
396
  };
403
397
  exports.buildCompactModelPrompt = buildCompactModelPrompt;
398
+ const buildMinimumPrompt = async (kit, agent, factionCtx, solRange, holdings) => {
399
+ const gameState = kit.state.state;
400
+ const [minSol, maxSol] = solRange ?? defaults_1.PERSONALITY_SOL[agent.personality];
401
+ const holdingsEntries = [...(holdings?.entries() ?? [])];
402
+ const TOKEN_MULTIPLIER = 1_000_000;
403
+ const valued = holdingsEntries
404
+ .map(([mint, bal]) => {
405
+ const f = factionCtx.all.find((ff) => ff.mint === mint);
406
+ if (!f)
407
+ return null;
408
+ return { id: mint.slice(-8), mint, valueSol: (bal / TOKEN_MULTIPLIER) * (f.price_sol ?? 0), bal };
409
+ })
410
+ .filter(Boolean)
411
+ .sort((a, b) => {
412
+ const aFnr = gameState.founded.includes(a.mint) ? 1 : 0;
413
+ const bFnr = gameState.founded.includes(b.mint) ? 1 : 0;
414
+ if (aFnr !== bFnr)
415
+ return bFnr - aFnr; // founded first
416
+ return b.valueSol - a.valueSol; // then by value
417
+ });
418
+ const pnl = (gameState.totalSolReceived - gameState.totalSolSpent) / 1e9;
419
+ const totalHoldingsValue = valued.reduce((sum, v) => sum + v.valueSol, 0);
420
+ const unrealizedPnl = totalHoldingsValue + pnl;
421
+ const netInvested = (gameState.totalSolSpent - gameState.totalSolReceived) / 1e9;
422
+ const totalTokens = holdingsEntries.reduce((sum, [, bal]) => sum + bal, 0);
423
+ const founded = gameState.founded.slice(0, 2).map((m) => m.slice(-8));
424
+ const heldMints = new Set(holdingsEntries.map(([m]) => m));
425
+ const memberOf = valued.filter((v) => v.valueSol > 0.001).map((v) => v.id);
426
+ const foundedSet = new Set(gameState.founded);
427
+ const nearbyMints = new Set(factionCtx.nearby.map(f => f.mint));
428
+ // Status tag for each faction
429
+ const statusTag = (f) => {
430
+ if (f.status === 'ascended')
431
+ return 'ASN';
432
+ if (f.status === 'ready')
433
+ return 'RD';
434
+ return 'RS';
435
+ };
436
+ // Sentiment label
437
+ const sentLabel = (s) => `${s >= 0 ? '+' : ''}${s.toFixed(2)}`;
438
+ // Per-position PnL (numeric, 2 decimal places)
439
+ const pnlValue = (valueSol, bal) => {
440
+ if (totalTokens <= 0 || netInvested <= 0)
441
+ return '0';
442
+ const estCost = netInvested * (bal / totalTokens);
443
+ const posPnl = valueSol - estCost;
444
+ return `${posPnl >= 0 ? '+' : ''}${posPnl.toFixed(2)}`;
445
+ };
446
+ // Build flat faction rows: FACTION (MCAP) STATUS MBR FNR [NB|UX]
447
+ const factionRows = [];
448
+ const seenMints = new Set();
449
+ // MBR factions first (most important to the agent)
450
+ for (const v of valued.slice(0, 3)) {
451
+ const f = factionCtx.all.find(ff => ff.mint.slice(-8) === v.id);
452
+ if (!f)
453
+ continue;
454
+ seenMints.add(f.mint);
455
+ const mcap = f.market_cap_sol ? `${f.market_cap_sol.toFixed(2)}` : '?';
456
+ const fnr = foundedSet.has(f.mint);
457
+ const sent = kit.state.sentimentMap.get(f.mint) ?? 0;
458
+ factionRows.push(`${f.mint.slice(-8)},${mcap},${statusTag(f)},true,${fnr},${Math.max(v.valueSol, 0.005).toFixed(2)},${pnlValue(v.valueSol, v.bal)},${sentLabel(sent)}`);
459
+ }
460
+ // Non-member factions
461
+ const nonMember = factionCtx.all.filter(f => !seenMints.has(f.mint) && f.status !== 'razed');
462
+ const nearby = nonMember.filter(f => nearbyMints.has(f.mint)).slice(0, 2);
463
+ const rest = nonMember.filter(f => !nearbyMints.has(f.mint));
464
+ const rising = rest.filter(f => f.status === 'rising').slice(0, 2);
465
+ const ascended = rest.filter(f => f.status === 'ascended').slice(0, 2);
466
+ const ready = rest.filter(f => f.status === 'ready').slice(0, 2);
467
+ const shown = new Set([...nearby, ...rising, ...ascended, ...ready].map(f => f.mint));
468
+ const unexplored = rest.filter(f => !shown.has(f.mint)).sort(() => Math.random() - 0.5).slice(0, 3);
469
+ for (const f of [...nearby, ...ascended, ...ready, ...rising, ...unexplored]) {
470
+ if (seenMints.has(f.mint))
471
+ continue;
472
+ seenMints.add(f.mint);
473
+ const mcap = f.market_cap_sol ? `${f.market_cap_sol.toFixed(2)}` : '?';
474
+ const sent = kit.state.sentimentMap.get(f.mint) ?? 0;
475
+ factionRows.push(`${f.mint.slice(-8)},${mcap},${statusTag(f)},false,false,0,0,${sentLabel(sent)}`);
476
+ }
477
+ // Fetch intel from table member factions only — no off-screen FIDs
478
+ let intelSnippet = '';
479
+ try {
480
+ const tableMemberMints = [...seenMints].filter(mint => heldMints.has(mint));
481
+ const lines = [];
482
+ for (const mint of tableMemberMints.slice(0, 1)) {
483
+ const f = factionCtx.all.find(ff => ff.mint === mint);
484
+ if (!f)
485
+ continue;
486
+ const intel = await (0, faction_1.fetchFactionIntel)(kit, f);
487
+ const latest = intel.recentComms.find((c) => c.sender !== agent.publicKey);
488
+ if (latest) {
489
+ lines.push(`@AP${latest.sender.slice(0, 4)} in ${mint.slice(-8)}: "${latest.memo.replace(/^<+/, '').replace(/>+\s*$/, '').slice(0, 60)}"`);
490
+ }
491
+ }
492
+ intelSnippet = lines.join('\n');
493
+ }
494
+ catch { }
495
+ // Pick example FIDs only from factions actually shown in the table
496
+ const tableFids = factionRows.map(r => r.split(',')[0]);
497
+ const m = tableFids.find(fid => heldMints.has([...seenMints].find(mint => mint.endsWith(fid)) ?? '')) ?? tableFids[0] ?? 'xxxxxxpw';
498
+ const nonMemberFids = tableFids.filter(fid => fid !== m);
499
+ const f1 = nonMemberFids.length > 0 ? (0, util_1.pick)(nonMemberFids) : m;
500
+ const f2 = nonMemberFids.length > 1 ? (0, util_1.pick)(nonMemberFids.filter(fid => fid !== f1)) : f1;
501
+ return `Welcome to Pyre, a faction warfare game. Think in English only. Think linearly: situation → decision → reason. Do not repeat yourself. Do NOT overthink, chess/strategy mood.
502
+ --- GOAL:
503
+ Maximize long-term profit and faction dominance.
504
+ --- LEGEND:
505
+ Factions are rival guilds. Higher MCAP = more power. Lifecycle: RS → RD → ASN.
506
+ HLTH: your overall profit and loss. your health.
507
+ FID: the faction identifier.
508
+ STATUS: RS (99 to 1300 SOL MCAP), RD (1300 MCAP), ASN (1300 MCAP and higher).
509
+ RS: rising. new faction. the lower the MCAP, the more you contribute to the treasury.
510
+ RD: ready, community transition stage before ascend.
511
+ ASN: ascended factions, established, more members. treasuries active.
512
+ MBR: true = you are a member. false = you are not a member.
513
+ FNR: true = you created it. false = you did not create it.
514
+ PNL: per-position profit. positive = winning, negative = losing.
515
+ SENT: sentiment score. positive = bullish, negative = bearish.
516
+ --- YOU ARE:
517
+ NAME: @AP${agent.publicKey.slice(0, 4)}
518
+ BIO: ${defaults_1.personalityDesc[agent.personality]}
519
+ HLTH: ${pnl >= 0 ? '+' : ''}${pnl.toFixed(5)} SOL
520
+ ${unrealizedPnl > 1 ? 'YOU ARE UP. consider taking profits.' : unrealizedPnl < -0.5 ? 'YOU ARE DOWN. be conservative. consider downsizing.' : 'BREAKEVEN. look for conviction plays.'}
521
+ --- INTEL:
522
+ ${intelSnippet}
523
+ --- FACTIONS:
524
+ FID,MCAP,STATUS,MBR,FNR,VALUE,PNL,SENT
525
+ ${factionRows.length > 0 ? factionRows.slice(0, 6).join('\n') : 'none'}
526
+ --- ACTIONS:
527
+ FORMAT: (action) $ OR (action) $ "*"
528
+ REPLACE $ with EXACTLY one FID from FACTIONS ONLY (always ends in pw).
529
+ REPLACE * with a ONE sentence RESPONSE, always in double quotes.
530
+ (+) $ - join.
531
+ (-) $ - leave or reduce.
532
+ (&) $ - increase position.
533
+ (!) $ "*" - talk in comms.
534
+ (#) $ "*" - fud or trash talk.
535
+ (^) $ - ascend. unlock treasury.
536
+ (~) $ - harvest fees.
537
+ (%) "..." - create new faction. "..." = creative name, in quotes.
538
+ (_) - skip turn.
539
+ --- RULES:
540
+ (+) and (&) increase MCAP. (-) decreases MCAP.
541
+ (!) increases SENT. (#) decreases SENT.
542
+ (!) any FACTIONS.
543
+ (^) FACTIONS where STATUS=RD.
544
+ (~) FACTIONS where STATUS=ASN.
545
+ (+) FACTIONS where MBR=false.
546
+ (-), (&) or (#) FACTIONS where MBR=true.
547
+ --- STRATEGIES:
548
+ - your personality is your tone.
549
+ - no FACTIONS? (%) to create one.
550
+ - learn about FACTIONS and other agents in INTEL. HLTH is performance. PNL and SENT are per-faction direction. use all three to decide.
551
+ - limit FACTIONS where MBR=true to AT MOST 3.${memberOf.length > 1 ? ` MBR=true on ${memberOf.length} FACTIONS — consider (-) from underperformers.` : ''}
552
+ - FACTIONS where FNR=true and MBR=false, consider (+). promote it with (!).
553
+ - FACTIONS where STATUS=RS may have higher reward if you (+) the right one.
554
+ - in FACTIONS where MBR=true, if MCAP increases, your PNL will increase.
555
+ - (&) and (!) to push FACTIONS where MBR=true and STATUS=RS to STATUS=ASN.
556
+ - consider (-) FACTIONS where MBR=true and PNL is positive to lock in profits.
557
+ - consider (-) FACTIONS where MBR=true and PNL is negative unless FNR=true or SENT is positive.
558
+ - when HLTH is negative, consider (_) or (-) weakest FACTIONS where MBR=true. (+) or (&) ONLY if you see opportunity.
559
+ - (_) if holding is the optimal move.
560
+ ---
561
+ one move per turn. output EXACTLY one line.
562
+ example format: ${(0, util_1.pick)([
563
+ `(+) ${f1}`,
564
+ `(&) ${m}`,
565
+ `(-) ${m}`,
566
+ `(!) ${m} "${(0, util_1.pick)(['love the energy. any strategies?', 'not leaving.'])}"`,
567
+ `(#) ${m} "${(0, util_1.pick)(['dead faction.', 'overvalued.'])}"`,
568
+ `(!) ${m} "${(0, util_1.pick)(['who else is here?', 'just getting started.'])}"`,
569
+ `(#) ${m} "${(0, util_1.pick)(['founders went quiet.', 'this faction is underperforming.'])}"`,
570
+ ])}
571
+ >`;
572
+ };
573
+ exports.buildMinimumPrompt = buildMinimumPrompt;
404
574
  /**
405
575
  * Resolve a symbol to a faction, disambiguating duplicates using agent context.
406
576
  */
@@ -665,18 +835,20 @@ function parseLLMMatch(match, factions, kit, agent, holdings, line, solRange) {
665
835
  }
666
836
  async function llmDecide(kit, agent, factions, recentMessages, llm, log, solRange, options) {
667
837
  const compact = options?.compact ?? false;
838
+ const min = options?.min ?? false;
668
839
  // Fetch holdings fresh from chain
669
840
  const holdings = await kit.state.getHoldings();
670
841
  // Fetch faction context: rising, ascended, nearby (parallel)
671
842
  // Compact mode: minimal fetches to keep context small for smol models
672
- const risingLimit = compact ? 3 : 5;
673
- const ascendedLimit = compact ? 3 : 5;
843
+ const small = compact || min;
844
+ const risingLimit = small ? 3 : 5;
845
+ const ascendedLimit = small ? 3 : 5;
674
846
  const [risingAll, ascendedAll, nearbyResult] = await Promise.all([
675
847
  kit.intel.getRisingFactions().catch(() => ({ factions: [] })),
676
848
  kit.intel.getAscendedFactions().catch(() => ({ factions: [] })),
677
- compact
849
+ small
678
850
  ? Promise.resolve({ factions: [], allies: [] })
679
- : kit.intel.getNearbyFactions(agent.publicKey, { depth: 2, limit: compact ? 7 : 15 }).catch(() => ({
851
+ : kit.intel.getNearbyFactions(agent.publicKey, { depth: 2, limit: 15 }).catch(() => ({
680
852
  factions: [],
681
853
  allies: [],
682
854
  })),
@@ -710,8 +882,8 @@ async function llmDecide(kit, agent, factions, recentMessages, llm, log, solRang
710
882
  nearby: nearbyResult.factions,
711
883
  all: allFactions,
712
884
  };
713
- const buildPrompt = compact ? exports.buildCompactModelPrompt : exports.buildAgentPrompt;
714
- const prompt = await buildPrompt(kit, agent, factionCtx, recentMessages, solRange, holdings);
885
+ const buildPrompt = min ? exports.buildMinimumPrompt : compact ? exports.buildCompactModelPrompt : exports.buildAgentPrompt;
886
+ const prompt = await buildPrompt(kit, agent, factionCtx, solRange, holdings);
715
887
  // Surface the faction table to the caller if requested
716
888
  if (options?.onPromptTable) {
717
889
  const tableStart = prompt.indexOf('--- FACTIONS:');
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "pyre-agent-kit",
3
- "version": "4.4.0",
3
+ "version": "4.4.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",