Skip to content

Commit 2ce3b00

Browse files
authored
fix(agent): finish the live query in evidence gather instead of stopping at discovery (#4976)
1 parent 31f3341 commit 2ce3b00

42 files changed

Lines changed: 1035 additions & 63 deletions

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

.github/workflows/release.yml

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -372,6 +372,31 @@ jobs:
372372
exit 1
373373
fi
374374
375+
- name: Re-sign macOS onedir after post-build mutations
376+
if: runner.os == 'macOS' && matrix.pyinstaller_mode == 'onedir'
377+
shell: bash
378+
run: |
379+
set -euo pipefail
380+
# Replacing/rewriting bundled dylibs (libssl align) invalidates the
381+
# adhoc signature PyInstaller applied. Consumer Macs then SIGKILL the
382+
# binary on first exec (exit 137, CODESIGNING / Invalid Page) even when
383+
# CI runners still let --version through. Sign nested libs first, then
384+
# the main binary — ``codesign --deep`` on the directory fails with
385+
# "invalid resource directory" for this PyInstaller layout.
386+
APP_DIR="./dist/opensre"
387+
APP_BIN="${APP_DIR}/opensre"
388+
if [ ! -f "$APP_BIN" ]; then
389+
echo "No onedir binary at ${APP_BIN}; skip re-sign"
390+
exit 0
391+
fi
392+
find "$APP_DIR" -type f \( -name '*.dylib' -o -name '*.so' -o -name '*.so.*' \) -print0 \
393+
| while IFS= read -r -d '' lib; do
394+
codesign --force --sign - "$lib"
395+
done
396+
codesign --force --sign - "$APP_BIN"
397+
codesign --verify --strict "$APP_BIN"
398+
echo "Re-signed onedir libs + ${APP_BIN} (adhoc) after post-build mutations"
399+
375400
- name: Smoke test binary (Unix)
376401
if: runner.os != 'Windows'
377402
shell: bash

AGENTS.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -311,6 +311,11 @@ Steps:
311311
when appending to an existing file: **use the import style the file already
312312
established** (an existing `import core.context_budget as budget` means new
313313
code calls `budget.name`, not `from core.context_budget import name`).
314+
- Except block handles `BaseException` (code-quality): catch `Exception`, not
315+
`BaseException`. Collecting request/transport failures in a test thread still
316+
works — `requests.exceptions.ConnectTimeout` subclasses `Exception`. Catching
317+
`BaseException` also swallows `KeyboardInterrupt` / `SystemExit`. Do not keep
318+
`noqa: BLE001` to silence it.
314319
- Unused global variable (CodeQL / code-quality "Unused global variable"):
315320
CodeQL often **does not credit cross-module imports** as a use of a module-
316321
level constant. A `FOO = "..."` in `text.py` that is only read via

core/agent_harness/AGENTS.md

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -63,8 +63,9 @@ After an L1 gather, `reclassify_evidence_need_after_gather` may flip to
6363
Prefer `GatheredEvidence.tool_results` from the gather loop; fall back to
6464
`split`/`partition` on the rendered `Tool:`/`Result:` observation (see
6565
`gather_observation.iter_tool_result_blocks`) — never regex or phrase lists.
66-
HogQL / empty-result failures and ordinary values that mention auth words stay
67-
L1 (no CTA).
66+
HogQL / empty-result failures stay L1 (no L0 CTA). A metric gather that never
67+
ran a live query still gets a draft HogQL/PromQL block and one
68+
`/integrations setup …` line, then stops (`turns/metric_query_floor.py`).
6869

6970
**No keyword intent routing around the action agent.** Do not scan user text
7071
with regex/keywords to skip gather, attach goals, or bypass `execute_actions`.

core/agent_harness/prompts/assistant/observation.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99
_HANDOFF_GUIDANCE_PREFIXES: tuple[str, ...] = (
1010
"evidence_tier:L0_degraded:config",
1111
"evidence_tier:L0_degraded",
12+
"evidence_tier:metric_unformed",
1213
"session_goal:",
1314
"database_query:",
1415
"incident_description:",

core/agent_harness/prompts/assistant/text.py

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -96,6 +96,20 @@
9696
"and do not open an onboarding wizard unprompted. A bare user yes after "
9797
"that CTA will run the connect slash; do not invent a second Want-me-to.\n\n"
9898
),
99+
"evidence_tier:metric_unformed": (
100+
"Gather ran but did not execute a live metric query (schema/list probes "
101+
"only, unknown event, or the query could not be formed). Do not invent "
102+
"a count.\n"
103+
"Structure the reply like this:\n"
104+
"1. One plain sentence: the live query could not be formed and why.\n"
105+
"2. A short draft query in a fenced code block, labeled as a draft — "
106+
"HogQL for PostHog / product analytics, PromQL for Grafana / metrics. "
107+
"Never invent metric numbers as fact.\n"
108+
"3. Exactly one setup line using a valid `/integrations setup <id>` "
109+
"(preferred source id from this session). Do not invent a vendor.\n"
110+
"Do NOT offer a full incident investigation. Do NOT close with "
111+
"**Want me to:**. Stop after this reply.\n\n"
112+
),
99113
# Connected preferred source failed auth/config after gather.
100114
# Prefix: ``evidence_tier:L0_degraded:config:<ids>`` (matched before plain L0).
101115
"evidence_tier:L0_degraded:config": (

core/agent_harness/prompts/assistant/turn.py

Lines changed: 7 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -103,9 +103,12 @@ def _build_integration_guard(ctx: TurnSnapshot) -> str:
103103
already reaches the gather prompt; the answer path was told only when the
104104
set was empty, so a reply could not say what it had looked at.
105105
106-
Also pins valid ``/integrations setup`` ids and preferred product-analytics
107-
coverage so the model cannot invent vendors (parity S5: Mixpanel CTA while
108-
PostHog was already ready).
106+
When something is connected, the valid ``/integrations setup`` ids and
107+
preferred product-analytics coverage are named too, so a reply cannot
108+
recommend an invented vendor over a ready one (parity S5: Mixpanel CTA
109+
while PostHog was already connected). An empty session gets neither: the
110+
id roster is several hundred characters of vendor names, and on a session
111+
with nothing connected it crowds out the user's actual question.
109112
"""
110113
from platform.harness_ports import (
111114
preferred_evidence_sources_for,
@@ -132,7 +135,7 @@ def _build_integration_guard(ctx: TurnSnapshot) -> str:
132135
"integrations, answer with guidance only."
133136
)
134137

135-
setupable = tuple(setupable_integration_services())
138+
setupable = tuple(setupable_integration_services()) if connected else ()
136139
if setupable:
137140
parts.append(
138141
"Only these service ids are valid in `/integrations setup <id>`: "

core/agent_harness/session_goal/evaluate.py

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,9 @@
2121
gather) and the reply is non-empty → achieve (same-turn answer). Waiting for
2222
a scrubbed/forgotten tag forced a redundant outer turn that repeated the
2323
live answer (parity S1 R7). Gather successes count: metric handoffs often
24-
leave action ``executed_success_count`` at 0.
24+
leave action ``executed_success_count`` at 0. Final-route identity alone
25+
(``cli_agent_fallback`` / summarize) is not evidence — unsupported fallbacks
26+
must not close the goal.
2527
* ``achieved`` with no checklist on a handoff goal → require tool evidence, or
2628
stay active.
2729
* Hosts may wrap :func:`evaluate_session_goal` with an LLM confirm for the
@@ -255,6 +257,8 @@ def evaluate_session_goal(
255257
# wait for session_goal:achieved — that tag is scrubbed from the
256258
# user-visible reply and models often omit it, which previously
257259
# forced a second outer turn that repeated the same metric answer.
260+
# Require real action/gather successes — route identity alone would
261+
# let unsupported fallbacks close the goal without evidence.
258262
verdict = SessionGoalVerdict(
259263
status=SessionGoalStatus.ACHIEVED,
260264
reason=SessionGoalReason.ACHIEVED_TOOL_EVIDENCE,

core/agent_harness/session_goal/run_until.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -245,6 +245,7 @@ def run_until_session_goal(
245245

246246
if active.turns_used == 0:
247247
active = active.record_turn()
248+
attach_session_goal(session, active)
248249

249250
active, last, stop = _finish_outer_turn(
250251
session,
@@ -272,6 +273,7 @@ def run_until_session_goal(
272273
_announce_working(session, active, on_progress)
273274
last = chat(continuation_nudge(active))
274275
active = active.record_turn()
276+
attach_session_goal(session, active)
275277
active, last, stop = _finish_outer_turn(
276278
session,
277279
active,

core/agent_harness/turns/answer_finalize.py

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,7 @@
3535
is_non_investigation_handoff,
3636
is_prior_investigation_follow_up_handoff,
3737
)
38+
from core.agent_harness.turns.metric_query_floor import apply_unformed_metric_floor
3839
from core.agent_harness.turns.turn_route import RouteIntent
3940
from platform.harness_ports import integration_setup_command
4041

@@ -148,6 +149,14 @@ def finalize_routed_answer(
148149
text = response_text
149150
if should_suppress_investigation_offer(evidence_need) or should_skip_gather(evidence_need):
150151
text = append_upgrade_cta(session, text, evidence_need)
152+
# Metric gather that never ran a live query still gets a draft HogQL/PromQL
153+
# block and one setup slash (parity S2). No-op when a query already ran.
154+
text = apply_unformed_metric_floor(
155+
text,
156+
evidence_need,
157+
observation=evidence_for_offer,
158+
setup_command_for=integration_setup_command,
159+
)
151160
# Bookkeeping only — never feed this into route selection.
152161
text_changed_after_streaming = text != streamed_text
153162

core/agent_harness/turns/evidence_need.py

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -342,8 +342,10 @@ def reclassify_evidence_need_after_gather(
342342
(``available: False`` + ``error``). Prefer ``GatheredEvidence.tool_results``
343343
(or the ``tool_results`` kwarg); fall back to ``split``/``partition`` on the
344344
observation string — never phrase lists over prose.
345-
HogQL / empty-result failures stay L1 (honest answer, no UpgradeCTA).
346-
Missing-source L0 is decided before gather and is left unchanged.
345+
HogQL / empty-result failures stay L1 (honest answer). A metric gather that
346+
never executed a live query still gets a draft HogQL/PromQL block and one
347+
setup slash without flipping this tier. Missing-source L0 is decided before
348+
gather and is left unchanged.
347349
"""
348350
if need.tier != EvidenceTier.L1 or not need.required_for_authoritative or not need.connected:
349351
return need

0 commit comments

Comments
 (0)