pyre-agent-kit 1.0.0 → 2.0.0
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/action.d.ts +7 -1
- package/dist/action.js +15 -2
- package/dist/agent.d.ts +2 -2
- package/dist/agent.js +11 -5
- package/dist/chain.d.ts +104 -0
- package/dist/chain.js +614 -0
- package/dist/defaults.js +1 -1
- package/dist/executor.js +3 -0
- package/dist/index.d.ts +2 -1
- package/dist/index.js +92 -27
- package/dist/types.d.ts +37 -1
- package/package.json +1 -1
package/dist/action.d.ts
CHANGED
|
@@ -1,3 +1,9 @@
|
|
|
1
1
|
import { Action, AgentState, FactionInfo, Personality } from './types';
|
|
2
|
-
|
|
2
|
+
/**
|
|
3
|
+
* Choose an action using weighted random selection.
|
|
4
|
+
*
|
|
5
|
+
* Accepts optional dynamic weights (from on-chain history).
|
|
6
|
+
* Falls back to static personality weights if not provided.
|
|
7
|
+
*/
|
|
8
|
+
export declare const chooseAction: (personality: Personality, agent: AgentState, canRally: boolean, knownFactions: FactionInfo[], dynamicWeights?: number[]) => Action;
|
|
3
9
|
export declare const sentimentBuySize: (agent: AgentState, factionMint: string) => number;
|
package/dist/action.js
CHANGED
|
@@ -7,8 +7,14 @@ const ALL_ACTIONS = [
|
|
|
7
7
|
'stronghold', 'war_loan', 'repay_loan', 'siege', 'ascend', 'raze', 'tithe',
|
|
8
8
|
'infiltrate', 'fud',
|
|
9
9
|
];
|
|
10
|
-
|
|
11
|
-
|
|
10
|
+
/**
|
|
11
|
+
* Choose an action using weighted random selection.
|
|
12
|
+
*
|
|
13
|
+
* Accepts optional dynamic weights (from on-chain history).
|
|
14
|
+
* Falls back to static personality weights if not provided.
|
|
15
|
+
*/
|
|
16
|
+
const chooseAction = (personality, agent, canRally, knownFactions, dynamicWeights) => {
|
|
17
|
+
const weights = dynamicWeights ? [...dynamicWeights] : [...defaults_1.PERSONALITY_WEIGHTS[personality]];
|
|
12
18
|
const hasHoldings = agent.holdings.size > 0;
|
|
13
19
|
const heldMints = [...agent.holdings.keys()];
|
|
14
20
|
const rivalFactions = knownFactions.filter(f => !heldMints.includes(f.mint));
|
|
@@ -59,6 +65,13 @@ const chooseAction = (personality, agent, canRally, knownFactions) => {
|
|
|
59
65
|
if (agent.infiltrated.size > 0) {
|
|
60
66
|
weights[1] += 0.10;
|
|
61
67
|
}
|
|
68
|
+
// Bearish sentiment on held factions → boost defect
|
|
69
|
+
if (hasHoldings) {
|
|
70
|
+
const bearishHeld = heldMints.filter(m => (agent.sentiment.get(m) ?? 0) < -2);
|
|
71
|
+
if (bearishHeld.length > 0) {
|
|
72
|
+
weights[1] += 0.05 * bearishHeld.length;
|
|
73
|
+
}
|
|
74
|
+
}
|
|
62
75
|
if (ascendedFactions.length > 0) {
|
|
63
76
|
if (holdsAscended) {
|
|
64
77
|
weights[6] += 0.15;
|
package/dist/agent.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
1
|
import { AgentState, FactionInfo, LLMAdapter, LLMDecision } from './types';
|
|
2
2
|
import { Connection } from '@solana/web3.js';
|
|
3
|
-
export declare const buildAgentPrompt: (agent: AgentState, factions: FactionInfo[], leaderboardSnippet: string, intelSnippet: string, recentMessages: string[], solRange?: [number, number]) => string;
|
|
4
|
-
export declare function llmDecide(agent: AgentState, factions: FactionInfo[], connection: Connection, recentMessages: string[], llm: LLMAdapter, log: (msg: string) => void, solRange?: [number, number]): Promise<LLMDecision | null>;
|
|
3
|
+
export declare const buildAgentPrompt: (agent: AgentState, factions: FactionInfo[], leaderboardSnippet: string, intelSnippet: string, recentMessages: string[], solRange?: [number, number], chainMemories?: string[]) => string;
|
|
4
|
+
export declare function llmDecide(agent: AgentState, factions: FactionInfo[], connection: Connection, recentMessages: string[], llm: LLMAdapter, log: (msg: string) => void, solRange?: [number, number], chainMemories?: string[]): Promise<LLMDecision | null>;
|
package/dist/agent.js
CHANGED
|
@@ -6,7 +6,7 @@ const pyre_world_kit_1 = require("pyre-world-kit");
|
|
|
6
6
|
const defaults_1 = require("./defaults");
|
|
7
7
|
const util_1 = require("./util");
|
|
8
8
|
const faction_1 = require("./faction");
|
|
9
|
-
const buildAgentPrompt = (agent, factions, leaderboardSnippet, intelSnippet, recentMessages, solRange) => {
|
|
9
|
+
const buildAgentPrompt = (agent, factions, leaderboardSnippet, intelSnippet, recentMessages, solRange, chainMemories) => {
|
|
10
10
|
const [minSol, maxSol] = solRange ?? defaults_1.PERSONALITY_SOL[agent.personality];
|
|
11
11
|
const holdingsList = [...agent.holdings.entries()]
|
|
12
12
|
.map(([mint, bal]) => {
|
|
@@ -29,6 +29,10 @@ const buildAgentPrompt = (agent, factions, leaderboardSnippet, intelSnippet, rec
|
|
|
29
29
|
const doNotRepeat = recentMessages.length > 0
|
|
30
30
|
? `\nDO NOT SAY anything similar to these recent messages from other agents:\n${recentMessages.map(m => `- "${m}"`).join('\n')}\n`
|
|
31
31
|
: '';
|
|
32
|
+
// On-chain memory — agent's own past memos as persistent context
|
|
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`
|
|
35
|
+
: '';
|
|
32
36
|
const voiceNudge = (0, util_1.pick)(defaults_1.VOICE_NUDGES);
|
|
33
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.
|
|
34
38
|
|
|
@@ -75,7 +79,7 @@ Recent: ${history}
|
|
|
75
79
|
Active factions: ${factionList}
|
|
76
80
|
Leaderboard preview: ${leaderboardSnippet}
|
|
77
81
|
Intel preview: ${intelSnippet}
|
|
78
|
-
${doNotRepeat}
|
|
82
|
+
${memoryBlock}${doNotRepeat}
|
|
79
83
|
|
|
80
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.
|
|
81
85
|
|
|
@@ -88,7 +92,9 @@ function parseLLMDecision(raw, factions, agent, solRange) {
|
|
|
88
92
|
return null;
|
|
89
93
|
for (const candidate of lines) {
|
|
90
94
|
const line = candidate.trim();
|
|
91
|
-
const cleaned = line
|
|
95
|
+
const cleaned = line
|
|
96
|
+
.replace(/\*+/g, '') // strip all bold/italic markdown (e.g. **DEFECT SBP "msg"**)
|
|
97
|
+
.replace(/^[-•>#\d.)\s]+/, '').replace(/^(?:WARNING|NOTE|RESPONSE|OUTPUT|ANSWER|RESULT|SCPRT|SCRIPT)\s*:?\s*/i, '').replace(/^ACTION\s+/i, '')
|
|
92
98
|
// Normalize Cyrillic lookalikes to Latin
|
|
93
99
|
.replace(/[АаА]/g, 'A').replace(/[Вв]/g, 'B').replace(/[Сс]/g, 'C').replace(/[Ее]/g, 'E')
|
|
94
100
|
.replace(/[Нн]/g, 'H').replace(/[Кк]/g, 'K').replace(/[Мм]/g, 'M').replace(/[Оо]/g, 'O')
|
|
@@ -198,7 +204,7 @@ function parseLLMMatch(match, factions, agent, line, solRange) {
|
|
|
198
204
|
reasoning: line,
|
|
199
205
|
};
|
|
200
206
|
}
|
|
201
|
-
async function llmDecide(agent, factions, connection, recentMessages, llm, log, solRange) {
|
|
207
|
+
async function llmDecide(agent, factions, connection, recentMessages, llm, log, solRange, chainMemories) {
|
|
202
208
|
let leaderboardSnippet = '';
|
|
203
209
|
try {
|
|
204
210
|
const lb = await (0, pyre_world_kit_1.getFactionLeaderboard)(connection, { limit: 5 });
|
|
@@ -262,7 +268,7 @@ async function llmDecide(agent, factions, connection, recentMessages, llm, log,
|
|
|
262
268
|
catch {
|
|
263
269
|
// intel fetch failed, proceed without it
|
|
264
270
|
}
|
|
265
|
-
const prompt = (0, exports.buildAgentPrompt)(agent, factions, leaderboardSnippet, intelSnippet, recentMessages, solRange);
|
|
271
|
+
const prompt = (0, exports.buildAgentPrompt)(agent, factions, leaderboardSnippet, intelSnippet, recentMessages, solRange, chainMemories);
|
|
266
272
|
const raw = await llm.generate(prompt);
|
|
267
273
|
if (!raw) {
|
|
268
274
|
log(`[${agent.publicKey.slice(0, 8)}] LLM returned null`);
|
package/dist/chain.d.ts
ADDED
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* On-Chain State Reconstruction
|
|
3
|
+
*
|
|
4
|
+
* Derives agent personality, sentiment, allies/rivals, and memory
|
|
5
|
+
* entirely from on-chain transaction history. The blockchain IS the
|
|
6
|
+
* agent's state — this module reads it back.
|
|
7
|
+
*
|
|
8
|
+
* Flow:
|
|
9
|
+
* 1. Fetch all tx signatures for agent wallet
|
|
10
|
+
* 2. Batch-parse transactions to extract action types + memos
|
|
11
|
+
* 3. Compute personality weights from action frequency distribution
|
|
12
|
+
* 4. Derive sentiment from buy/sell patterns + memo analysis
|
|
13
|
+
* 5. Derive allies/rivals from shared faction interactions
|
|
14
|
+
* 6. Extract agent's own memos as persistent memory
|
|
15
|
+
*/
|
|
16
|
+
import { Connection } from '@solana/web3.js';
|
|
17
|
+
import type { Action, Personality, FactionInfo, OnChainAction, ChainDerivedState } from './types';
|
|
18
|
+
/**
|
|
19
|
+
* Fetch all Torch Market transactions for an agent wallet.
|
|
20
|
+
* Paginates through signature history and batch-fetches parsed txs.
|
|
21
|
+
*/
|
|
22
|
+
export declare function fetchAgentHistory(connection: Connection, agentPubkey: string, knownMints: Set<string>, opts?: {
|
|
23
|
+
maxSignatures?: number;
|
|
24
|
+
}): Promise<OnChainAction[]>;
|
|
25
|
+
/**
|
|
26
|
+
* Compute personality weights from on-chain action history.
|
|
27
|
+
*
|
|
28
|
+
* Uses exponential decay blending:
|
|
29
|
+
* finalWeight = seed × decay^n + observed × (1 - decay^n)
|
|
30
|
+
*
|
|
31
|
+
* Where n = total action count. This means:
|
|
32
|
+
* n=0 → 100% seed weights (new agent)
|
|
33
|
+
* n=10 → ~80% observed, ~20% seed
|
|
34
|
+
* n=50 → ~99.9% observed
|
|
35
|
+
*
|
|
36
|
+
* The agent quickly becomes defined by its actual behavior.
|
|
37
|
+
*/
|
|
38
|
+
export declare function computeWeightsFromHistory(history: OnChainAction[], seedPersonality: Personality, decay?: number): number[];
|
|
39
|
+
/**
|
|
40
|
+
* Recompute weights from raw action counts (no history parsing needed).
|
|
41
|
+
* Used for live personality evolution during runtime.
|
|
42
|
+
*/
|
|
43
|
+
export declare function weightsFromCounts(counts: number[], seedPersonality: Personality, decay?: number): number[];
|
|
44
|
+
/** Action type to index in the ALL_ACTIONS array */
|
|
45
|
+
export declare function actionIndex(action: Action): number;
|
|
46
|
+
export declare function classifyPersonality(weights: number[], memos?: string[], perFactionHistory?: Map<string, number[]>): Personality;
|
|
47
|
+
/**
|
|
48
|
+
* Compute sentiment towards factions from on-chain interaction patterns.
|
|
49
|
+
*
|
|
50
|
+
* Sentiment signals:
|
|
51
|
+
* - Buys (join/dex_buy/message) → +1 per tx
|
|
52
|
+
* - Sells (defect/dex_sell/fud) → -2 per tx (sells signal stronger negative)
|
|
53
|
+
* - Rally → +3 (strong conviction signal)
|
|
54
|
+
* - Infiltrate → -5 (explicitly hostile)
|
|
55
|
+
* - Memo keywords → ±1
|
|
56
|
+
*
|
|
57
|
+
* Clamped to [-10, +10].
|
|
58
|
+
*/
|
|
59
|
+
export declare function computeSentimentFromHistory(history: OnChainAction[], factions: FactionInfo[]): Map<string, number>;
|
|
60
|
+
/**
|
|
61
|
+
* Derive allies and rivals from on-chain interactions.
|
|
62
|
+
*
|
|
63
|
+
* Ally signals:
|
|
64
|
+
* - Other agents who bought into the same factions the agent holds
|
|
65
|
+
* - Other agents whose memos in shared factions are positive
|
|
66
|
+
*
|
|
67
|
+
* Rival signals:
|
|
68
|
+
* - Agents who sold from factions the agent holds
|
|
69
|
+
* - Agents whose memos in shared factions are negative
|
|
70
|
+
* - Agents present in factions the agent infiltrated
|
|
71
|
+
*/
|
|
72
|
+
export declare function deriveAlliesRivals(agentHistory: OnChainAction[], factionComms: Map<string, {
|
|
73
|
+
sender: string;
|
|
74
|
+
memo: string;
|
|
75
|
+
}[]>, heldMints: Set<string>): {
|
|
76
|
+
allies: Set<string>;
|
|
77
|
+
rivals: Set<string>;
|
|
78
|
+
};
|
|
79
|
+
/**
|
|
80
|
+
* Derive SOL spending range from actual transaction history.
|
|
81
|
+
* Falls back to personality defaults if no buy history exists.
|
|
82
|
+
*/
|
|
83
|
+
export declare function deriveSolRange(_history: OnChainAction[], personality: Personality): [number, number];
|
|
84
|
+
/**
|
|
85
|
+
* Extract the agent's own memos as persistent memory.
|
|
86
|
+
* These are messages the agent wrote on-chain — its thoughts,
|
|
87
|
+
* trash talk, and strategic communications.
|
|
88
|
+
*/
|
|
89
|
+
export declare function extractMemories(history: OnChainAction[]): string[];
|
|
90
|
+
/**
|
|
91
|
+
* Reconstruct full agent state from on-chain history.
|
|
92
|
+
*
|
|
93
|
+
* This is the main entry point. Call on startup to derive
|
|
94
|
+
* personality, weights, sentiment, allies/rivals, and memory
|
|
95
|
+
* entirely from the blockchain. No JSON files needed.
|
|
96
|
+
*/
|
|
97
|
+
export declare function reconstructFromChain(connection: Connection, agentPubkey: string, factions: FactionInfo[], seedPersonality: Personality, opts?: {
|
|
98
|
+
maxSignatures?: number;
|
|
99
|
+
decay?: number;
|
|
100
|
+
factionComms?: Map<string, {
|
|
101
|
+
sender: string;
|
|
102
|
+
memo: string;
|
|
103
|
+
}[]>;
|
|
104
|
+
}): Promise<ChainDerivedState>;
|
package/dist/chain.js
ADDED
|
@@ -0,0 +1,614 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* On-Chain State Reconstruction
|
|
4
|
+
*
|
|
5
|
+
* Derives agent personality, sentiment, allies/rivals, and memory
|
|
6
|
+
* entirely from on-chain transaction history. The blockchain IS the
|
|
7
|
+
* agent's state — this module reads it back.
|
|
8
|
+
*
|
|
9
|
+
* Flow:
|
|
10
|
+
* 1. Fetch all tx signatures for agent wallet
|
|
11
|
+
* 2. Batch-parse transactions to extract action types + memos
|
|
12
|
+
* 3. Compute personality weights from action frequency distribution
|
|
13
|
+
* 4. Derive sentiment from buy/sell patterns + memo analysis
|
|
14
|
+
* 5. Derive allies/rivals from shared faction interactions
|
|
15
|
+
* 6. Extract agent's own memos as persistent memory
|
|
16
|
+
*/
|
|
17
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
18
|
+
exports.fetchAgentHistory = fetchAgentHistory;
|
|
19
|
+
exports.computeWeightsFromHistory = computeWeightsFromHistory;
|
|
20
|
+
exports.weightsFromCounts = weightsFromCounts;
|
|
21
|
+
exports.actionIndex = actionIndex;
|
|
22
|
+
exports.classifyPersonality = classifyPersonality;
|
|
23
|
+
exports.computeSentimentFromHistory = computeSentimentFromHistory;
|
|
24
|
+
exports.deriveAlliesRivals = deriveAlliesRivals;
|
|
25
|
+
exports.deriveSolRange = deriveSolRange;
|
|
26
|
+
exports.extractMemories = extractMemories;
|
|
27
|
+
exports.reconstructFromChain = reconstructFromChain;
|
|
28
|
+
const web3_js_1 = require("@solana/web3.js");
|
|
29
|
+
const defaults_1 = require("./defaults");
|
|
30
|
+
// Torch Market program ID
|
|
31
|
+
const TORCH_PROGRAM_ID = '8hbUkonssSEEtkqzwM7ZcZrD9evacM92TcWSooVF4BeT';
|
|
32
|
+
const MEMO_PROGRAM_IDS = new Set([
|
|
33
|
+
'MemoSq4gqABAXKb96qnH8TysNcWxMyWCqXgDLGmfcHr',
|
|
34
|
+
]);
|
|
35
|
+
const ALL_ACTIONS = [
|
|
36
|
+
'join', 'defect', 'rally', 'launch', 'message',
|
|
37
|
+
'stronghold', 'war_loan', 'repay_loan', 'siege', 'ascend', 'raze', 'tithe',
|
|
38
|
+
'infiltrate', 'fud',
|
|
39
|
+
];
|
|
40
|
+
// ─── Transaction Fetching ────────────────────────────────────────
|
|
41
|
+
/**
|
|
42
|
+
* Fetch all Torch Market transactions for an agent wallet.
|
|
43
|
+
* Paginates through signature history and batch-fetches parsed txs.
|
|
44
|
+
*/
|
|
45
|
+
async function fetchAgentHistory(connection, agentPubkey, knownMints, opts) {
|
|
46
|
+
const pubkey = new web3_js_1.PublicKey(agentPubkey);
|
|
47
|
+
const maxSigs = opts?.maxSignatures ?? 500;
|
|
48
|
+
const allActions = [];
|
|
49
|
+
// Paginate through signature history
|
|
50
|
+
let before;
|
|
51
|
+
let fetched = 0;
|
|
52
|
+
while (fetched < maxSigs) {
|
|
53
|
+
const batchSize = Math.min(maxSigs - fetched, 1000);
|
|
54
|
+
const signatures = await connection.getSignaturesForAddress(pubkey, { limit: batchSize, before }, 'confirmed');
|
|
55
|
+
if (signatures.length === 0)
|
|
56
|
+
break;
|
|
57
|
+
fetched += signatures.length;
|
|
58
|
+
before = signatures[signatures.length - 1].signature;
|
|
59
|
+
// Batch fetch parsed transactions (100 per RPC call)
|
|
60
|
+
const BATCH_SIZE = 100;
|
|
61
|
+
for (let i = 0; i < signatures.length; i += BATCH_SIZE) {
|
|
62
|
+
const batch = signatures.slice(i, i + BATCH_SIZE);
|
|
63
|
+
const sigStrings = batch.map(s => s.signature);
|
|
64
|
+
let txs;
|
|
65
|
+
try {
|
|
66
|
+
txs = await connection.getParsedTransactions(sigStrings, {
|
|
67
|
+
maxSupportedTransactionVersion: 0,
|
|
68
|
+
});
|
|
69
|
+
}
|
|
70
|
+
catch {
|
|
71
|
+
continue;
|
|
72
|
+
}
|
|
73
|
+
for (let j = 0; j < txs.length; j++) {
|
|
74
|
+
const tx = txs[j];
|
|
75
|
+
if (!tx?.meta || tx.meta.err)
|
|
76
|
+
continue;
|
|
77
|
+
const parsed = parseTransaction(tx, batch[j].signature, batch[j].blockTime ?? 0, agentPubkey, knownMints);
|
|
78
|
+
if (parsed)
|
|
79
|
+
allActions.push(parsed);
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
// Sort chronologically (oldest first)
|
|
84
|
+
allActions.sort((a, b) => a.timestamp - b.timestamp);
|
|
85
|
+
return allActions;
|
|
86
|
+
}
|
|
87
|
+
// ─── Transaction Parsing ─────────────────────────────────────────
|
|
88
|
+
/**
|
|
89
|
+
* Parse a single transaction into a game action.
|
|
90
|
+
* Uses program logs to determine instruction type, extracts memo and mint.
|
|
91
|
+
*/
|
|
92
|
+
function parseTransaction(tx, signature, timestamp, agentPubkey, knownMints) {
|
|
93
|
+
const logs = tx.meta?.logMessages ?? [];
|
|
94
|
+
const accountKeys = tx.transaction.message.accountKeys.map(k => k.pubkey.toString());
|
|
95
|
+
// Check if this tx involves the Torch program
|
|
96
|
+
const isTorchTx = accountKeys.includes(TORCH_PROGRAM_ID) ||
|
|
97
|
+
logs.some(l => l.includes(TORCH_PROGRAM_ID));
|
|
98
|
+
if (!isTorchTx)
|
|
99
|
+
return null;
|
|
100
|
+
// Determine action type from logs
|
|
101
|
+
let action = categorizeFromLogs(logs);
|
|
102
|
+
// Find faction mint from account keys
|
|
103
|
+
const mint = findMintInAccounts(accountKeys, knownMints);
|
|
104
|
+
// Extract memo
|
|
105
|
+
const memo = extractMemo(tx);
|
|
106
|
+
// Buy + memo = message (micro buy with text), sell + memo = fud (micro sell with text)
|
|
107
|
+
if (memo?.trim()) {
|
|
108
|
+
if (action === 'join' || action === 'dex_buy')
|
|
109
|
+
action = 'message';
|
|
110
|
+
else if (action === 'defect' || action === 'dex_sell')
|
|
111
|
+
action = 'fud';
|
|
112
|
+
}
|
|
113
|
+
// Find other agents (signers that aren't this agent)
|
|
114
|
+
const otherAgents = tx.transaction.message.accountKeys
|
|
115
|
+
.filter(k => k.signer && k.pubkey.toString() !== agentPubkey)
|
|
116
|
+
.map(k => k.pubkey.toString());
|
|
117
|
+
return { signature, timestamp, action, mint, memo, otherAgents };
|
|
118
|
+
}
|
|
119
|
+
/**
|
|
120
|
+
* Categorize transaction type from Anchor program logs.
|
|
121
|
+
*/
|
|
122
|
+
function categorizeFromLogs(logs) {
|
|
123
|
+
const logStr = logs.join(' ');
|
|
124
|
+
// Order matters — check specific instructions before generic ones
|
|
125
|
+
if (logs.some(l => l.includes('Instruction: CreateToken') || l.includes('create_token')))
|
|
126
|
+
return 'launch';
|
|
127
|
+
if (logs.some(l => l.includes('Instruction: CreateVault') || l.includes('create_vault')))
|
|
128
|
+
return 'stronghold';
|
|
129
|
+
// VaultSwap can be buy or sell — check for direction hints in logs
|
|
130
|
+
if (logs.some(l => l.includes('Instruction: VaultSwap') || l.includes('vault_swap'))) {
|
|
131
|
+
// VaultSwap logs typically include buy/sell direction
|
|
132
|
+
if (logStr.includes('is_buy: true') || logStr.includes('Buy'))
|
|
133
|
+
return 'dex_buy';
|
|
134
|
+
if (logStr.includes('is_buy: false') || logStr.includes('Sell'))
|
|
135
|
+
return 'dex_sell';
|
|
136
|
+
return 'dex_buy'; // default to buy if unclear
|
|
137
|
+
}
|
|
138
|
+
if (logs.some(l => l.includes('Instruction: Buy') || l.includes('Program log: Buy')))
|
|
139
|
+
return 'join';
|
|
140
|
+
if (logs.some(l => l.includes('Instruction: Sell') || l.includes('Program log: Sell')))
|
|
141
|
+
return 'defect';
|
|
142
|
+
if (logs.some(l => l.includes('Instruction: Star') || l.includes('star')))
|
|
143
|
+
return 'rally';
|
|
144
|
+
if (logs.some(l => l.includes('Instruction: Borrow') || l.includes('borrow')))
|
|
145
|
+
return 'war_loan';
|
|
146
|
+
if (logs.some(l => l.includes('Instruction: Repay') || l.includes('repay')))
|
|
147
|
+
return 'repay_loan';
|
|
148
|
+
if (logs.some(l => l.includes('Instruction: Liquidate') || l.includes('liquidate')))
|
|
149
|
+
return 'siege';
|
|
150
|
+
if (logs.some(l => l.includes('Instruction: Migrate') || l.includes('migrate')))
|
|
151
|
+
return 'ascend';
|
|
152
|
+
if (logs.some(l => l.includes('Instruction: ReclaimFailedToken') || l.includes('reclaim')))
|
|
153
|
+
return 'raze';
|
|
154
|
+
if (logs.some(l => l.includes('Instruction: HarvestFees') || l.includes('SwapFeesToSol') || l.includes('harvest')))
|
|
155
|
+
return 'tithe';
|
|
156
|
+
if (logs.some(l => l.includes('Instruction: DepositVault') || l.includes('deposit_vault')))
|
|
157
|
+
return 'fund';
|
|
158
|
+
return 'unknown';
|
|
159
|
+
}
|
|
160
|
+
/**
|
|
161
|
+
* Find a known faction mint in transaction account keys.
|
|
162
|
+
*/
|
|
163
|
+
function findMintInAccounts(accountKeys, knownMints) {
|
|
164
|
+
for (const key of accountKeys) {
|
|
165
|
+
if (knownMints.has(key))
|
|
166
|
+
return key;
|
|
167
|
+
}
|
|
168
|
+
return undefined;
|
|
169
|
+
}
|
|
170
|
+
/**
|
|
171
|
+
* Extract SPL Memo text from a transaction.
|
|
172
|
+
*/
|
|
173
|
+
function extractMemo(tx) {
|
|
174
|
+
for (const ix of tx.transaction.message.instructions) {
|
|
175
|
+
const programId = 'programId' in ix ? ix.programId.toString() : '';
|
|
176
|
+
const programName = 'program' in ix ? ix.program : '';
|
|
177
|
+
if (MEMO_PROGRAM_IDS.has(programId) || programName === 'spl-memo') {
|
|
178
|
+
if ('parsed' in ix) {
|
|
179
|
+
const text = typeof ix.parsed === 'string' ? ix.parsed : JSON.stringify(ix.parsed);
|
|
180
|
+
if (text?.trim())
|
|
181
|
+
return text.trim();
|
|
182
|
+
}
|
|
183
|
+
}
|
|
184
|
+
}
|
|
185
|
+
// Also check inner instructions
|
|
186
|
+
for (const inner of tx.meta?.innerInstructions ?? []) {
|
|
187
|
+
for (const ix of inner.instructions) {
|
|
188
|
+
const programId = 'programId' in ix ? ix.programId.toString() : '';
|
|
189
|
+
const programName = 'program' in ix ? ix.program : '';
|
|
190
|
+
if (MEMO_PROGRAM_IDS.has(programId) || programName === 'spl-memo') {
|
|
191
|
+
if ('parsed' in ix) {
|
|
192
|
+
const text = typeof ix.parsed === 'string' ? ix.parsed : JSON.stringify(ix.parsed);
|
|
193
|
+
if (text?.trim())
|
|
194
|
+
return text.trim();
|
|
195
|
+
}
|
|
196
|
+
}
|
|
197
|
+
}
|
|
198
|
+
}
|
|
199
|
+
return undefined;
|
|
200
|
+
}
|
|
201
|
+
// ─── Weight Computation ──────────────────────────────────────────
|
|
202
|
+
/**
|
|
203
|
+
* Compute personality weights from on-chain action history.
|
|
204
|
+
*
|
|
205
|
+
* Uses exponential decay blending:
|
|
206
|
+
* finalWeight = seed × decay^n + observed × (1 - decay^n)
|
|
207
|
+
*
|
|
208
|
+
* Where n = total action count. This means:
|
|
209
|
+
* n=0 → 100% seed weights (new agent)
|
|
210
|
+
* n=10 → ~80% observed, ~20% seed
|
|
211
|
+
* n=50 → ~99.9% observed
|
|
212
|
+
*
|
|
213
|
+
* The agent quickly becomes defined by its actual behavior.
|
|
214
|
+
*/
|
|
215
|
+
function computeWeightsFromHistory(history, seedPersonality, decay = 0.85) {
|
|
216
|
+
const seed = [...defaults_1.PERSONALITY_WEIGHTS[seedPersonality]];
|
|
217
|
+
if (history.length === 0)
|
|
218
|
+
return seed;
|
|
219
|
+
// Count occurrences of each action type
|
|
220
|
+
const counts = new Array(ALL_ACTIONS.length).fill(0);
|
|
221
|
+
for (const entry of history) {
|
|
222
|
+
const action = normalizeChainAction(entry.action);
|
|
223
|
+
const idx = ALL_ACTIONS.indexOf(action);
|
|
224
|
+
if (idx >= 0)
|
|
225
|
+
counts[idx]++;
|
|
226
|
+
}
|
|
227
|
+
const total = counts.reduce((a, b) => a + b, 0);
|
|
228
|
+
if (total === 0)
|
|
229
|
+
return seed;
|
|
230
|
+
// Compute observed frequency distribution
|
|
231
|
+
const observed = counts.map(c => c / total);
|
|
232
|
+
// Blend seed with observed using exponential decay
|
|
233
|
+
const n = total;
|
|
234
|
+
const seedFactor = Math.pow(decay, n);
|
|
235
|
+
const observedFactor = 1 - seedFactor;
|
|
236
|
+
return seed.map((s, i) => s * seedFactor + observed[i] * observedFactor);
|
|
237
|
+
}
|
|
238
|
+
/**
|
|
239
|
+
* Normalize chain action types to standard Action types.
|
|
240
|
+
* dex_buy → join, dex_sell → defect, etc.
|
|
241
|
+
*/
|
|
242
|
+
function normalizeChainAction(action) {
|
|
243
|
+
switch (action) {
|
|
244
|
+
case 'dex_buy': return 'join';
|
|
245
|
+
case 'dex_sell': return 'defect';
|
|
246
|
+
case 'fund': return 'stronghold';
|
|
247
|
+
case 'unknown': return 'join'; // safe fallback
|
|
248
|
+
default: return action;
|
|
249
|
+
}
|
|
250
|
+
}
|
|
251
|
+
/**
|
|
252
|
+
* Recompute weights from raw action counts (no history parsing needed).
|
|
253
|
+
* Used for live personality evolution during runtime.
|
|
254
|
+
*/
|
|
255
|
+
function weightsFromCounts(counts, seedPersonality, decay = 0.85) {
|
|
256
|
+
const seed = [...defaults_1.PERSONALITY_WEIGHTS[seedPersonality]];
|
|
257
|
+
const total = counts.reduce((a, b) => a + b, 0);
|
|
258
|
+
if (total === 0)
|
|
259
|
+
return seed;
|
|
260
|
+
const observed = counts.map(c => c / total);
|
|
261
|
+
const seedFactor = Math.pow(decay, total);
|
|
262
|
+
return seed.map((s, i) => s * seedFactor + observed[i] * (1 - seedFactor));
|
|
263
|
+
}
|
|
264
|
+
/** Action type to index in the ALL_ACTIONS array */
|
|
265
|
+
function actionIndex(action) {
|
|
266
|
+
return ALL_ACTIONS.indexOf(action);
|
|
267
|
+
}
|
|
268
|
+
// ─── Memo Content Personality Signals ─────────────────────────────
|
|
269
|
+
const PROVOCATEUR_VOICE = /trash|beef|fight|chaos|war|destroy|crush|pathetic|laughable|weak|dead|fake|scam|clown|joke|fool|coward|expose|call.?out|dare|challenge|predict|bold|hot.?take/;
|
|
270
|
+
const SCOUT_VOICE = /intel|data|notice|observ|suspicious|watch|track|report|warn|alert|question|why did|who.?s|pattern|trend|%|percent|member|holder|accumul/;
|
|
271
|
+
const LOYALIST_VOICE = /ride.?or.?die|loyal|hold|believe|strong|build|together|ally|alliance|trust|support|back|hype|power|conviction|never.?sell|diamond/;
|
|
272
|
+
const MERCENARY_VOICE = /profit|alpha|flip|exit|dump|cash|roi|returns|opportunity|play|angle|trade|stack|bag|gain|solo|lone.?wolf/;
|
|
273
|
+
const WHALE_VOICE = /flex|position|size|deploy|capital|market|move|massive|big|dominate|everyone.?watch|listen|whale|stack|load/;
|
|
274
|
+
function scoreMemoPersonality(memos) {
|
|
275
|
+
const scores = {
|
|
276
|
+
loyalist: 0, mercenary: 0, provocateur: 0, scout: 0, whale: 0,
|
|
277
|
+
};
|
|
278
|
+
for (const memo of memos) {
|
|
279
|
+
const text = memo.toLowerCase();
|
|
280
|
+
if (PROVOCATEUR_VOICE.test(text))
|
|
281
|
+
scores.provocateur++;
|
|
282
|
+
if (SCOUT_VOICE.test(text))
|
|
283
|
+
scores.scout++;
|
|
284
|
+
if (LOYALIST_VOICE.test(text))
|
|
285
|
+
scores.loyalist++;
|
|
286
|
+
if (MERCENARY_VOICE.test(text))
|
|
287
|
+
scores.mercenary++;
|
|
288
|
+
if (WHALE_VOICE.test(text))
|
|
289
|
+
scores.whale++;
|
|
290
|
+
}
|
|
291
|
+
return scores;
|
|
292
|
+
}
|
|
293
|
+
// ─── Personality Classification ──────────────────────────────────
|
|
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.
|
|
307
|
+
*/
|
|
308
|
+
function scoreActions(r) {
|
|
309
|
+
const joinRate = r[0], defectRate = r[1], rallyRate = r[2], messageRate = r[4];
|
|
310
|
+
const warLoanRate = r[6], siegeRate = r[8], titheRate = r[11];
|
|
311
|
+
const infiltrateRate = r[12], fudRate = r[13];
|
|
312
|
+
const commsRate = messageRate + fudRate;
|
|
313
|
+
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
|
|
316
|
+
const msgRatio = commsRate > 0 ? messageRate / commsRate : 0;
|
|
317
|
+
return {
|
|
318
|
+
// Loyalist: high message ratio, joins, rallies — positive vibes
|
|
319
|
+
loyalist: msgRatio * 3 + joinRate * 2 + rallyRate * 3 + titheRate * 2
|
|
320
|
+
- fudRatio * 4 - defectRate * 3,
|
|
321
|
+
// Mercenary: defect + fud cycle, infiltration pattern
|
|
322
|
+
mercenary: defectRate * 4 + fudRatio * 2
|
|
323
|
+
+ (defectRate > 0 && fudRate > 0 ? 3 : 0)
|
|
324
|
+
+ infiltrateRate * 3 + warLoanRate * 2 + siegeRate * 2
|
|
325
|
+
- msgRatio * 2 - rallyRate * 2,
|
|
326
|
+
// Provocateur: high fud ratio — more fud than message
|
|
327
|
+
provocateur: fudRatio * 4 + infiltrateRate * 2
|
|
328
|
+
- msgRatio * 2 - rallyRate * 2,
|
|
329
|
+
// Scout: high message ratio, low fud — observes, doesn't attack
|
|
330
|
+
scout: msgRatio * 3 + rallyRate - fudRatio * 3 - defectRate,
|
|
331
|
+
// Whale: trades a lot but talks very little
|
|
332
|
+
whale: (tradeRate > commsRate ? 1 : 0) * 2 + warLoanRate * 3 + defectRate * 2
|
|
333
|
+
- commsRate * 3,
|
|
334
|
+
};
|
|
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
|
+
const memoScores = scoreMemoPersonality(memos);
|
|
375
|
+
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
|
+
let best = 'loyalist';
|
|
387
|
+
let bestScore = -Infinity;
|
|
388
|
+
for (const [p, score] of Object.entries(finalScores)) {
|
|
389
|
+
if (score > bestScore) {
|
|
390
|
+
bestScore = score;
|
|
391
|
+
best = p;
|
|
392
|
+
}
|
|
393
|
+
}
|
|
394
|
+
return best;
|
|
395
|
+
}
|
|
396
|
+
// ─── Sentiment from On-Chain Data ────────────────────────────────
|
|
397
|
+
const POSITIVE_PATTERN = /strong|rally|bull|pump|rising|hold|loyal|power|growing|moon|love|trust|alpha|build|conviction/;
|
|
398
|
+
const NEGATIVE_PATTERN = /weak|dump|bear|dead|fail|raze|crash|abandon|scam|rug|sell|exit|trash|hate|fake/;
|
|
399
|
+
/**
|
|
400
|
+
* Compute sentiment towards factions from on-chain interaction patterns.
|
|
401
|
+
*
|
|
402
|
+
* Sentiment signals:
|
|
403
|
+
* - Buys (join/dex_buy/message) → +1 per tx
|
|
404
|
+
* - Sells (defect/dex_sell/fud) → -2 per tx (sells signal stronger negative)
|
|
405
|
+
* - Rally → +3 (strong conviction signal)
|
|
406
|
+
* - Infiltrate → -5 (explicitly hostile)
|
|
407
|
+
* - Memo keywords → ±1
|
|
408
|
+
*
|
|
409
|
+
* Clamped to [-10, +10].
|
|
410
|
+
*/
|
|
411
|
+
function computeSentimentFromHistory(history, factions) {
|
|
412
|
+
const sentiment = new Map();
|
|
413
|
+
for (const entry of history) {
|
|
414
|
+
if (!entry.mint)
|
|
415
|
+
continue;
|
|
416
|
+
const current = sentiment.get(entry.mint) ?? 0;
|
|
417
|
+
let delta = 0;
|
|
418
|
+
switch (entry.action) {
|
|
419
|
+
case 'join':
|
|
420
|
+
case 'dex_buy':
|
|
421
|
+
delta = 1;
|
|
422
|
+
break;
|
|
423
|
+
case 'defect':
|
|
424
|
+
case 'dex_sell':
|
|
425
|
+
delta = -2;
|
|
426
|
+
break;
|
|
427
|
+
case 'rally':
|
|
428
|
+
delta = 3;
|
|
429
|
+
break;
|
|
430
|
+
case 'infiltrate':
|
|
431
|
+
delta = -5;
|
|
432
|
+
break;
|
|
433
|
+
case 'message':
|
|
434
|
+
delta = 0.5; // slight positive (paid to speak)
|
|
435
|
+
break;
|
|
436
|
+
case 'fud':
|
|
437
|
+
delta = -1.5;
|
|
438
|
+
break;
|
|
439
|
+
case 'war_loan':
|
|
440
|
+
delta = 1; // leveraging = bullish
|
|
441
|
+
break;
|
|
442
|
+
}
|
|
443
|
+
// Memo sentiment analysis
|
|
444
|
+
if (entry.memo) {
|
|
445
|
+
const text = entry.memo.toLowerCase();
|
|
446
|
+
if (POSITIVE_PATTERN.test(text))
|
|
447
|
+
delta += 0.5;
|
|
448
|
+
if (NEGATIVE_PATTERN.test(text))
|
|
449
|
+
delta -= 0.5;
|
|
450
|
+
}
|
|
451
|
+
sentiment.set(entry.mint, Math.max(-10, Math.min(10, current + delta)));
|
|
452
|
+
}
|
|
453
|
+
return sentiment;
|
|
454
|
+
}
|
|
455
|
+
// ─── Ally/Rival Detection ────────────────────────────────────────
|
|
456
|
+
/**
|
|
457
|
+
* Derive allies and rivals from on-chain interactions.
|
|
458
|
+
*
|
|
459
|
+
* Ally signals:
|
|
460
|
+
* - Other agents who bought into the same factions the agent holds
|
|
461
|
+
* - Other agents whose memos in shared factions are positive
|
|
462
|
+
*
|
|
463
|
+
* Rival signals:
|
|
464
|
+
* - Agents who sold from factions the agent holds
|
|
465
|
+
* - Agents whose memos in shared factions are negative
|
|
466
|
+
* - Agents present in factions the agent infiltrated
|
|
467
|
+
*/
|
|
468
|
+
function deriveAlliesRivals(agentHistory, factionComms, heldMints) {
|
|
469
|
+
const allyScore = new Map();
|
|
470
|
+
const rivalScore = new Map();
|
|
471
|
+
// Score from faction comms (other agents' messages in held factions)
|
|
472
|
+
for (const [mint, comms] of factionComms) {
|
|
473
|
+
if (!heldMints.has(mint))
|
|
474
|
+
continue;
|
|
475
|
+
for (const c of comms) {
|
|
476
|
+
const text = c.memo.toLowerCase();
|
|
477
|
+
if (POSITIVE_PATTERN.test(text)) {
|
|
478
|
+
allyScore.set(c.sender, (allyScore.get(c.sender) ?? 0) + 1);
|
|
479
|
+
}
|
|
480
|
+
if (NEGATIVE_PATTERN.test(text)) {
|
|
481
|
+
rivalScore.set(c.sender, (rivalScore.get(c.sender) ?? 0) + 1);
|
|
482
|
+
}
|
|
483
|
+
}
|
|
484
|
+
}
|
|
485
|
+
// Convert scores to sets (threshold: net positive → ally, net negative → rival)
|
|
486
|
+
const allies = new Set();
|
|
487
|
+
const rivals = new Set();
|
|
488
|
+
const allAgents = new Set([...allyScore.keys(), ...rivalScore.keys()]);
|
|
489
|
+
for (const agent of allAgents) {
|
|
490
|
+
const aScore = allyScore.get(agent) ?? 0;
|
|
491
|
+
const rScore = rivalScore.get(agent) ?? 0;
|
|
492
|
+
if (aScore > rScore)
|
|
493
|
+
allies.add(agent);
|
|
494
|
+
else if (rScore > aScore)
|
|
495
|
+
rivals.add(agent);
|
|
496
|
+
}
|
|
497
|
+
return { allies, rivals };
|
|
498
|
+
}
|
|
499
|
+
// ─── SOL Range Derivation ────────────────────────────────────────
|
|
500
|
+
/**
|
|
501
|
+
* Derive SOL spending range from actual transaction history.
|
|
502
|
+
* Falls back to personality defaults if no buy history exists.
|
|
503
|
+
*/
|
|
504
|
+
function deriveSolRange(_history, personality) {
|
|
505
|
+
// For now, we keep the personality-based ranges as they represent
|
|
506
|
+
// risk tolerance. In the future, this could analyze actual tx amounts
|
|
507
|
+
// from the parsed transaction data (lamports in/out).
|
|
508
|
+
// The current torchsdk doesn't expose lamport amounts in parsed tx data,
|
|
509
|
+
// so we'd need to decode inner instructions to extract this.
|
|
510
|
+
return defaults_1.PERSONALITY_SOL[personality];
|
|
511
|
+
}
|
|
512
|
+
// ─── Memory Extraction ──────────────────────────────────────────
|
|
513
|
+
/**
|
|
514
|
+
* Extract the agent's own memos as persistent memory.
|
|
515
|
+
* These are messages the agent wrote on-chain — its thoughts,
|
|
516
|
+
* trash talk, and strategic communications.
|
|
517
|
+
*/
|
|
518
|
+
function extractMemories(history) {
|
|
519
|
+
return history
|
|
520
|
+
.filter(h => h.memo && h.memo.trim().length > 0)
|
|
521
|
+
.map(h => {
|
|
522
|
+
const faction = h.mint ? h.mint.slice(0, 8) : '???';
|
|
523
|
+
const time = new Date(h.timestamp * 1000).toISOString().slice(0, 10);
|
|
524
|
+
const action = h.action === 'join' || h.action === 'dex_buy' ? 'joined'
|
|
525
|
+
: h.action === 'defect' || h.action === 'dex_sell' ? 'defected'
|
|
526
|
+
: h.action === 'fud' ? 'fudded'
|
|
527
|
+
: h.action === 'message' ? 'said'
|
|
528
|
+
: h.action;
|
|
529
|
+
return `[${time}] ${action} ${faction}: "${h.memo}"`;
|
|
530
|
+
});
|
|
531
|
+
}
|
|
532
|
+
// ─── Full State Reconstruction ──────────────────────────────────
|
|
533
|
+
/**
|
|
534
|
+
* Reconstruct full agent state from on-chain history.
|
|
535
|
+
*
|
|
536
|
+
* This is the main entry point. Call on startup to derive
|
|
537
|
+
* personality, weights, sentiment, allies/rivals, and memory
|
|
538
|
+
* entirely from the blockchain. No JSON files needed.
|
|
539
|
+
*/
|
|
540
|
+
async function reconstructFromChain(connection, agentPubkey, factions, seedPersonality, opts) {
|
|
541
|
+
const knownMints = new Set(factions.map(f => f.mint));
|
|
542
|
+
// 1. Fetch on-chain history
|
|
543
|
+
const history = await fetchAgentHistory(connection, agentPubkey, knownMints, {
|
|
544
|
+
maxSignatures: opts?.maxSignatures,
|
|
545
|
+
});
|
|
546
|
+
// 2. Compute personality weights from action frequency
|
|
547
|
+
const weights = computeWeightsFromHistory(history, seedPersonality, opts?.decay);
|
|
548
|
+
// 3. Classify emergent personality (action ratios + memo content, per-faction)
|
|
549
|
+
const memoTexts = history.filter(h => h.memo?.trim()).map(h => h.memo);
|
|
550
|
+
// Build per-faction action counts for per-ticker personality scoring
|
|
551
|
+
const perFaction = new Map();
|
|
552
|
+
for (const entry of history) {
|
|
553
|
+
if (!entry.mint)
|
|
554
|
+
continue;
|
|
555
|
+
const normalized = normalizeChainAction(entry.action);
|
|
556
|
+
const idx = ALL_ACTIONS.indexOf(normalized);
|
|
557
|
+
if (idx < 0)
|
|
558
|
+
continue;
|
|
559
|
+
if (!perFaction.has(entry.mint))
|
|
560
|
+
perFaction.set(entry.mint, new Array(ALL_ACTIONS.length).fill(0));
|
|
561
|
+
perFaction.get(entry.mint)[idx]++;
|
|
562
|
+
}
|
|
563
|
+
const personality = history.length > 0 ? classifyPersonality(weights, memoTexts, perFaction) : seedPersonality;
|
|
564
|
+
// 4. Compute sentiment from on-chain interactions
|
|
565
|
+
const sentiment = computeSentimentFromHistory(history, factions);
|
|
566
|
+
// 5. Derive allies/rivals
|
|
567
|
+
const heldMints = new Set();
|
|
568
|
+
const mintBalances = new Map();
|
|
569
|
+
for (const entry of history) {
|
|
570
|
+
if (!entry.mint)
|
|
571
|
+
continue;
|
|
572
|
+
const current = mintBalances.get(entry.mint) ?? 0;
|
|
573
|
+
if (entry.action === 'join' || entry.action === 'dex_buy' || entry.action === 'message') {
|
|
574
|
+
mintBalances.set(entry.mint, current + 1);
|
|
575
|
+
}
|
|
576
|
+
else if (entry.action === 'defect' || entry.action === 'dex_sell') {
|
|
577
|
+
mintBalances.set(entry.mint, current - 1);
|
|
578
|
+
}
|
|
579
|
+
}
|
|
580
|
+
for (const [mint, bal] of mintBalances) {
|
|
581
|
+
if (bal > 0)
|
|
582
|
+
heldMints.add(mint);
|
|
583
|
+
}
|
|
584
|
+
const { allies, rivals } = deriveAlliesRivals(history, opts?.factionComms ?? new Map(), heldMints);
|
|
585
|
+
// 6. Derive SOL range
|
|
586
|
+
const solRange = deriveSolRange(history, personality);
|
|
587
|
+
// 7. Extract memories (agent's own memos)
|
|
588
|
+
const memories = extractMemories(history);
|
|
589
|
+
// 8. Build recent history descriptions
|
|
590
|
+
const recentHistory = history.slice(-15).map(h => {
|
|
591
|
+
const faction = h.mint
|
|
592
|
+
? factions.find(f => f.mint === h.mint)?.symbol ?? h.mint.slice(0, 8)
|
|
593
|
+
: '?';
|
|
594
|
+
const memoSuffix = h.memo ? ` — "${h.memo.slice(0, 60)}"` : '';
|
|
595
|
+
return `${h.action} ${faction}${memoSuffix}`;
|
|
596
|
+
});
|
|
597
|
+
// 9. Find founded factions
|
|
598
|
+
const founded = history
|
|
599
|
+
.filter(h => h.action === 'launch' && h.mint)
|
|
600
|
+
.map(h => h.mint);
|
|
601
|
+
return {
|
|
602
|
+
weights,
|
|
603
|
+
personality,
|
|
604
|
+
sentiment,
|
|
605
|
+
allies,
|
|
606
|
+
rivals,
|
|
607
|
+
solRange,
|
|
608
|
+
actionCount: history.length,
|
|
609
|
+
recentHistory,
|
|
610
|
+
founded,
|
|
611
|
+
memories,
|
|
612
|
+
history,
|
|
613
|
+
};
|
|
614
|
+
}
|
package/dist/defaults.js
CHANGED
|
@@ -79,7 +79,7 @@ exports.ACTION_MAP = {
|
|
|
79
79
|
'SMEAR': 'FUD', 'SLANDER': 'FUD', 'DISCREDIT': 'FUD', 'SABOTAGE': 'FUD', 'UNDERMINE': 'FUD', 'ARGUE': 'FUD', 'TRASH': 'FUD', 'CRITICIZE': 'FUD', 'MOCK': 'FUD', 'FUDS': 'FUD',
|
|
80
80
|
'ENDORSE': 'RALLY', 'PROMOTE': 'RALLY', 'BOOST': 'RALLY',
|
|
81
81
|
// Common misspellings
|
|
82
|
-
'DEFLECT': 'DEFECT', 'DEFEKT': 'DEFECT', 'DEFCT': 'DEFECT', 'DEFFECT': 'DEFECT',
|
|
82
|
+
'DEFLECT': 'DEFECT', 'DEFEKT': 'DEFECT', 'DEFCT': 'DEFECT', 'DEFFECT': 'DEFECT', 'DERECT': 'DEFECT',
|
|
83
83
|
'JION': 'JOIN', 'JOING': 'JOIN', 'JOIIN': 'JOIN',
|
|
84
84
|
'RALEY': 'RALLY', 'RALY': 'RALLY', 'RALLLY': 'RALLY',
|
|
85
85
|
'LANCH': 'LAUNCH', 'LAUCH': 'LAUNCH',
|
package/dist/executor.js
CHANGED
|
@@ -355,6 +355,9 @@ const handlers = {
|
|
|
355
355
|
stronghold: ctx.agent.publicKey, ascended: faction.status === 'ascended',
|
|
356
356
|
});
|
|
357
357
|
await (0, tx_1.sendAndConfirm)(ctx.connection, ctx.agent.keypair, result);
|
|
358
|
+
// Fudding tanks your own sentiment — you're going bearish
|
|
359
|
+
const fudSentiment = ctx.agent.sentiment.get(faction.mint) ?? 0;
|
|
360
|
+
ctx.agent.sentiment.set(faction.mint, Math.max(-10, fudSentiment - 2));
|
|
358
361
|
ctx.agent.lastAction = `fud ${faction.symbol}`;
|
|
359
362
|
return `argued in ${faction.symbol}: "${ctx.decision.message}"`;
|
|
360
363
|
},
|
package/dist/index.d.ts
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { PyreAgentConfig, PyreAgent } from './types';
|
|
2
|
-
export type { PyreAgentConfig, PyreAgent, AgentTickResult, SerializedAgentState, LLMAdapter, LLMDecision, FactionInfo, Personality, Action, AgentState, } from './types';
|
|
2
|
+
export type { PyreAgentConfig, PyreAgent, AgentTickResult, SerializedAgentState, LLMAdapter, LLMDecision, FactionInfo, Personality, Action, AgentState, OnChainAction, ChainDerivedState, } from './types';
|
|
3
3
|
export { assignPersonality, PERSONALITY_SOL, PERSONALITY_WEIGHTS, personalityDesc, VOICE_NUDGES } from './defaults';
|
|
4
4
|
export { ensureStronghold } from './stronghold';
|
|
5
5
|
export { sendAndConfirm } from './tx';
|
|
6
|
+
export { reconstructFromChain, computeWeightsFromHistory, classifyPersonality, weightsFromCounts, actionIndex } from './chain';
|
|
6
7
|
export declare function createPyreAgent(config: PyreAgentConfig): Promise<PyreAgent>;
|
package/dist/index.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.sendAndConfirm = exports.ensureStronghold = exports.VOICE_NUDGES = exports.personalityDesc = exports.PERSONALITY_WEIGHTS = exports.PERSONALITY_SOL = exports.assignPersonality = void 0;
|
|
3
|
+
exports.actionIndex = exports.weightsFromCounts = exports.classifyPersonality = exports.computeWeightsFromHistory = exports.reconstructFromChain = exports.sendAndConfirm = exports.ensureStronghold = exports.VOICE_NUDGES = exports.personalityDesc = exports.PERSONALITY_WEIGHTS = exports.PERSONALITY_SOL = exports.assignPersonality = void 0;
|
|
4
4
|
exports.createPyreAgent = createPyreAgent;
|
|
5
5
|
const pyre_world_kit_1 = require("pyre-world-kit");
|
|
6
6
|
const defaults_1 = require("./defaults");
|
|
@@ -8,6 +8,7 @@ const action_1 = require("./action");
|
|
|
8
8
|
const agent_1 = require("./agent");
|
|
9
9
|
const executor_1 = require("./executor");
|
|
10
10
|
const stronghold_1 = require("./stronghold");
|
|
11
|
+
const chain_1 = require("./chain");
|
|
11
12
|
const util_1 = require("./util");
|
|
12
13
|
var defaults_2 = require("./defaults");
|
|
13
14
|
Object.defineProperty(exports, "assignPersonality", { enumerable: true, get: function () { return defaults_2.assignPersonality; } });
|
|
@@ -19,41 +20,29 @@ var stronghold_2 = require("./stronghold");
|
|
|
19
20
|
Object.defineProperty(exports, "ensureStronghold", { enumerable: true, get: function () { return stronghold_2.ensureStronghold; } });
|
|
20
21
|
var tx_1 = require("./tx");
|
|
21
22
|
Object.defineProperty(exports, "sendAndConfirm", { enumerable: true, get: function () { return tx_1.sendAndConfirm; } });
|
|
23
|
+
var chain_2 = require("./chain");
|
|
24
|
+
Object.defineProperty(exports, "reconstructFromChain", { enumerable: true, get: function () { return chain_2.reconstructFromChain; } });
|
|
25
|
+
Object.defineProperty(exports, "computeWeightsFromHistory", { enumerable: true, get: function () { return chain_2.computeWeightsFromHistory; } });
|
|
26
|
+
Object.defineProperty(exports, "classifyPersonality", { enumerable: true, get: function () { return chain_2.classifyPersonality; } });
|
|
27
|
+
Object.defineProperty(exports, "weightsFromCounts", { enumerable: true, get: function () { return chain_2.weightsFromCounts; } });
|
|
28
|
+
Object.defineProperty(exports, "actionIndex", { enumerable: true, get: function () { return chain_2.actionIndex; } });
|
|
22
29
|
async function createPyreAgent(config) {
|
|
23
30
|
const { connection, keypair, network, llm, maxFoundedFactions = 2, strongholdFundSol, strongholdTopupThresholdSol, strongholdTopupReserveSol, } = config;
|
|
24
31
|
const publicKey = keypair.publicKey.toBase58();
|
|
25
|
-
const
|
|
26
|
-
const solRange = config.solRange ?? defaults_1.PERSONALITY_SOL[personality];
|
|
32
|
+
const seedPersonality = config.personality ?? config.state?.personality ?? (0, defaults_1.assignPersonality)();
|
|
27
33
|
const logger = config.logger ?? ((msg) => console.log(`[${(0, util_1.ts)()}] ${msg}`));
|
|
28
34
|
const strongholdOpts = {
|
|
29
35
|
fundSol: strongholdFundSol,
|
|
30
36
|
topupThresholdSol: strongholdTopupThresholdSol,
|
|
31
37
|
topupReserveSol: strongholdTopupReserveSol,
|
|
32
38
|
};
|
|
33
|
-
// Build agent state from serialized or fresh
|
|
34
|
-
const prior = config.state;
|
|
35
|
-
const state = {
|
|
36
|
-
keypair,
|
|
37
|
-
publicKey,
|
|
38
|
-
personality,
|
|
39
|
-
holdings: new Map(Object.entries(prior?.holdings ?? {})),
|
|
40
|
-
founded: prior?.founded ?? [],
|
|
41
|
-
rallied: new Set(prior?.rallied ?? []),
|
|
42
|
-
voted: new Set([...(prior?.voted ?? []), ...Object.keys(prior?.holdings ?? {})]),
|
|
43
|
-
hasStronghold: prior?.hasStronghold ?? false,
|
|
44
|
-
activeLoans: new Set(prior?.activeLoans ?? []),
|
|
45
|
-
infiltrated: new Set(prior?.infiltrated ?? []),
|
|
46
|
-
sentiment: new Map(Object.entries(prior?.sentiment ?? {})),
|
|
47
|
-
allies: new Set(prior?.allies ?? []),
|
|
48
|
-
rivals: new Set(prior?.rivals ?? []),
|
|
49
|
-
actionCount: prior?.actionCount ?? 0,
|
|
50
|
-
lastAction: prior?.lastAction ?? 'none',
|
|
51
|
-
recentHistory: prior?.recentHistory ?? [],
|
|
52
|
-
};
|
|
53
39
|
// Track known factions and used names
|
|
54
40
|
const knownFactions = [];
|
|
55
41
|
const usedFactionNames = new Set();
|
|
56
42
|
const recentMessages = [];
|
|
43
|
+
// Runtime action tracking for live personality evolution
|
|
44
|
+
const actionCounts = new Array(14).fill(0);
|
|
45
|
+
const memoBuffer = [];
|
|
57
46
|
// Discover existing factions
|
|
58
47
|
try {
|
|
59
48
|
const result = await (0, pyre_world_kit_1.getFactions)(connection, { limit: 50, sort: 'newest' });
|
|
@@ -68,6 +57,59 @@ async function createPyreAgent(config) {
|
|
|
68
57
|
catch {
|
|
69
58
|
logger(`[${publicKey.slice(0, 8)}] faction discovery failed`);
|
|
70
59
|
}
|
|
60
|
+
// ─── On-Chain State Reconstruction ───────────────────────────────
|
|
61
|
+
// Derive personality, weights, sentiment, allies/rivals from chain history.
|
|
62
|
+
// Falls back to seed personality + serialized state if chain fetch fails.
|
|
63
|
+
let chainState = null;
|
|
64
|
+
let personality = seedPersonality;
|
|
65
|
+
let dynamicWeights;
|
|
66
|
+
let solRange = config.solRange ?? defaults_1.PERSONALITY_SOL[seedPersonality];
|
|
67
|
+
try {
|
|
68
|
+
chainState = await (0, chain_1.reconstructFromChain)(connection, publicKey, knownFactions, seedPersonality, { maxSignatures: 500 });
|
|
69
|
+
if (chainState.actionCount > 0) {
|
|
70
|
+
personality = chainState.personality;
|
|
71
|
+
dynamicWeights = chainState.weights;
|
|
72
|
+
solRange = chainState.solRange;
|
|
73
|
+
logger(`[${publicKey.slice(0, 8)}] on-chain reconstruction: ${chainState.actionCount} actions, personality: ${seedPersonality} → ${personality}, memories: ${chainState.memories.length}`);
|
|
74
|
+
}
|
|
75
|
+
else {
|
|
76
|
+
logger(`[${publicKey.slice(0, 8)}] no on-chain history, using seed personality: ${seedPersonality}`);
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
catch (err) {
|
|
80
|
+
logger(`[${publicKey.slice(0, 8)}] chain reconstruction failed (${err.message?.slice(0, 60)}), using fallback state`);
|
|
81
|
+
}
|
|
82
|
+
// Build agent state — chain-derived where available, serialized fallback, fresh default
|
|
83
|
+
const prior = config.state;
|
|
84
|
+
const state = {
|
|
85
|
+
keypair,
|
|
86
|
+
publicKey,
|
|
87
|
+
personality,
|
|
88
|
+
holdings: new Map(Object.entries(prior?.holdings ?? {})),
|
|
89
|
+
founded: chainState?.founded ?? prior?.founded ?? [],
|
|
90
|
+
rallied: new Set(prior?.rallied ?? []),
|
|
91
|
+
voted: new Set([...(prior?.voted ?? []), ...Object.keys(prior?.holdings ?? {})]),
|
|
92
|
+
hasStronghold: prior?.hasStronghold ?? false,
|
|
93
|
+
activeLoans: new Set(prior?.activeLoans ?? []),
|
|
94
|
+
infiltrated: new Set(prior?.infiltrated ?? []),
|
|
95
|
+
// Chain-derived sentiment as baseline, overwritten by serialized if available
|
|
96
|
+
sentiment: chainState && chainState.actionCount > 0
|
|
97
|
+
? chainState.sentiment
|
|
98
|
+
: new Map(Object.entries(prior?.sentiment ?? {})),
|
|
99
|
+
// Chain-derived allies/rivals as baseline
|
|
100
|
+
allies: chainState && chainState.actionCount > 0
|
|
101
|
+
? chainState.allies
|
|
102
|
+
: new Set(prior?.allies ?? []),
|
|
103
|
+
rivals: chainState && chainState.actionCount > 0
|
|
104
|
+
? chainState.rivals
|
|
105
|
+
: new Set(prior?.rivals ?? []),
|
|
106
|
+
actionCount: chainState?.actionCount ?? prior?.actionCount ?? 0,
|
|
107
|
+
lastAction: prior?.lastAction ?? 'none',
|
|
108
|
+
// Chain memories + recent history for LLM context
|
|
109
|
+
recentHistory: chainState && chainState.actionCount > 0
|
|
110
|
+
? chainState.recentHistory
|
|
111
|
+
: prior?.recentHistory ?? [],
|
|
112
|
+
};
|
|
71
113
|
// Ensure stronghold exists
|
|
72
114
|
await (0, stronghold_1.ensureStronghold)(connection, state, logger, strongholdOpts);
|
|
73
115
|
function serialize() {
|
|
@@ -95,14 +137,14 @@ async function createPyreAgent(config) {
|
|
|
95
137
|
let decision = null;
|
|
96
138
|
let usedLLM = false;
|
|
97
139
|
if (llm && activeFactions.length > 0) {
|
|
98
|
-
decision = await (0, agent_1.llmDecide)(state, activeFactions, connection, recentMessages, llm, logger, solRange);
|
|
140
|
+
decision = await (0, agent_1.llmDecide)(state, activeFactions, connection, recentMessages, llm, logger, solRange, chainState?.memories);
|
|
99
141
|
if (decision)
|
|
100
142
|
usedLLM = true;
|
|
101
143
|
}
|
|
102
|
-
// Fallback: weighted random
|
|
144
|
+
// Fallback: weighted random (using dynamic weights from chain history)
|
|
103
145
|
if (!decision) {
|
|
104
146
|
const canRally = activeFactions.some(f => !state.rallied.has(f.mint));
|
|
105
|
-
const action = (0, action_1.chooseAction)(state.personality, state, canRally, activeFactions);
|
|
147
|
+
const action = (0, action_1.chooseAction)(state.personality, state, canRally, activeFactions, dynamicWeights);
|
|
106
148
|
const [minSol, maxSol] = solRange;
|
|
107
149
|
decision = { action, sol: (0, util_1.randRange)(minSol, maxSol) };
|
|
108
150
|
// Pick a target faction for the fallback
|
|
@@ -200,6 +242,14 @@ async function createPyreAgent(config) {
|
|
|
200
242
|
if (recentMessages.length > 30)
|
|
201
243
|
recentMessages.shift();
|
|
202
244
|
}
|
|
245
|
+
// Track action for live personality evolution
|
|
246
|
+
if (result.success) {
|
|
247
|
+
const idx = (0, chain_1.actionIndex)(decision.action);
|
|
248
|
+
if (idx >= 0)
|
|
249
|
+
actionCounts[idx]++;
|
|
250
|
+
if (decision.message?.trim())
|
|
251
|
+
memoBuffer.push(decision.message);
|
|
252
|
+
}
|
|
203
253
|
return {
|
|
204
254
|
action: decision.action,
|
|
205
255
|
faction: decision.faction,
|
|
@@ -210,10 +260,25 @@ async function createPyreAgent(config) {
|
|
|
210
260
|
usedLLM,
|
|
211
261
|
};
|
|
212
262
|
}
|
|
263
|
+
function evolve() {
|
|
264
|
+
const total = actionCounts.reduce((a, b) => a + b, 0);
|
|
265
|
+
if (total < 5)
|
|
266
|
+
return false; // not enough data
|
|
267
|
+
const weights = (0, chain_1.weightsFromCounts)(actionCounts, seedPersonality);
|
|
268
|
+
const newPersonality = (0, chain_1.classifyPersonality)(weights, memoBuffer);
|
|
269
|
+
dynamicWeights = weights;
|
|
270
|
+
if (newPersonality !== state.personality) {
|
|
271
|
+
logger(`[${publicKey.slice(0, 8)}] personality evolved: ${state.personality} → ${newPersonality} (${total} runtime actions)`);
|
|
272
|
+
state.personality = newPersonality;
|
|
273
|
+
return true;
|
|
274
|
+
}
|
|
275
|
+
return false;
|
|
276
|
+
}
|
|
213
277
|
return {
|
|
214
278
|
publicKey,
|
|
215
|
-
personality,
|
|
279
|
+
personality: state.personality,
|
|
216
280
|
tick,
|
|
281
|
+
evolve,
|
|
217
282
|
getState: () => state,
|
|
218
283
|
serialize,
|
|
219
284
|
};
|
package/dist/types.d.ts
CHANGED
|
@@ -107,12 +107,48 @@ export interface SerializedAgentState {
|
|
|
107
107
|
export interface PyreAgent {
|
|
108
108
|
/** Agent's public key */
|
|
109
109
|
readonly publicKey: string;
|
|
110
|
-
/** Agent's personality */
|
|
110
|
+
/** Agent's personality (emergent from on-chain history) */
|
|
111
111
|
readonly personality: Personality;
|
|
112
112
|
/** Run one decision+action cycle */
|
|
113
113
|
tick(factions?: FactionInfo[]): Promise<AgentTickResult>;
|
|
114
|
+
/** Recompute personality + weights from accumulated runtime actions. Returns true if personality changed. */
|
|
115
|
+
evolve(): boolean;
|
|
114
116
|
/** Get current mutable state */
|
|
115
117
|
getState(): AgentState;
|
|
116
118
|
/** Serialize state for persistence */
|
|
117
119
|
serialize(): SerializedAgentState;
|
|
118
120
|
}
|
|
121
|
+
/** A single on-chain action parsed from transaction history */
|
|
122
|
+
export interface OnChainAction {
|
|
123
|
+
signature: string;
|
|
124
|
+
timestamp: number;
|
|
125
|
+
action: Action | 'fund' | 'dex_buy' | 'dex_sell' | 'unknown';
|
|
126
|
+
mint?: string;
|
|
127
|
+
memo?: string;
|
|
128
|
+
otherAgents?: string[];
|
|
129
|
+
}
|
|
130
|
+
/** State derived entirely from on-chain data */
|
|
131
|
+
export interface ChainDerivedState {
|
|
132
|
+
/** Personality weights computed from action frequency */
|
|
133
|
+
weights: number[];
|
|
134
|
+
/** Classified personality from weight distribution */
|
|
135
|
+
personality: Personality;
|
|
136
|
+
/** Sentiment from on-chain interactions (buys/sells/memos per faction) */
|
|
137
|
+
sentiment: Map<string, number>;
|
|
138
|
+
/** Allies derived from shared factions + positive memos */
|
|
139
|
+
allies: Set<string>;
|
|
140
|
+
/** Rivals derived from defections + negative memos */
|
|
141
|
+
rivals: Set<string>;
|
|
142
|
+
/** SOL spending range derived from actual tx amounts */
|
|
143
|
+
solRange: [number, number];
|
|
144
|
+
/** Total on-chain actions */
|
|
145
|
+
actionCount: number;
|
|
146
|
+
/** Recent action descriptions for LLM context */
|
|
147
|
+
recentHistory: string[];
|
|
148
|
+
/** Founded faction mints (agent was signer on CreateToken) */
|
|
149
|
+
founded: string[];
|
|
150
|
+
/** Agent's own memos as persistent memory */
|
|
151
|
+
memories: string[];
|
|
152
|
+
/** Raw parsed history */
|
|
153
|
+
history: OnChainAction[];
|
|
154
|
+
}
|