hermes: group legacy triage API runs

This commit is contained in:
jenkins 2026-08-13 01:14:31 -03:00
parent 33df446a68
commit 42d4b34557
4 changed files with 40 additions and 15 deletions

View File

@ -224,6 +224,8 @@ spec:
/opt/hermes/.venv/bin/python /opt/coordinator/migrate_api_session_lineage.py
env:
- {name: HERMES_API_DEFAULT_PARENT_SESSION_ID, value: automated-triage}
- name: HERMES_API_DEFAULT_PARENT_MATCH_PREFIXES
value: "A static-analysis finding, not a build failure.||Use $triage-titan-test-failures."
securityContext:
allowPrivilegeEscalation: false
runAsUser: 10000
@ -309,8 +311,8 @@ spec:
value: triage
- name: HERMES_API_DEFAULT_PARENT_SESSION_ID
value: automated-triage
- name: HERMES_API_DEFAULT_PARENT_MATCH_PREFIX
value: A static-analysis finding, not a build failure.
- name: HERMES_API_DEFAULT_PARENT_MATCH_PREFIXES
value: "A static-analysis finding, not a build failure.||Use $triage-titan-test-failures."
# Claude subscription OAuth token (sk-ant-oat01...). The anthropic
# provider accepts ANTHROPIC_API_KEY, ANTHROPIC_TOKEN, or this, in
# that order; an OAuth token is not an API key, so it must arrive

View File

@ -27,12 +27,15 @@ LEGACY_ORPHANED_SMOKE_SESSIONS = {
}
TRIAGE_PARENT = "automated-triage"
TRIAGE_PARENT_TITLE = "Automated triage"
TRIAGE_MESSAGE_PREFIX = "A static-analysis finding, not a build failure."
TRIAGE_MESSAGE_PREFIXES = (
"A static-analysis finding, not a build failure.",
"Use $triage-titan-test-failures.",
)
def _triage_title(message: str, session_id: str) -> str:
"""Create a concise label from Ariadne's stable incident contract."""
match = re.search(r"for incident ([^\s]+)\.", message)
match = re.search(r"(?:for|Analyze) incident ([^\s.]+)(?:\.|\s)", message)
if not match:
return f"Automated triage run · {session_id[-8:]}"
parts = match.group(1).split("/")
@ -40,6 +43,11 @@ def _triage_title(message: str, session_id: str) -> str:
return f"Sonar · {label} · {session_id[-8:]}"
def _is_automated_triage(message: str) -> bool:
"""Recognize only the stable Ariadne automation contracts."""
return message.startswith(TRIAGE_MESSAGE_PREFIXES)
def migrate_triage_group(connection: sqlite3.Connection) -> int:
"""Nest only recognized automated Sonar runs under one durable parent."""
rows = connection.execute(
@ -51,11 +59,10 @@ def migrate_triage_group(connection: sqlite3.Connection) -> int:
AND s.parent_session_id IS NULL
AND s.archived = 0
AND m.role = 'user'
AND m.content LIKE ?
ORDER BY s.started_at
""",
(f"{TRIAGE_MESSAGE_PREFIX}%",),
"""
).fetchall()
rows = [row for row in rows if _is_automated_triage(str(row[2] or ""))]
if not rows:
return 0
started_at = min(float(row[1] or time.time()) for row in rows)

View File

@ -65,8 +65,12 @@ RUNS_AFTER = ''' run_id = f"run_{uuid.uuid4().hex}"
"X-Hermes-Parent-Session-Id"
)
default_parent = os.environ.get("HERMES_API_DEFAULT_PARENT_SESSION_ID", "").strip()
default_prefix = os.environ.get("HERMES_API_DEFAULT_PARENT_MATCH_PREFIX", "").strip()
if not raw_parent and default_parent and default_prefix and user_message.startswith(default_prefix):
default_prefixes = tuple(
item.strip()
for item in os.environ.get("HERMES_API_DEFAULT_PARENT_MATCH_PREFIXES", "").split("||")
if item.strip()
)
if not raw_parent and default_parent and default_prefixes and user_message.startswith(default_prefixes):
raw_parent = default_parent
parent_session_id = str(raw_parent).strip() if raw_parent else None
@ -92,7 +96,7 @@ RUNS_AFTER = ''' run_id = f"run_{uuid.uuid4().hex}"
system_prompt=instructions if isinstance(instructions, str) else None,
parent_session_id=parent_session_id,
)
incident = re.search(r"for incident ([^\\s]+)\\.", user_message)
incident = re.search(r"(?:for|Analyze) incident ([^\\s.]+)(?:\\.|\\s)", user_message)
if incident and parent_session_id == default_parent:
parts = incident.group(1).split("/")
label = " · ".join(parts[1:3]) if len(parts) >= 3 else incident.group(1)

View File

@ -997,8 +997,8 @@ def test_api_session_patch_accepts_parent_lineage(tmp_path: Path):
assert "X-Hermes-Parent-Session-Id" in patched
assert "parent_session_id=parent_session_id" in patched
assert "Parent session not found" in patched
assert "HERMES_API_DEFAULT_PARENT_MATCH_PREFIX" in patched
assert "user_message.startswith(default_prefix)" in patched
assert "HERMES_API_DEFAULT_PARENT_MATCH_PREFIXES" in patched
assert "user_message.startswith(default_prefixes)" in patched
def test_legacy_api_sessions_are_nested_idempotently(tmp_path: Path):
@ -1074,21 +1074,26 @@ def test_automated_triage_sessions_are_grouped_without_touching_interactive_runs
)
connection.executemany(
"INSERT INTO sessions (id, source, started_at) VALUES (?, 'api_server', ?)",
(("triage-run", 1.0), ("interactive-run", 2.0)),
(("triage-run", 1.0), ("jenkins-run", 1.5), ("interactive-run", 2.0)),
)
connection.executemany(
"INSERT INTO messages (session_id, role, content) VALUES (?, 'user', ?)",
(
(
"triage-run",
module.TRIAGE_MESSAGE_PREFIX
module.TRIAGE_MESSAGE_PREFIXES[0]
+ " Fix for incident sonar/bstein_home/python:S2208/finding-key.",
),
(
"jenkins-run",
module.TRIAGE_MESSAGE_PREFIXES[1]
+ "\nAnalyze incident soteria/291 for the Jenkins job soteria.",
),
("interactive-run", "Please explain this alert to me."),
),
)
assert module.migrate(database, group_triage=True) == 1
assert module.migrate(database, group_triage=True) == 2
assert module.migrate(database, group_triage=True) == 0
with sqlite3.connect(database) as connection:
parent = connection.execute(
@ -1100,12 +1105,19 @@ def test_automated_triage_sessions_are_grouped_without_touching_interactive_runs
interactive = connection.execute(
"SELECT parent_session_id FROM sessions WHERE id = 'interactive-run'"
).fetchone()
jenkins = connection.execute(
"SELECT parent_session_id, title FROM sessions WHERE id = 'jenkins-run'"
).fetchone()
assert parent == (module.TRIAGE_PARENT_TITLE,)
assert triage == (
module.TRIAGE_PARENT,
"Sonar · bstein_home · python:S2208 · iage-run",
)
assert interactive == (None,)
assert jenkins == (
module.TRIAGE_PARENT,
"Sonar · soteria/291 · kins-run",
)
def test_switchyard_brokers_and_native_claude_lane_use_the_right_images():