Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
21 commits
Select commit Hold shift + click to select a range
e76da6a
Simplify hackbot agent structure into self-contained agents/<name>/ f…
suhaibmujahid Jun 9, 2026
3a1079d
Remove unused load_config from bug-fix agent config
suhaibmujahid Jun 9, 2026
4f72865
Add framework-neutral agent-tools package with unified @tool declarat…
suhaibmujahid Jun 9, 2026
101c290
Drop the duplicate_bugs tool
suhaibmujahid Jun 9, 2026
2318676
Avoid copying per-member pyproject files into build contexts
suhaibmujahid Jun 10, 2026
61690f5
Drop the thin agent_runner wrapper and let agent be the single package
suhaibmujahid Jun 10, 2026
f37de1f
Move source-checkout helper into hackbot-runtime
suhaibmujahid Jun 10, 2026
c676bba
Bind-mount the workspace instead of COPY . . in uv builder images
suhaibmujahid Jun 10, 2026
b4d9ab7
Move bug-fix agent under the hackbot_agents PEP 420 namespace
suhaibmujahid Jun 10, 2026
36f5f1c
Remove stale git lock files before updating the source checkout
suhaibmujahid Jun 10, 2026
adfd221
Convert the bug-fix broker from a package to a single module
suhaibmujahid Jun 11, 2026
4409ab5
Declare hatchling build backend for agent-tools and hackbot-runtime
suhaibmujahid Jun 11, 2026
2816854
Add actions_to_tool_names and use in config
suhaibmujahid Jun 11, 2026
bc98278
Move ACTIONS_SERVER_NAME into hackbot-runtime
suhaibmujahid Jun 11, 2026
d86661e
Improve hackbot-runtime
suhaibmujahid Jun 11, 2026
040d895
Drop the run_local.py convenience script
suhaibmujahid Jun 11, 2026
d5054f9
Cleanup comment separators and reflow comments
suhaibmujahid Jun 11, 2026
6761966
Return a HackbotAgentResult model from agent main()
suhaibmujahid Jun 11, 2026
5a08a06
Rewrite agents/README.md as a friendlier onboarding guide
suhaibmujahid Jun 11, 2026
2cff913
Remove Anthropic credentials precheck
suhaibmujahid Jun 11, 2026
5e99440
Move agent log path and upload to hackbot-runtime
suhaibmujahid Jun 11, 2026
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
110 changes: 110 additions & 0 deletions agents/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,110 @@
# Hackbot Agents

Each subdirectory is a single, self-contained agent — its logic, its entrypoint,
and its deployment all live together, so you can understand one agent without
hunting around the repo.

New here? The best way to start is to read through **`bug-fix/`** — it's our reference
agent, and the fastest path to your own is to copy it and adapt.

## How an agent works (the important part)

When the platform runs your agent, it calls `python -m hackbot_agents.<name>`, which runs
your `__main__.py`. Your job is to fill in three small pieces:

```python
class AgentInputs(BaseSettings): # per-run inputs, read from env (bug_id -> BUG_ID)
bug_id: int

async def main(ctx: HackbotContext) -> BugFixResult:
inputs = AgentInputs()
return await run_bug_fix(bug=inputs.bug_id, ...) # your real logic lives in agent.py

run_async(main) # finds hackbot.toml, runs main, exits the process
```

Three rules are worth remembering:

- **To report success,** return a `HackbotAgentResult` (subclass it with your own fields).
The runtime saves it to `summary.json` under `findings`.
- **To report failure,** just raise. Use `AgentError("…")` when it's an expected, explainable
failure; let any other exception bubble up for an unexpected crash.
- **`ctx` is your window to the platform** — everything it prepared for you hangs off it:
`ctx.source_repo`, `ctx.firefox`, `ctx.anthropic.api_key`, `ctx.actions`,
`ctx.publish_file`, `ctx.publish_json`. You never wire these up yourself.

## What's in an agent folder (`agents/<name>/`)

