a tool to help your Letta AI agents navigate bluesky

Merge pull request #11 from taurean/improved-sleep-schedule

Improved sleep schedule

authored by taurean.bryant.land and committed by GitHub d2332c3a 307ec095

+2 -1
deno.json
··· 3 3 "config": "deno run --allow-read --allow-write setup.ts", 4 4 "mount": "deno run --allow-net --allow-env --allow-read --env mount.ts", 5 5 "watch": "deno run --allow-net --allow-env --env --watch main.ts", 6 - "start": "deno run --allow-net --allow-env --env main.ts" 6 + "start": "deno run --allow-net --allow-env --env main.ts", 7 + "test": "deno test" 7 8 }, 8 9 "imports": { 9 10 "@std/assert": "jsr:@std/assert@1",
+2 -1
tasks/sendSleepMessage.ts
··· 2 2 agentContext, 3 3 claimTaskThread, 4 4 releaseTaskThread, 5 + isAgentAsleep, 5 6 } from "../utils/agentContext.ts"; 6 7 import { 7 8 getNow, ··· 35 36 36 37 const now = getNow(); 37 38 38 - if (now.hour >= agentContext.sleepTime) { 39 + if (isAgentAsleep(now.hour)) { 39 40 console.log(`🔹 attempting to wind down ${agentContext.agentBskyName}`); 40 41 } else { 41 42 const delay = msUntilDailyWindow(
+2 -1
tasks/sendWakeMessage.ts
··· 2 2 agentContext, 3 3 claimTaskThread, 4 4 releaseTaskThread, 5 + isAgentAwake, 5 6 } from "../utils/agentContext.ts"; 6 7 import { getNow, msFrom, msRandomOffset } from "../utils/time.ts"; 7 8 import { messageAgent } from "../utils/messageAgent.ts"; ··· 31 32 32 33 const now = getNow(); 33 34 34 - if (now.hour >= agentContext.wakeTime && now.hour < agentContext.sleepTime) { 35 + if (isAgentAwake(now.hour)) { 35 36 console.log(`🔹 attempting to wake up ${agentContext.agentBskyName}`); 36 37 } else { 37 38 const delay = msUntilDailyWindow(
+175
utils/agentContext.test.ts
··· 1 + import { assertEquals } from "@std/assert"; 2 + import { isAgentAwake, isAgentAsleep } from "./sleepWakeHelpers.ts"; 3 + 4 + // Normal Schedule Tests (wake=8, sleep=22) 5 + // Agent should be awake from 8am to 10pm 6 + 7 + Deno.test("Normal schedule - should be asleep before wake time (7am)", () => { 8 + assertEquals(isAgentAwake(7, 8, 22), false); 9 + assertEquals(isAgentAsleep(7, 8, 22), true); 10 + }); 11 + 12 + Deno.test("Normal schedule - should be awake at wake time (8am)", () => { 13 + assertEquals(isAgentAwake(8, 8, 22), true); 14 + assertEquals(isAgentAsleep(8, 8, 22), false); 15 + }); 16 + 17 + Deno.test("Normal schedule - should be awake during day (12pm)", () => { 18 + assertEquals(isAgentAwake(12, 8, 22), true); 19 + assertEquals(isAgentAsleep(12, 8, 22), false); 20 + }); 21 + 22 + Deno.test("Normal schedule - should be awake before sleep time (9pm)", () => { 23 + assertEquals(isAgentAwake(21, 8, 22), true); 24 + assertEquals(isAgentAsleep(21, 8, 22), false); 25 + }); 26 + 27 + Deno.test("Normal schedule - should be asleep at sleep time (10pm)", () => { 28 + assertEquals(isAgentAwake(22, 8, 22), false); 29 + assertEquals(isAgentAsleep(22, 8, 22), true); 30 + }); 31 + 32 + Deno.test("Normal schedule - should be asleep late night (11pm)", () => { 33 + assertEquals(isAgentAwake(23, 8, 22), false); 34 + assertEquals(isAgentAsleep(23, 8, 22), true); 35 + }); 36 + 37 + Deno.test("Normal schedule - should be asleep at midnight", () => { 38 + assertEquals(isAgentAwake(0, 8, 22), false); 39 + assertEquals(isAgentAsleep(0, 8, 22), true); 40 + }); 41 + 42 + // Cross-Midnight Schedule Tests (wake=9, sleep=2) 43 + // Agent should be awake from 9am to 2am (next day) 44 + 45 + Deno.test("Cross-midnight schedule - should be awake at midnight", () => { 46 + assertEquals(isAgentAwake(0, 9, 2), true); 47 + assertEquals(isAgentAsleep(0, 9, 2), false); 48 + }); 49 + 50 + Deno.test("Cross-midnight schedule - should be awake late night (1am)", () => { 51 + assertEquals(isAgentAwake(1, 9, 2), true); 52 + assertEquals(isAgentAsleep(1, 9, 2), false); 53 + }); 54 + 55 + Deno.test("Cross-midnight schedule - should be asleep at sleep time (2am)", () => { 56 + assertEquals(isAgentAwake(2, 9, 2), false); 57 + assertEquals(isAgentAsleep(2, 9, 2), true); 58 + }); 59 + 60 + Deno.test("Cross-midnight schedule - should be asleep early morning (3am)", () => { 61 + assertEquals(isAgentAwake(3, 9, 2), false); 62 + assertEquals(isAgentAsleep(3, 9, 2), true); 63 + }); 64 + 65 + Deno.test("Cross-midnight schedule - should be asleep before wake (8am)", () => { 66 + assertEquals(isAgentAwake(8, 9, 2), false); 67 + assertEquals(isAgentAsleep(8, 9, 2), true); 68 + }); 69 + 70 + Deno.test("Cross-midnight schedule - should be awake at wake time (9am)", () => { 71 + assertEquals(isAgentAwake(9, 9, 2), true); 72 + assertEquals(isAgentAsleep(9, 9, 2), false); 73 + }); 74 + 75 + Deno.test("Cross-midnight schedule - should be awake during day (12pm)", () => { 76 + assertEquals(isAgentAwake(12, 9, 2), true); 77 + assertEquals(isAgentAsleep(12, 9, 2), false); 78 + }); 79 + 80 + Deno.test("Cross-midnight schedule - should be awake late night (11pm)", () => { 81 + assertEquals(isAgentAwake(23, 9, 2), true); 82 + assertEquals(isAgentAsleep(23, 9, 2), false); 83 + }); 84 + 85 + // Edge Case Tests 86 + 87 + Deno.test("Edge case - equal wake/sleep times (midnight) should be asleep", () => { 88 + // When wake == sleep, the agent should be asleep at all hours 89 + assertEquals(isAgentAwake(0, 0, 0), false); 90 + assertEquals(isAgentAsleep(0, 0, 0), true); 91 + assertEquals(isAgentAwake(12, 0, 0), false); 92 + assertEquals(isAgentAsleep(12, 0, 0), true); 93 + }); 94 + 95 + Deno.test("Edge case - nearly 24 hours awake (wake=1, sleep=0)", () => { 96 + // Asleep only from midnight to 1am 97 + assertEquals(isAgentAwake(0, 1, 0), false); 98 + assertEquals(isAgentAsleep(0, 1, 0), true); 99 + assertEquals(isAgentAwake(1, 1, 0), true); 100 + assertEquals(isAgentAsleep(1, 1, 0), false); 101 + assertEquals(isAgentAwake(23, 1, 0), true); 102 + assertEquals(isAgentAsleep(23, 1, 0), false); 103 + }); 104 + 105 + Deno.test("Edge case - nearly 24 hours asleep (wake=0, sleep=23)", () => { 106 + // Awake only from midnight to 11pm 107 + assertEquals(isAgentAwake(0, 0, 23), true); 108 + assertEquals(isAgentAsleep(0, 0, 23), false); 109 + assertEquals(isAgentAwake(22, 0, 23), true); 110 + assertEquals(isAgentAsleep(22, 0, 23), false); 111 + assertEquals(isAgentAwake(23, 0, 23), false); 112 + assertEquals(isAgentAsleep(23, 0, 23), true); 113 + }); 114 + 115 + Deno.test("Edge case - adjacent hours (wake=10, sleep=11)", () => { 116 + // Awake only from 10am to 11am 117 + assertEquals(isAgentAwake(9, 10, 11), false); 118 + assertEquals(isAgentAsleep(9, 10, 11), true); 119 + assertEquals(isAgentAwake(10, 10, 11), true); 120 + assertEquals(isAgentAsleep(10, 10, 11), false); 121 + assertEquals(isAgentAwake(11, 10, 11), false); 122 + assertEquals(isAgentAsleep(11, 10, 11), true); 123 + assertEquals(isAgentAwake(12, 10, 11), false); 124 + assertEquals(isAgentAsleep(12, 10, 11), true); 125 + }); 126 + 127 + // Inverse Relationship Tests 128 + 129 + Deno.test("Inverse relationship - awake and asleep are always opposite (normal schedule)", () => { 130 + const wakeTime = 8; 131 + const sleepTime = 22; 132 + 133 + // Test all 24 hours 134 + for (let hour = 0; hour < 24; hour++) { 135 + const awake = isAgentAwake(hour, wakeTime, sleepTime); 136 + const asleep = isAgentAsleep(hour, wakeTime, sleepTime); 137 + assertEquals( 138 + awake, 139 + !asleep, 140 + `Hour ${hour}: awake=${awake}, asleep=${asleep} should be opposite`, 141 + ); 142 + } 143 + }); 144 + 145 + Deno.test("Inverse relationship - awake and asleep are always opposite (cross-midnight)", () => { 146 + const wakeTime = 9; 147 + const sleepTime = 2; 148 + 149 + // Test all 24 hours 150 + for (let hour = 0; hour < 24; hour++) { 151 + const awake = isAgentAwake(hour, wakeTime, sleepTime); 152 + const asleep = isAgentAsleep(hour, wakeTime, sleepTime); 153 + assertEquals( 154 + awake, 155 + !asleep, 156 + `Hour ${hour}: awake=${awake}, asleep=${asleep} should be opposite`, 157 + ); 158 + } 159 + }); 160 + 161 + Deno.test("Inverse relationship - awake and asleep are always opposite (edge case)", () => { 162 + const wakeTime = 23; 163 + const sleepTime = 1; 164 + 165 + // Test all 24 hours 166 + for (let hour = 0; hour < 24; hour++) { 167 + const awake = isAgentAwake(hour, wakeTime, sleepTime); 168 + const asleep = isAgentAsleep(hour, wakeTime, sleepTime); 169 + assertEquals( 170 + awake, 171 + !asleep, 172 + `Hour ${hour}: awake=${awake}, asleep=${asleep} should be opposite`, 173 + ); 174 + } 175 + });
+12
utils/agentContext.ts
··· 14 14 } from "./const.ts"; 15 15 import { msFrom } from "./time.ts"; 16 16 import { bsky } from "./bsky.ts"; 17 + import { 18 + isAgentAsleep as checkIsAsleep, 19 + isAgentAwake as checkIsAwake, 20 + } from "./sleepWakeHelpers.ts"; 17 21 18 22 export const getLettaApiKey = (): string => { 19 23 const value = Deno.env.get("LETTA_API_KEY")?.trim(); ··· 638 642 agentContext.replyCount = 0; 639 643 agentContext.quoteCount = 0; 640 644 }; 645 + 646 + export const isAgentAwake = (hour: number): boolean => { 647 + return checkIsAwake(hour, agentContext.wakeTime, agentContext.sleepTime); 648 + }; 649 + 650 + export const isAgentAsleep = (hour: number): boolean => { 651 + return checkIsAsleep(hour, agentContext.wakeTime, agentContext.sleepTime); 652 + };
+40
utils/sleepWakeHelpers.ts
··· 1 + /** 2 + * Helper functions for determining agent sleep/wake status 3 + * These functions handle both normal schedules and cross-midnight schedules 4 + */ 5 + 6 + /** 7 + * Core logic: Determines if agent should be asleep at given hour 8 + */ 9 + export const isAgentAsleep = ( 10 + hour: number, 11 + wakeTime: number, 12 + sleepTime: number, 13 + ): boolean => { 14 + // Edge case: if wake == sleep, agent has zero awake time (always asleep) 15 + if (wakeTime === sleepTime) { 16 + return true; 17 + } 18 + 19 + // If sleepTime > wakeTime: normal same-day schedule (e.g., wake=8, sleep=22) 20 + // Agent is asleep from sleepTime until midnight, OR from midnight until wakeTime 21 + if (sleepTime > wakeTime) { 22 + return hour >= sleepTime || hour < wakeTime; 23 + } 24 + 25 + // If sleepTime < wakeTime: schedule crosses midnight (e.g., wake=9, sleep=2) 26 + // Agent is asleep from sleepTime until wakeTime (same day) 27 + return hour >= sleepTime && hour < wakeTime; 28 + }; 29 + 30 + /** 31 + * Semantic wrapper: Determines if agent should be awake at given hour 32 + * Simply the inverse of isAgentAsleep 33 + */ 34 + export const isAgentAwake = ( 35 + hour: number, 36 + wakeTime: number, 37 + sleepTime: number, 38 + ): boolean => { 39 + return !isAgentAsleep(hour, wakeTime, sleepTime); 40 + };