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
2 changes: 1 addition & 1 deletion pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -36,4 +36,4 @@ python_version = "3.12"
strict = true

[tool.uv.sources]
agent-core = { git = "https://github.com/AS215932/agent-core", tag = "v0.4.0" }
agent-core = { git = "https://github.com/AS215932/agent-core", tag = "v0.5.0" }
27 changes: 26 additions & 1 deletion src/hyrule_engineering_loop/agent_core_trace.py
Original file line number Diff line number Diff line change
Expand Up @@ -54,11 +54,36 @@ def emit_loop_trace(state: Mapping[str, Any]) -> int:
adapter = importlib.import_module("agent_core.adapters.engineering_loop")
sink = _sink_from_env()
run_id = state.get("change_id")
events = adapter.trace_events_from_loop_trace(dict(state), run_id=run_id)
events = adapter.trace_events_from_loop_trace(_trace_payload(state), run_id=run_id)
Comment thread
Svaag marked this conversation as resolved.
count = 0
for event in events:
if sink.emit(event):
count += 1
return count
except Exception: # best-effort: emission must never break the loop
return 0


def emit_published_trace(state: Mapping[str, Any], pr_results: list[dict[str, Any]]) -> int:
"""Re-emit trace after PR publication adds GitHub URL/commit metadata."""
if not pr_results:
return 0
return emit_loop_trace({**dict(state), "pr_status": "pushed", "pr_results": pr_results})