```
agents/<name>/
pyproject.toml # the distribution "hackbot-agent-<name>" and its dependencies
hackbot.toml # what you need the platform to prepare: [source], [firefox]
Dockerfile # how it ships
compose.yml # how to run it locally
hackbot_agents/ # a shared namespace package — please don't add __init__.py here!
<name_snake>/ # your agent's package (e.g. bug_fix)
__init__.py # empty
__main__.py # AgentInputs + main(ctx) + run_async(main)
agent.py # entrypoint: your prompts, logic, and HackbotAgentResult subclass
```

One thing to watch: **never create `hackbot_agents/__init__.py`.** Leaving it out is what
lets several agents live side by side in one environment without overwriting each other (PEP 420).
It's an easy mistake to make, and a confusing one to debug.

## Telling the platform what you need (`hackbot.toml`)

Think of `hackbot.toml` as your request to the platform: "please have these ready for me."
Everything is optional — only list what you actually use.

```toml
[source] # the platform shallow-clones and refreshes this for you
repo_url = "https://github.com/mozilla-firefox/firefox.git"

[firefox] # Firefox build paths, derived from the checkout
enabled = true
objdir = "objdir-ff-asan"
```

Everything else has a natural home: your agent's name and description go in `pyproject.toml`,
model and tool choices stay in code, and secrets and per-run inputs come from the environment.

## Building blocks you can reuse

Please reach for these instead of rolling your own — they're shared on purpose.

From **`hackbot-runtime`**:

- `HackbotContext, AgentError, HackbotAgentResult, run_async` — the pieces from the contract above.
- `from hackbot_runtime.claude import Reporter` — pretty-prints the agent's streamed messages
to stdout and your log (call `reporter.header(...)` per work item, `reporter.message(msg)` per message).
- `from hackbot_runtime.actions.claude_sdk import actions_server_for` — gives you
`(recorder, mcp_server)` so write-actions get recorded into `summary.json` rather than
silently mutating the world.

Your actual **tools** (the things the model can call) come from **`agent-tools`**, each behind
its own extra (`[bugzilla]`, `[firefox]`):

```python
from agent_tools import bugzilla
from agent_tools.claude_sdk import build_sdk_server
server = build_sdk_server("bugzilla", BugzillaContext(client=...), bugzilla.TOOLS)
```

From there, you assemble your own `ClaudeAgentOptions` and drive the `ClaudeSDKClient` loop —
that part stays in your hands, where you want it.

## Creating your own agent

1. **Copy `bug-fix/`** as your starting point. Rename the folder, the distribution name in
`pyproject.toml`, and the commands in `Dockerfile`/`compose.yml` (`python -m hackbot_agents.<name>`).
2. **Trim `hackbot.toml`** to just the `[source]`/`[firefox]` tables you need.
3. **Write your two modules:** `__main__.py` (`AgentInputs` + `main`) and `agent.py` (your logic
plus a `HackbotAgentResult` subclass). Keep `<name_snake>/__init__.py` empty.
4. **Register it** in `services/hackbot-api/`: add a Pydantic input model in `app/schemas.py`,
and a single `AGENT_REGISTRY` entry in `app/agents.py` (`name`/`description`/`job_name`/
`input_schema`). Env vars are derived from your schema automatically (`bug_id` → `BUG_ID`),
so there's no `build_env` to write — put deploy-time constants like broker URLs in the Job's
static env instead.

And that's the whole recipe: one folder, one schema, one registry line. Welcome aboard!
37 changes: 17 additions & 20 deletions agents/bug-fix/Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -2,46 +2,43 @@ FROM python:3.12 AS builder

COPY --from=ghcr.io/astral-sh/uv:latest /uv /uvx /bin/

WORKDIR /app
ENV UV_PROJECT_ENVIRONMENT=/opt/venv

