Skip to content

Commit 014df53

Browse files
fix: funding tier cooldowns share one key, suppressing notifications
executeFundingStrategies uses a single `last_funding_request` KV key for all three tier cooldowns (low_compute: 24h, critical: 6h, dead: 2h). When the dead-tier plea fires every 2 hours, it resets the shared timer — so if the agent recovers to low_compute, the polite creator notification is suppressed for up to 24 hours because hoursSinceLastBeg is only ~2h (from the dead-tier write). Fix: use per-tier keys (`last_funding_request_<tier>`) so each tier tracks its own cooldown independently. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
1 parent 08487fe commit 014df53

2 files changed

Lines changed: 106 additions & 5 deletions

File tree

src/__tests__/funding.test.ts

Lines changed: 98 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,98 @@
1+
/**
2+
* Funding Strategy Tests
3+
*
4+
* Tests for executeFundingStrategies, especially per-tier cooldown isolation.
5+
*/
6+
7+
import { describe, it, expect, beforeEach, afterEach } from "vitest";
8+
import { executeFundingStrategies } from "../survival/funding.js";
9+
import {
10+
MockConwayClient,
11+
createTestDb,
12+
createTestIdentity,
13+
createTestConfig,
14+
} from "./mocks.js";
15+
import type { AutomatonDatabase } from "../types.js";
16+
17+
describe("executeFundingStrategies", () => {
18+
let db: AutomatonDatabase;
19+
let conway: MockConwayClient;
20+
21+
beforeEach(() => {
22+
db = createTestDb();
23+
conway = new MockConwayClient();
24+
conway.creditsCents = 5; // low balance
25+
});
26+
27+
afterEach(() => {
28+
db.close();
29+
});
30+
31+
it("dead-tier cooldown does not suppress low_compute notification", async () => {
32+
const identity = createTestIdentity();
33+
const config = createTestConfig();
34+
35+
// First: trigger dead-tier plea
36+
const deadAttempts = await executeFundingStrategies(
37+
"dead",
38+
identity,
39+
config,
40+
db,
41+
conway,
42+
);
43+
expect(deadAttempts.length).toBe(1);
44+
expect(deadAttempts[0].strategy).toBe("desperate_plea");
45+
46+
// Now: agent recovers to low_compute. With the fix, the low_compute
47+
// notification should fire because it has its own cooldown key.
48+
const lowAttempts = await executeFundingStrategies(
49+
"low_compute",
50+
identity,
51+
config,
52+
db,
53+
conway,
54+
);
55+
expect(lowAttempts.length).toBe(1);
56+
expect(lowAttempts[0].strategy).toBe("polite_creator_notification");
57+
});
58+
59+
it("critical-tier cooldown does not suppress low_compute notification", async () => {
60+
const identity = createTestIdentity();
61+
const config = createTestConfig();
62+
63+
// Trigger critical-tier notice
64+
const criticalAttempts = await executeFundingStrategies(
65+
"critical",
66+
identity,
67+
config,
68+
db,
69+
conway,
70+
);
71+
expect(criticalAttempts.length).toBe(1);
72+
expect(criticalAttempts[0].strategy).toBe("urgent_local_notice");
73+
74+
// low_compute should still fire independently
75+
const lowAttempts = await executeFundingStrategies(
76+
"low_compute",
77+
identity,
78+
config,
79+
db,
80+
conway,
81+
);
82+
expect(lowAttempts.length).toBe(1);
83+
expect(lowAttempts[0].strategy).toBe("polite_creator_notification");
84+
});
85+
86+
it("respects per-tier cooldown on repeated calls", async () => {
87+
const identity = createTestIdentity();
88+
const config = createTestConfig();
89+
90+
// First dead-tier call fires
91+
const first = await executeFundingStrategies("dead", identity, config, db, conway);
92+
expect(first.length).toBe(1);
93+
94+
// Immediate second dead-tier call should be suppressed (2h cooldown)
95+
const second = await executeFundingStrategies("dead", identity, config, db, conway);
96+
expect(second.length).toBe(0);
97+
});
98+
});

src/survival/funding.ts

Lines changed: 8 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -35,16 +35,19 @@ export async function executeFundingStrategies(
3535
const attempts: FundingAttempt[] = [];
3636
const creditsCents = await conway.getCreditsBalance().catch(() => 0);
3737

38-
// Check how recently we last begged (don't spam)
39-
const lastBeg = db.getKV("last_funding_request");
38+
// Check how recently we last begged for this specific tier (don't spam).
39+
// Each tier has its own cooldown key so that e.g. dead-tier begs (2h)
40+
// don't suppress the low_compute notification (24h cooldown).
41+
const tierKey = `last_funding_request_${tier}`;
42+
const lastBeg = db.getKV(tierKey);
4043
const lastBegTime = lastBeg ? new Date(lastBeg).getTime() : 0;
4144
const hoursSinceLastBeg = (Date.now() - lastBegTime) / (1000 * 60 * 60);
4245

4346
if (tier === "low_compute" && hoursSinceLastBeg > 24) {
4447
// Record low-compute notice locally.
4548
const msg = `Low compute: ${formatCredits(creditsCents)} remaining. Consider credit top-up for ${identity.address}.`;
4649
db.setKV("funding_notice_low", msg);
47-
db.setKV("last_funding_request", new Date().toISOString());
50+
db.setKV(tierKey, new Date().toISOString());
4851

4952
attempts.push({
5053
strategy: "polite_creator_notification",
@@ -57,7 +60,7 @@ export async function executeFundingStrategies(
5760
if (tier === "critical" && hoursSinceLastBeg > 6) {
5861
const msg = `Critical compute: ${formatCredits(creditsCents)} remaining. Top up via credit transfer API to ${identity.address}.`;
5962
db.setKV("funding_notice_critical", msg);
60-
db.setKV("last_funding_request", new Date().toISOString());
63+
db.setKV(tierKey, new Date().toISOString());
6164

6265
attempts.push({
6366
strategy: "urgent_local_notice",
@@ -70,7 +73,7 @@ export async function executeFundingStrategies(
7073
if (tier === "dead" && hoursSinceLastBeg > 2) {
7174
const plea = `Dead tier reached. ${config.name} has ${formatCredits(creditsCents)} remaining after ${db.getTurnCount()} turns. Top-up required at ${identity.address}.`;
7275
db.setKV("funding_notice_dead", plea);
73-
db.setKV("last_funding_request", new Date().toISOString());
76+
db.setKV(tierKey, new Date().toISOString());
7477

7578
attempts.push({
7679
strategy: "desperate_plea",

0 commit comments

Comments
 (0)