Incident window tool integration#954
Conversation
Greptile SummaryThis PR wires the Confidence Score: 5/5Safe to merge — all remaining findings are P2 style/diagnostic-accuracy suggestions with no runtime impact. The implementation is clean and well-tested (14 new tests covering all priority branches, defensive paths, and backward-compat). The only finding is a P2: the app/tools/GitDeployTimelineTool/init.py — the Important Files Changed
Sequence DiagramsequenceDiagram
participant EA as extract_alert node
participant AS as AgentState
participant II as InvestigateInput
participant PA as plan_actions node
participant AVS as available_sources["_meta"]
participant EP as GitDeployTimelineTool.extract_params
participant RUN as get_git_deploy_timeline()
EA->>EA: resolve_incident_window(raw_alert)
EA->>AS: state["incident_window"] = window.to_dict()
AS->>II: InvestigateInput.from_state(state)
note over II: isinstance guard: non-dict → None
II->>PA: input_data.incident_window
alt incident_window is not None
PA->>AVS: available_sources["_meta"] = {"incident_window": ...}
else no window
note over AVS: _meta key omitted entirely
end
AVS->>EP: sources["_meta"]["incident_window"]
EP->>RUN: shared_incident_window=...
alt no since/until/window_minutes override
RUN->>RUN: IncidentWindow.from_dict(shared_incident_window)
RUN->>RUN: use shared window → source="shared_incident_window"
else caller provided overrides
RUN->>RUN: ignore shared window → source="tool_default"
end
Reviews (1): Last reviewed commit: "feat(incident-window): wire GitDeployTim..." | Re-trigger Greptile |
PR 2 of the dynamic incident-window work. Stacked on PR 1 (foundation).
What was needed:
PR 1 added state.incident_window populated by extract_alert from the
alert's own timestamps. But no tool reads it yet. This PR proves the
end-to-end pattern with one tool: GitDeployTimelineTool.
What this PR does:
app/nodes/investigate/models.py: InvestigateInput gains a typed
incident_window: dict | None. from_state populates it from
state.incident_window with a defensive isinstance check.
app/nodes/plan_actions/plan_actions.py: after detect_sources runs,
the wrapping step attaches available_sources["_meta"] = {
"incident_window": } when input_data.incident_window is set.
The "_meta" key is reserved for investigation-level context shared
across tools — today only the incident window, future PRs may add
window history. The key is OMITTED when state has no window so the
sources dict shape stays clean for tools that don't opt in.
app/tools/GitDeployTimelineTool/init.py:
"incident_window") and threads it as the shared_incident_window
kwarg. Defensive isinstance guards against non-dict _meta or
non-dict incident_window so a future upstream bug cannot crash
the tool.
parameter. window_minutes_before_alert default changed from
DEFAULT_WINDOW_MINUTES to None so the tool can distinguish
"caller didn't override" from "caller wants 120 min".
IncidentWindow.from_dict; None on bad shape -> falls
through to default)
"shared_incident_window" or "tool_default" so the diagnose
narrative can explain where the window came from.
app/nodes/plan_actions/build_prompt.py: one-line update to the
GitHub planning hint telling the agent the tool now auto-picks up
the shared incident window when no explicit args are passed.
What's not in this PR (deferred to follow-ups):
Each is a small ~50-line follow-up using the same pattern.
Tests (14 new):
Tool integration:
- extract_params reads _meta.incident_window and passes it through
- extract_params handles missing _meta key (returns None)
- extract_params defensive against non-dict _meta (returns None)
- extract_params defensive against non-dict incident_window
(returns None)
- run uses shared window when caller passes no overrides
- caller since/until overrides shared window (caller wins)
- caller window_minutes_before_alert overrides shared window
- falls back to DEFAULT_WINDOW_MINUTES when no shared window present
- falls back to default when shared window dict is malformed
Wiring contract:
- InvestigateInput surfaces incident_window from state
- InvestigateInput defaults to None when state has no window
- InvestigateInput rejects non-dict incident_window (defensive)
- _meta.incident_window attached when state has one
- _meta key omitted entirely when state has no window