def _trace_payload(state: Mapping[str, Any]) -> dict[str, Any]:
payload = dict(state)
pr_results = state.get("pr_results")
if not isinstance(pr_results, list) or not pr_results:
return payload
first = pr_results[0]
if not isinstance(first, Mapping):
return payload
github_pr = first.get("github_pr")
if isinstance(github_pr, Mapping) and github_pr.get("url") and not payload.get("pr_url"):
payload["pr_url"] = github_pr.get("url")
if first.get("commit") and not payload.get("commit_sha"):
payload["commit_sha"] = first.get("commit")
if first.get("repo") and not payload.get("repository"):
payload["repository"] = first.get("repo")
return payload
2 changes: 2 additions & 0 deletions src/hyrule_engineering_loop/cli.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@
run_feature_dry_live,
run_feature_intake,
)
from hyrule_engineering_loop.agent_core_trace import emit_published_trace
from hyrule_engineering_loop.graph import build_graph
from hyrule_engineering_loop.knowledge_context import KnowledgeContextConfig
from hyrule_engineering_loop.intake import (
Expand Down Expand Up @@ -292,6 +293,7 @@ def pr_command(args: argparse.Namespace) -> int:
state["pr_labels"] = args.label
state["pr_reviewers"] = args.reviewer
state["pr_create_github"] = args.create_github_pr
emit_published_trace(state, pr_results)
_write_state(path, state)
print(f"[CLI] published {len(pr_results)} promoted worktree(s)")
return 0
Expand Down
2 changes: 2 additions & 0 deletions src/hyrule_engineering_loop/daemon.py
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@
from pathlib import Path
from typing import Any, Callable, TypeAlias

from hyrule_engineering_loop.agent_core_trace import emit_published_trace
from hyrule_engineering_loop.feature import run_feature_intake
from hyrule_engineering_loop.knowledge_context import KnowledgeContextConfig
from hyrule_engineering_loop.lhp import LhpClientConfig, fetch_lhp_payload, parse_lhp_pointer, post_lhp_update, render_lhp_request
Expand Down Expand Up @@ -526,6 +527,7 @@ def daemon_once(
pr_reviewers=[],
create_github_pr=True,
)
emit_published_trace(publish_state, pr_results)
report.outcome = "published"
github = pr_results[0].get("github_pr", {}) if pr_results else {}
report.pr_url = github.get("url") if isinstance(github, dict) else None
Expand Down
2 changes: 2 additions & 0 deletions src/hyrule_engineering_loop/operator_harness.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@

from langgraph.checkpoint.memory import MemorySaver

from hyrule_engineering_loop.agent_core_trace import emit_published_trace
from hyrule_engineering_loop.graph import build_graph
from hyrule_engineering_loop.nodes import ALL_ROLES
from hyrule_engineering_loop.pr import publish_promoted_worktrees
Expand Down Expand Up @@ -143,6 +144,7 @@ def run_operator_dry_run(
"pr_results": pr_results,
"pr_create_github": True,
}
emit_published_trace(published_state, pr_results)
_write_state(state_path, published_state)

first_result = pr_results[0]
Expand Down
60 changes: 55 additions & 5 deletions tests/test_agent_core_trace.py
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,21 @@ def log_message(self, _format: str, *args: object) -> None:
def _state() -> dict[str, object]:
return {
"change_id": "chg-test-1",
"pr_results": [
{
"repo": "network-operations",
"commit": "abc123",
"github_pr": {"created": True, "url": "https://github.com/AS215932/network-operations/pull/318"},
}
],
"workflow_run_id": "28392093138",
"trace_events": [
{
"node": "hydrate_context",
"timestamp": "2026-06-29T00:00:00Z",
"output": {"status": "passed"},
}
],
"llm_outputs": [
{
"role": "security_auditor",
Expand Down Expand Up @@ -73,16 +88,49 @@ def test_emits_when_enabled(monkeypatch, tmp_path):
monkeypatch.setenv("HYRULE_ENGINEERING_AGENT_CORE_TRACE", "1")
monkeypatch.setenv("HYRULE_ENGINEERING_AGENT_CORE_TRACE_PATH", str(sink))
count = agent_core_trace.emit_loop_trace(_state())
assert count == 3
assert count == 4
lines = sink.read_text(encoding="utf-8").strip().splitlines()
assert len(lines) == 3
assert len(lines) == 4
kinds = {json.loads(line)["event_type"] for line in lines}
assert kinds == {"model_call", "tool_call", "backend_execution"}
assert kinds == {"model_call", "tool_call", "backend_execution", "loop_node"}
backend = next(
json.loads(line) for line in lines if json.loads(line)["event_type"] == "backend_execution"
)
assert backend["cost"]["input_tokens"] == 100
assert backend["run_id"] == "chg-test-1"
assert backend["change_id"] == "chg-test-1"
assert backend["pr_number"] == 318
assert backend["commit_sha"] == "abc123"
assert backend["workflow_run_id"] == "28392093138"
loop_node = next(json.loads(line) for line in lines if json.loads(line)["event_type"] == "loop_node")
assert loop_node["parent_event_id"]


def test_emit_published_trace_reemits_pr_correlation(monkeypatch, tmp_path):
sink = tmp_path / "published.jsonl"
state = _state()
state.pop("pr_results")
monkeypatch.setenv("HYRULE_ENGINEERING_AGENT_CORE_TRACE", "1")
monkeypatch.setenv("HYRULE_ENGINEERING_AGENT_CORE_TRACE_PATH", str(sink))

count = agent_core_trace.emit_published_trace(
state,
[
{
"repo": "network-operations",
"commit": "publishedabc",
"github_pr": {
"created": True,
"url": "https://github.com/AS215932/network-operations/pull/319",
},
}
],
)

assert count == 4
records = [json.loads(line) for line in sink.read_text(encoding="utf-8").splitlines()]
assert {record["pr_number"] for record in records} == {319}
assert {record["commit_sha"] for record in records} == {"publishedabc"}


def test_emits_to_collector_and_file_when_collector_url_is_set(monkeypatch, tmp_path):
Expand All @@ -93,10 +141,12 @@ def test_emits_to_collector_and_file_when_collector_url_is_set(monkeypatch, tmp_
monkeypatch.setenv("HYRULE_ENGINEERING_AGENT_CORE_TRACE_COLLECTOR_URL", url)
count = agent_core_trace.emit_loop_trace(_state())

assert count == 3
assert len(sink.read_text(encoding="utf-8").strip().splitlines()) == 3
assert count == 4
assert len(sink.read_text(encoding="utf-8").strip().splitlines()) == 4
assert [event["event_type"] for event in received] == [
"model_call",
"tool_call",
"backend_execution",
"loop_node",
]
assert received[-1]["change_id"] == "chg-test-1"
6 changes: 3 additions & 3 deletions uv.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.