Skip to content
14 changes: 14 additions & 0 deletions libs/hackbot-runtime/hackbot_runtime/config.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,8 @@
"""

import tomllib
from functools import cached_property
from importlib import metadata
from pathlib import Path

from pydantic import BaseModel
Expand Down Expand Up @@ -42,6 +44,18 @@ class HackbotConfig(BaseModel):

source: SourceConfig | None = None
firefox: FirefoxConfig | None = None
# Set by the runtime from the entry point, not read from the toml.
agent_name: str | None = None

@cached_property
def agent_version(self) -> str | None:
"""The version in the agent's pyproject.toml, or None when it isn't installed."""
if self.agent_name is None:
return None
try:
return metadata.version(f"hackbot-agent-{self.agent_name}")
except metadata.PackageNotFoundError:
return None


def load_config(path: Path) -> HackbotConfig:
Expand Down
10 changes: 7 additions & 3 deletions libs/hackbot-runtime/hackbot_runtime/runtime.py
Original file line number Diff line number Diff line change
Expand Up @@ -118,9 +118,12 @@ def _discover_config_path(entrypoint: Callable) -> Path | None:

def _resolve_config(entrypoint: Callable, config: ConfigArg) -> HackbotConfig:
if isinstance(config, HackbotConfig):
return config
path = config if isinstance(config, Path) else _discover_config_path(entrypoint)
return load_config(path) if path else HackbotConfig()
parsed = config
else:
path = config if isinstance(config, Path) else _discover_config_path(entrypoint)
parsed = load_config(path) if path else HackbotConfig()
parsed.agent_name = resolve_agent_name(entrypoint)
return parsed


def _load_hackbot(entrypoint: Callable, config: ConfigArg) -> HackbotContext | None:
Expand Down Expand Up @@ -169,6 +172,7 @@ def _finish(ctx: HackbotContext, outcome: object) -> int:
log.error(msg)
payload = _error_payload(ctx, msg)
exit_code = 1
payload["agent_version"] = ctx.config.agent_version

try:
_publish_log(ctx)
Expand Down
16 changes: 16 additions & 0 deletions libs/hackbot-runtime/tests/test_runtime.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
AgentError,
HackbotAgentResult,
HackbotContext,
config,
run_async,
runtime,
)
Expand Down Expand Up @@ -228,6 +229,21 @@ async def main(ctx):
assert summary["findings"] == {"num_turns": 1, "total_cost_usd": None}


def test_run_async_records_agent_version(tmp_path, monkeypatch):
_run_env(tmp_path, monkeypatch)
versions = {"hackbot-agent-tests": "1.2.3"}
monkeypatch.setattr(config.metadata, "version", versions.__getitem__)

async def main(ctx):
return HackbotAgentResult(num_turns=1)

with pytest.raises(SystemExit):
run_async(main)

summary = json.loads((tmp_path / "t" / "summary.json").read_text())
assert summary["agent_version"] == "1.2.3"


def test_run_async_exits_nonzero_when_agent_raises(tmp_path, monkeypatch):
_run_env(tmp_path, monkeypatch)

Expand Down
1 change: 1 addition & 0 deletions services/hackbot-api/app/schemas.py
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ class ArtifactRef(BaseModel):
class RunSummary(BaseModel):
status: str
error: str | None = None
agent_version: str | None = None
findings: dict[str, Any] = {}
actions: list[dict[str, Any]] = []

Expand Down