Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
251 changes: 125 additions & 126 deletions dist/app.py

Large diffs are not rendered by default.

251 changes: 125 additions & 126 deletions dist/apps/code-review.py

Large diffs are not rendered by default.

251 changes: 125 additions & 126 deletions dist/apps/incident-management.py

Large diffs are not rendered by default.

17 changes: 11 additions & 6 deletions sonar-project.properties
Original file line number Diff line number Diff line change
Expand Up @@ -14,12 +14,17 @@ sonar.python.version=3.11
sonar.exclusions=dist/**,scripts/**,docs/**,.venv/**,incidents/**
sonar.test.exclusions=tests/fixtures/**

# Copy-Paste Detection exclusions. ``runtime/tools/gateway.py`` carries
# a deliberate sync (``_run``) + async (``_arun``) mirror — every
# ``BaseTool`` must support both invocation styles. The sibling blocks
# look like duplication to CPD but are an architectural requirement,
# not drift; modifying them in tandem is the project convention.
sonar.cpd.exclusions=src/runtime/tools/gateway.py
# Copy-Paste Detection exclusions.
# * ``runtime/tools/gateway.py`` — deliberate sync (``_run``) + async
# (``_arun``) mirror; every ``BaseTool`` must support both
# invocation styles. The sibling blocks look like duplication to
# CPD but are an architectural requirement, not drift; modifying
# them in tandem is the project convention.
# * ``runtime/agents/responsive.py`` — the "responsive" agent-kind
# factory mirrors ``runtime/graph.py:make_agent_node`` by design:
# ``_build_agent_nodes`` dispatches to either factory based on
# ``skill.kind``. Same pattern as gateway above.
sonar.cpd.exclusions=src/runtime/tools/gateway.py,src/runtime/agents/responsive.py

# Coverage exclusions — UI is excluded because Streamlit rendering is exercised
# manually in a browser, not by the unit-test suite. Coverage gates apply to
Expand Down
30 changes: 15 additions & 15 deletions src/runtime/agents/responsive.py
Original file line number Diff line number Diff line change
Expand Up @@ -103,9 +103,9 @@ async def node(state: GraphState) -> dict:
# the same reload comment in ``runtime.graph.make_agent_node``
# for the full rationale.
try:
incident: Session = store.load(inc_id)
session: Session = store.load(inc_id)
except FileNotFoundError:
incident = state_session
session = state_session

# M3: emit agent_started telemetry before any work happens.
if event_log is not None:
Expand All @@ -123,7 +123,7 @@ async def node(state: GraphState) -> dict:
# live ``Session`` for this run.
if gateway_cfg is not None:
run_tools = [
wrap_tool(t, session=incident, gateway_cfg=gateway_cfg,
wrap_tool(t, session=session, gateway_cfg=gateway_cfg,
agent_name=skill.name, store=store,
gate_policy=gate_policy,
event_log=event_log)
Expand All @@ -147,15 +147,15 @@ async def node(state: GraphState) -> dict:
checkpointer=checkpointer,
)
inner_thread_id = (
f"{inc_id}:agent:{skill.name}:turn{len(incident.agents_run)}"
f"{inc_id}:agent:{skill.name}:turn{len(session.agents_run)}"
)
inner_cfg = {"configurable": {"thread_id": inner_thread_id}}

# Phase 11 (FOC-04): reset per-turn confidence hint at the
# start of each agent step so the gateway treats the first
# tool call of the turn as "no signal yet".
try:
incident.turn_confidence_hint = None
session.turn_confidence_hint = None
except (AttributeError, ValueError):
pass

Expand All @@ -166,7 +166,7 @@ async def node(state: GraphState) -> dict:
inner_has_checkpointer=checkpointer is not None,
initial_input={
"messages": [
HumanMessage(content=_format_agent_input(incident))
HumanMessage(content=_format_agent_input(session))
]
},
)
Expand All @@ -176,30 +176,30 @@ async def node(state: GraphState) -> dict:
except Exception as exc: # noqa: BLE001
return _handle_agent_failure(
skill_name=skill.name, started_at=started_at, exc=exc,
inc_id=inc_id, store=store, fallback=incident,
inc_id=inc_id, store=store, fallback=session,
)

# Tools (e.g. registered patch tools) write straight to disk.
# Reload so the node's own append of agent_run + tool_calls
# happens against the tool-mutated state.
incident = store.load(inc_id)
session = store.load(inc_id)

messages = result.get("messages", [])
ts = datetime.now(timezone.utc).strftime(_UTC_TS_FMT)

agent_confidence, agent_rationale, agent_signal = _harvest_tool_calls_and_patches(
messages, skill.name, incident, ts, valid_signals,
messages, skill.name, session, ts, valid_signals,
terminal_tool_names=terminal_tool_names,
patch_tool_names=patch_tool_names,
)
# Phase 11 (FOC-04): update hint so any subsequent in-turn
# tool call sees the harvested confidence.
if agent_confidence is not None:
try:
incident.turn_confidence_hint = agent_confidence
session.turn_confidence_hint = agent_confidence
except (AttributeError, ValueError):
pass
_pair_tool_responses(messages, incident)
_pair_tool_responses(messages, session)

# Phase 10 (FOC-03 / D-10-03): parse envelope; reconcile against
# any typed-terminal-tool-arg confidence. Envelope failure is a
Expand All @@ -209,7 +209,7 @@ async def node(state: GraphState) -> dict:
except EnvelopeMissingError as exc:
return _handle_agent_failure(
skill_name=skill.name, started_at=started_at, exc=exc,
inc_id=inc_id, store=store, fallback=incident,
inc_id=inc_id, store=store, fallback=session,
)

terminal_tool_for_log = _first_terminal_tool_called_this_turn(
Expand Down Expand Up @@ -245,13 +245,13 @@ async def node(state: GraphState) -> dict:
)

_record_success_run(
incident=incident, skill_name=skill.name, started_at=started_at,
session=session, skill_name=skill.name, started_at=started_at,
final_text=final_text, usage=usage,
confidence=final_confidence, rationale=final_rationale,
signal=final_signal,
store=store,
)
next_route_signal = decide_route(incident)
next_route_signal = decide_route(session)
next_node = route_from_skill(skill, next_route_signal)

# M3: emit route_decided + agent_finished. agent_finished carries
Expand Down Expand Up @@ -282,7 +282,7 @@ async def node(state: GraphState) -> dict:
"event_log.record(agent_finished) failed", exc_info=True,
)

return {"session": incident, "next_route": next_node,
return {"session": session, "next_route": next_node,
"last_agent": skill.name, "error": None}

return node
Expand Down
16 changes: 8 additions & 8 deletions src/runtime/agents/supervisor.py
Original file line number Diff line number Diff line change
Expand Up @@ -57,7 +57,7 @@ def _safe_eval(expr: str, ctx: dict[str, Any]) -> Any:
return eval(code, {"__builtins__": {}}, ctx) # noqa: S307 — AST-whitelisted


def _ctx_for_session(incident: Session) -> dict[str, Any]:
def _ctx_for_session(session: Session) -> dict[str, Any]:
"""Build the variable namespace dispatch-rule expressions see.

Exposes the live session payload as ``session`` plus a few
Expand All @@ -66,7 +66,7 @@ def _ctx_for_session(incident: Session) -> dict[str, Any]:
AST checker already restricts the language so we don't need to
sandbox the namespace any further.
"""
payload = incident.model_dump()
payload = session.model_dump()
return {
"session": payload,
"status": payload.get("status"),
Expand Down Expand Up @@ -112,7 +112,7 @@ def _llm_pick_target(
*,
skill: Skill,
llm: BaseChatModel,
incident: Session,
session: Session,
) -> str:
"""One-shot LLM dispatch: ask the model to choose a subordinate.

Expand All @@ -127,7 +127,7 @@ def _llm_pick_target(
f"Choose ONE of: {', '.join(skill.subordinates)}.\n"
f"Reply with only the agent name."
)
payload = json.dumps(incident.model_dump(), default=str)
payload = json.dumps(session.model_dump(), default=str)
msgs = [
SystemMessage(content=prompt),
HumanMessage(content=payload),
Expand All @@ -154,15 +154,15 @@ def _llm_pick_target(
def _rule_pick_target(
*,
skill: Skill,
incident: Session,
session: Session,
) -> tuple[str, str | None]:
"""Walk dispatch_rules in order; return (target, matched_when).

Falls back to the first subordinate when no rule matches; the
fallback case carries ``matched_when=None`` so the audit log can
distinguish "default" from "rule X matched".
"""
ctx = _ctx_for_session(incident)
ctx = _ctx_for_session(session)
for rule in skill.dispatch_rules:
try:
if bool(_safe_eval(rule.when, ctx)):
Expand Down Expand Up @@ -337,7 +337,7 @@ async def node(state: GraphState) -> dict:

rule_matched: str | None = None
if skill.dispatch_strategy == "rule":
target, rule_matched = _rule_pick_target(skill=skill, incident=sess)
target, rule_matched = _rule_pick_target(skill=skill, session=sess)
else: # "llm"
if llm is None:
logger.warning(
Expand All @@ -346,7 +346,7 @@ async def node(state: GraphState) -> dict:
)
target = skill.subordinates[0]
else:
target = _llm_pick_target(skill=skill, llm=llm, incident=sess)
target = _llm_pick_target(skill=skill, llm=llm, session=sess)

# Audit: one structured log entry per dispatch.
try:
Expand Down
2 changes: 1 addition & 1 deletion src/runtime/config.py
Original file line number Diff line number Diff line change
Expand Up @@ -134,7 +134,7 @@ class MCPConfig(BaseModel):


class MetadataConfig(BaseModel):
"""Relational store for incident metadata. SQLite (dev) or Postgres (prod)."""
"""Relational store for session metadata. SQLite (dev) or Postgres (prod)."""
url: str = "sqlite:///incidents/incidents.db"
pool_size: int = 5 # postgres only; sqlite uses NullPool
echo: bool = False
Expand Down
8 changes: 4 additions & 4 deletions src/runtime/dedup.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
structured output {is_duplicate, confidence, rationale}.

The pipeline is **framework-level** and never imports the
incident-management state class (R4 in the Phase-7 plan). Apps inject
domain-specific Session subclass (R4 in the Phase-7 plan). Apps inject
domain-specific text via a ``text_extractor: Callable[[Session], str]``
callable.

Expand Down Expand Up @@ -61,7 +61,7 @@ class DedupConfig(BaseModel):
All numeric thresholds are inclusive at the lower bound (``>=``),
so a candidate hitting exactly ``stage1_threshold`` is considered.

Defaults are tuned for the incident-management example. Apps that
Defaults are tuned for the example app. Apps that
want different policies override via YAML.
"""

Expand Down Expand Up @@ -93,7 +93,7 @@ def assert_model_exists(self, llm_cfg: "LLMConfig") -> None:
"""Fail fast if ``stage2_model`` is missing from the LLM registry.

Called at orchestrator boot when dedup is enabled. Raising here
is preferred over discovering the typo on the first incident.
is preferred over discovering the typo on the first session.
"""
if self.stage2_model not in llm_cfg.models:
raise ValueError(
Expand Down Expand Up @@ -342,7 +342,7 @@ def _stage1(
if env:
filter_kwargs["environment"] = env
# ``status_filter`` is the resolved session bucket — only_closed
# maps to "resolved" in the incident-management vocabulary.
# maps to "resolved" in the example app vocabulary.
# Apps that disable only_closed get all statuses other than
# in-flight via the empty filter (HistoryStore default behaviour
# already screens deleted rows).
Expand Down
Loading
Loading