hermes: close stale API worker sessions
This commit is contained in:
parent
6f459dfc63
commit
6fd7b9bbba
@ -37,6 +37,28 @@ TRIAGE_MESSAGE_PREFIXES = (
|
||||
)
|
||||
|
||||
|
||||
def close_stale_api_workers(connection: sqlite3.Connection) -> int:
|
||||
"""Close parent-linked API runs that survived a prior gateway lifetime."""
|
||||
columns = {
|
||||
str(row[1])
|
||||
for row in connection.execute("PRAGMA table_info(sessions)").fetchall()
|
||||
}
|
||||
if not {"ended_at", "end_reason"}.issubset(columns):
|
||||
return 0
|
||||
cursor = connection.execute(
|
||||
"""
|
||||
UPDATE sessions
|
||||
SET ended_at = ?,
|
||||
end_reason = 'api_run_recovered_stale'
|
||||
WHERE source = 'api_server'
|
||||
AND parent_session_id IS NOT NULL
|
||||
AND ended_at IS NULL
|
||||
""",
|
||||
(time.time(),),
|
||||
)
|
||||
return cursor.rowcount
|
||||
|
||||
|
||||
def _triage_title(message: str, session_id: str) -> str:
|
||||
"""Create a concise label from Ariadne's stable incident contract."""
|
||||
match = re.search(r"(?:for|Analyze) incident ([^\s.]+)(?:\.|\s)", message)
|
||||
@ -98,6 +120,7 @@ def migrate(path: Path = STATE_DB, *, group_triage: bool = False) -> int:
|
||||
return 0
|
||||
changed = 0
|
||||
with sqlite3.connect(path) as connection:
|
||||
changed += close_stale_api_workers(connection)
|
||||
parent = connection.execute(
|
||||
"SELECT id FROM sessions WHERE id = ?", (LEGACY_CASSANDRA_PARENT,)
|
||||
).fetchone()
|
||||
|
||||
@ -1186,6 +1186,46 @@ def test_automated_triage_sessions_are_grouped_without_touching_interactive_runs
|
||||
)
|
||||
|
||||
|
||||
def test_stale_parent_linked_api_workers_close_on_startup(tmp_path: Path):
|
||||
"""A previous gateway lifetime cannot leave phantom active workers."""
|
||||
module_path = HERMES / "scripts" / "migrate_api_session_lineage.py"
|
||||
spec = importlib.util.spec_from_file_location("close_stale_api_workers", module_path)
|
||||
assert spec and spec.loader
|
||||
module = importlib.util.module_from_spec(spec)
|
||||
spec.loader.exec_module(module)
|
||||
database = tmp_path / "state.db"
|
||||
with sqlite3.connect(database) as connection:
|
||||
connection.execute(
|
||||
"CREATE TABLE sessions (id TEXT PRIMARY KEY, source TEXT, "
|
||||
"parent_session_id TEXT, title TEXT, started_at REAL, ended_at REAL, "
|
||||
"end_reason TEXT, archived INTEGER DEFAULT 0)"
|
||||
)
|
||||
connection.executemany(
|
||||
"INSERT INTO sessions "
|
||||
"(id, source, parent_session_id, started_at, ended_at, end_reason) "
|
||||
"VALUES (?, ?, ?, 1, ?, ?)",
|
||||
(
|
||||
("stale", "api_server", "parent", None, None),
|
||||
("root", "api_server", None, None, None),
|
||||
("finished", "api_server", "parent", 2.0, "api_run_completed"),
|
||||
("interactive", "tui", "parent", None, None),
|
||||
),
|
||||
)
|
||||
|
||||
assert module.migrate(database) == 1
|
||||
assert module.migrate(database) == 0
|
||||
with sqlite3.connect(database) as connection:
|
||||
rows = connection.execute(
|
||||
"SELECT id, ended_at, end_reason FROM sessions ORDER BY id"
|
||||
).fetchall()
|
||||
by_id = {row[0]: row[1:] for row in rows}
|
||||
assert by_id["stale"][0] is not None
|
||||
assert by_id["stale"][1] == "api_run_recovered_stale"
|
||||
assert by_id["root"] == (None, None)
|
||||
assert by_id["finished"] == (2.0, "api_run_completed")
|
||||
assert by_id["interactive"] == (None, None)
|
||||
|
||||
|
||||
def test_switchyard_brokers_and_native_claude_lane_use_the_right_images():
|
||||
"""Thin brokers stay small while native Claude runs beside owner auth."""
|
||||
dockerfile = (ROOT / "dockerfiles" / "Dockerfile.hermes-switchyard-brokers").read_text()
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user