okami.mom/albedo

git clone https://git.nkp.pet/okami.mom/albedo

nekomimi.petperf: stop rediscovering skills on every session read; unload quiet sessions98d6ff4

main
3.4 KiB98 linesraw
1"""Quiet sessions unload, watched ones stay, and session reads reuse discovery."""
2
3import http.client
4import json
5import time
6import unittest
7
8from harness import Albedo, Provider, exclusive, text
9
10# Short enough that the test waits seconds, long enough to outlast a turn.
11UNLOAD_SECONDS = 2
12
13
14def loaded(app):
15    with app.api("/sessions?scope=all") as response:
16        return {
17            row["id"]: row["cursor"] is not None for row in json.load(response)["items"]
18        }
19
20
21def wait_for(condition, message):
22    deadline = time.monotonic() + UNLOAD_SECONDS * 5
23    while time.monotonic() < deadline:
24        if condition():
25            return
26        time.sleep(0.1)
27    raise AssertionError(message)
28
29
30# exclusive: shortens the daemon's unload limit
31@exclusive
32class SessionUnloadTests(unittest.TestCase):
33    def setUp(self):
34        self.provider = Provider(lambda _request: text("ok"))
35        self.addCleanup(self.provider.close)
36        self.app = Albedo(
37            self.provider,
38            prepare=lambda app: app.env.update(
39                ALBEDO_UNLOAD_SECONDS=str(UNLOAD_SECONDS)
40            ),
41        )
42        self.app.__enter__()
43        self.addCleanup(self.app.__exit__, None, None, None)
44
45    def turn(self, session, prompt):
46        self.app.prompt(session, prompt).close()
47        self.app.idle(session)
48
49    def test_quiet_session_unloads_unless_watched(self):
50        app = self.app
51        quiet, watched = app.session(), app.session()
52        self.turn(quiet, "hello")
53        self.turn(watched, "hello")
54        # A connection of its own, so the requests below never share it.
55        stream = http.client.HTTPConnection("127.0.0.1", app.connection["port"])
56        self.addCleanup(stream.close)
57        stream.request(
58            "GET",
59            f"/sessions/{watched}",
60            headers={
61                "Authorization": "Bearer " + app.connection["token"],
62                "Accept": "text/event-stream",
63            },
64        )
65        stream.getresponse().readline()
66
67        wait_for(lambda: not loaded(app)[quiet], "a quiet session stayed loaded")
68        self.assertTrue(loaded(app)[watched], "a watched session was unloaded")
69
70        # Reading stored history, as the session list's preview does, does
71        # not load the session again.
72        with app.api(f"/sessions/{quiet}/history?limit=16") as response:
73            self.assertTrue(json.load(response)["items"])
74        self.assertFalse(loaded(app)[quiet])
75
76        requests = len(self.provider.requests)
77        self.turn(quiet, "back again")
78        self.assertEqual(len(self.provider.requests), requests + 1)
79
80        stream.close()
81        wait_for(lambda: not loaded(app)[watched], "a detached session stayed loaded")
82
83    def test_session_reads_reuse_discovery_until_reload(self):
84        app = self.app
85        session = app.session()
86
87        def skills():
88            with app.api(f"/sessions/{session}?tail=0") as response:
89                return json.load(response)["selection"]["effective"]["skills"]
90
91        before = skills()
92        late = app.workspace / ".agents/skills/late/SKILL.md"
93        late.parent.mkdir(parents=True)
94        late.write_text("---\nname: late\ndescription: added later\n---\nbody\n")
95        self.assertEqual(skills(), before, "a session read walked skill files")
96        with app.api(f"/sessions/{session}/reload", {"target": "session"}) as response:
97            json.load(response)
98        self.assertTrue(any("late" in key for key in skills()))