# Workspace metadata first so the dep-download layer caches independently
# of source changes.
COPY pyproject.toml uv.lock VERSION ./
COPY http_service/pyproject.toml ./http_service/
COPY services/hackbot-api/pyproject.toml ./services/hackbot-api/
COPY agents/bug-fix/pyproject.toml ./agents/bug-fix/
COPY libs/hackbot-runtime/pyproject.toml ./libs/hackbot-runtime/
WORKDIR /app

# Install external deps without building workspace members.
RUN --mount=type=cache,target=/root/.cache/uv \
uv sync --locked --no-dev --no-install-workspace --package hackbot-agent-bug-fix

# Workspace members the agent image actually needs (source included).
COPY agents/bug-fix ./agents/bug-fix
COPY bugbug ./bugbug
COPY libs/hackbot-runtime ./libs/hackbot-runtime
--mount=type=bind,source=pyproject.toml,target=pyproject.toml \
--mount=type=bind,source=uv.lock,target=uv.lock \
--mount=type=bind,source=VERSION,target=VERSION \
uv sync --frozen --no-dev --no-install-workspace --package hackbot-agent-bug-fix

RUN --mount=type=cache,target=/root/.cache/uv \
uv sync --locked --no-dev --package hackbot-agent-bug-fix
--mount=type=bind,target=/app,rw \
uv sync --locked --no-dev --no-editable --package hackbot-agent-bug-fix

FROM python:3.12 AS base

COPY --from=builder /app /app
WORKDIR /app/agents/bug-fix
COPY --from=builder /opt/venv /opt/venv
WORKDIR /app

ENV PYTHONUNBUFFERED=1
ENV PYTHONDONTWRITEBYTECODE=1
ENV PATH="/app/.venv/bin:$PATH"
ENV PATH="/opt/venv/bin:$PATH"

FROM base AS agent

# hackbot.toml lives at the agent root (not inside the package), so copy it into
# the working dir; the runtime discovers it there (cwd) at startup.
COPY agents/bug-fix/hackbot.toml /app/hackbot.toml

RUN useradd --create-home --shell /bin/bash agent \
&& mkdir -p /workspace \
&& chown agent:agent /workspace

USER agent

CMD ["python", "-m", "agent_runner"]
CMD ["python", "-m", "hackbot_agents.bug_fix"]

FROM base AS broker

Expand All @@ -51,4 +48,4 @@ USER broker

EXPOSE 8765

CMD ["python", "-m", "broker"]
CMD ["python", "-m", "hackbot_agents.bug_fix.broker"]
118 changes: 0 additions & 118 deletions agents/bug-fix/agent_runner/__main__.py

This file was deleted.

Empty file removed agents/bug-fix/broker/__init__.py
Empty file.
7 changes: 7 additions & 0 deletions agents/bug-fix/hackbot.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
[source]
repo_url = "https://github.com/mozilla-firefox/firefox.git"
checkout_path = "/workspace/firefox"

[firefox]
enabled = true
objdir = "objdir-ff-asan"
39 changes: 39 additions & 0 deletions agents/bug-fix/hackbot_agents/bug_fix/__main__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
from hackbot_runtime import HackbotContext, run_async
from pydantic_settings import BaseSettings, SettingsConfigDict

from .agent import BugFixResult, run_bug_fix


class AgentInputs(BaseSettings):
bug_id: int
bugzilla_mcp_url: str
model: str | None = None
max_turns: int | None = None
effort: str | None = None

model_config = SettingsConfigDict(extra="ignore")


async def main(ctx: HackbotContext) -> BugFixResult:
inputs = AgentInputs()

return await run_bug_fix(
task="Triage and fix the bug, and verify the fix",
bugzilla_mcp_server={
"type": "http",
"url": inputs.bugzilla_mcp_url,
},
source_repo=ctx.source_repo,
fx_ctx=ctx.firefox,
bug=inputs.bug_id,
model=inputs.model,
max_turns=inputs.max_turns,
effort=inputs.effort,
log=ctx.log_path,
verbose=True,
actions_recorder=ctx.actions,
)


if __name__ == "__main__":
run_async(main)
Loading