@blockrun/franklin 3.15.21 → 3.15.22

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.
@@ -11,6 +11,16 @@ const ENTITIES_FILE = path.join(BRAIN_DIR, 'entities.jsonl');
11
11
  const OBSERVATIONS_FILE = path.join(BRAIN_DIR, 'observations.jsonl');
12
12
  const RELATIONS_FILE = path.join(BRAIN_DIR, 'relations.jsonl');
13
13
  const MAX_ENTITIES = 200;
14
+ // Observations and relations were previously unbounded — `extract.ts`
15
+ // runs at every session end (commands/start.ts:515) so they grew
16
+ // linearly forever. Caps below give comfortable headroom for a year+
17
+ // of normal use without making per-entity scans pathological:
18
+ // - 2000 obs / 200 entities = ~10 observations per entity on average
19
+ // - 500 relations covers heavy cross-references between the entity set
20
+ // On cap breach we drop the oldest entries — younger observations are
21
+ // usually more relevant and more confident than an aging one.
22
+ const MAX_OBSERVATIONS = 2000;
23
+ const MAX_RELATIONS = 500;
14
24
  function uid() { return crypto.randomBytes(8).toString('hex'); }
15
25
  function ensureDir() {
16
26
  fs.mkdirSync(BRAIN_DIR, { recursive: true });
@@ -110,7 +120,7 @@ export function addObservation(entityId, content, source, confidence = 0.8, tags
110
120
  if (existing.some(o => o.entity_id === entityId && o.content.toLowerCase().trim() === contentLower)) {
111
121
  return;
112
122
  }
113
- appendJsonl(OBSERVATIONS_FILE, {
123
+ const next = {
114
124
  id: uid(),
115
125
  entity_id: entityId,
116
126
  content,
@@ -118,7 +128,18 @@ export function addObservation(entityId, content, source, confidence = 0.8, tags
118
128
  confidence,
119
129
  tags,
120
130
  created_at: Date.now(),
121
- });
131
+ };
132
+ // Cap reached — rewrite the file with the trimmed set instead of
133
+ // appending. saveJsonl is atomic (tmp + rename) so a crash mid-write
134
+ // can't corrupt observations.
135
+ if (existing.length >= MAX_OBSERVATIONS) {
136
+ existing.sort((a, b) => b.created_at - a.created_at);
137
+ existing.length = MAX_OBSERVATIONS - 1;
138
+ existing.unshift(next);
139
+ saveJsonl(OBSERVATIONS_FILE, existing);
140
+ return;
141
+ }
142
+ appendJsonl(OBSERVATIONS_FILE, next);
122
143
  }
123
144
  // ─── Relations ────────────────────────────────────────────────────────────
124
145
  export function loadRelations() {
@@ -140,7 +161,7 @@ export function upsertRelation(fromId, toId, type, confidence = 0.8) {
140
161
  saveJsonl(RELATIONS_FILE, relations);
141
162
  }
142
163
  else {
143
- appendJsonl(RELATIONS_FILE, {
164
+ const next = {
144
165
  id: uid(),
145
166
  from_id: fromId,
146
167
  to_id: toId,
@@ -148,7 +169,25 @@ export function upsertRelation(fromId, toId, type, confidence = 0.8) {
148
169
  confidence,
149
170
  count: 1,
150
171
  last_seen: Date.now(),
151
- });
172
+ };
173
+ // Same cap pattern as observations. When the relation set is at
174
+ // its ceiling we drop the lowest-count, oldest-seen entries to
175
+ // make room — count + recency together approximate "this
176
+ // relation is still being seen", so eviction targets entries the
177
+ // brain extractor hasn't reinforced in a while.
178
+ if (relations.length >= MAX_RELATIONS) {
179
+ relations.sort((a, b) => {
180
+ if (b.count !== a.count)
181
+ return b.count - a.count;
182
+ return b.last_seen - a.last_seen;
183
+ });
184
+ relations.length = MAX_RELATIONS - 1;
185
+ relations.unshift(next);
186
+ saveJsonl(RELATIONS_FILE, relations);
187
+ }
188
+ else {
189
+ appendJsonl(RELATIONS_FILE, next);
190
+ }
152
191
  }
153
192
  }
154
193
  // ─── Search ───────────────────────────────────────────────────────────────
@@ -163,7 +163,7 @@ async function execute(input, ctx) {
163
163
  try {
164
164
  switch (action) {
165
165
  case 'searchPolymarket': {
166
- const raw = await getWithPayment('/api/v1/pm/polymarket/markets', {
166
+ const raw = await getWithPayment('/v1/pm/polymarket/markets', {
167
167
  search,
168
168
  status: status ?? 'active',
169
169
  sort: sort ?? 'volume',
@@ -193,7 +193,7 @@ async function execute(input, ctx) {
193
193
  return { output: lines.join('\n') };
194
194
  }
195
195
  case 'searchKalshi': {
196
- const raw = await getWithPayment('/api/v1/pm/kalshi/markets', {
196
+ const raw = await getWithPayment('/v1/pm/kalshi/markets', {
197
197
  search,
198
198
  status: status ?? 'open',
199
199
  sort: sort ?? 'volume',
@@ -223,7 +223,7 @@ async function execute(input, ctx) {
223
223
  return { output: lines.join('\n') };
224
224
  }
225
225
  case 'crossPlatform': {
226
- const raw = await getWithPayment('/api/v1/pm/matching-markets/pairs', {
226
+ const raw = await getWithPayment('/v1/pm/matching-markets/pairs', {
227
227
  limit: cappedLimit,
228
228
  }, ctx);
229
229
  const pairs = unwrapList(raw);
@@ -249,7 +249,7 @@ async function execute(input, ctx) {
249
249
  if (!conditionId) {
250
250
  return { output: 'Error: conditionId is required for smartMoney (Polymarket condition_id from a prior searchPolymarket call)', isError: true };
251
251
  }
252
- const path = `/api/v1/pm/polymarket/market/${encodeURIComponent(conditionId)}/smart-money`;
252
+ const path = `/v1/pm/polymarket/market/${encodeURIComponent(conditionId)}/smart-money`;
253
253
  const data = await getWithPayment(path, {}, ctx);
254
254
  const buyers = (data.buyers ?? []).slice(0, 5);
255
255
  const sellers = (data.sellers ?? []).slice(0, 5);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@blockrun/franklin",
3
- "version": "3.15.21",
3
+ "version": "3.15.22",
4
4
  "description": "Franklin — The AI agent with a wallet. Spends USDC autonomously to get real work done. Pay per action, no subscriptions.",
5
5
  "type": "module",
6
6
  "exports": {