Repository navigation
Add the gecko-triage agent for Gecko bug triage - #7239
alastor0325 wants to merge 28 commits into
Conversation
6e70c78 to
80c98ff
Compare
There was a problem hiding this comment.
I had a quick, relatively to the PR size ;) and I have some generic comments.
@padenot will take a look as well.
80c98ff to
606b41c
Compare
|
I submitted another PR to start intergrating some of my skills into |
15e6da1 to
b938e1b
Compare
|
I had a short discussion with Paul today. I’m thinking about which parts could become shared tools or move in-tree to benefit other tools, as I’m also working on another PR to let the |
b9c6364 to
11d7d58
Compare
|
I rebased this PR on the newest
|
11d7d58 to
4310a0f
Compare
Notes for mozilla/bugbug#7239: where the two differ in scope, team configuration, enforcement, and security model, plus open questions.
Notes for mozilla/bugbug#7239: where the two differ in scope, team configuration, enforcement, and security model, plus open questions.
Notes for mozilla/bugbug#7239: where the two differ in scope, team configuration, enforcement, and security model, plus open questions.
Notes for mozilla/bugbug#7239: where the two differ in scope, team configuration, enforcement, and security model, plus open questions.
Notes for mozilla/bugbug#7239: where the two differ in scope, team configuration, enforcement, and security model, plus open questions.
Notes for mozilla/bugbug#7239: where the two differ in scope, team configuration, enforcement, and security model, plus open questions.
Notes for mozilla/bugbug#7239: where the two differ in scope, team configuration, enforcement, and security model, plus open questions.
Notes for mozilla/bugbug#7239: where the two differ in scope, team configuration, enforcement, and security model, plus open questions.
Notes for mozilla/bugbug#7239: where the two differ in scope, team configuration, enforcement, and security model, plus open questions.
Notes for mozilla/bugbug#7239: where the two differ in scope, team configuration, enforcement, and security model, plus open questions.
Notes for mozilla/bugbug#7239: where the two differ in scope, team configuration, enforcement, and security model, plus open questions.
4310a0f to
60d5899
Compare
get_bug_history returns the change history of a bug. get_bug_comments reads a long thread in pages: start is a comment number, and the result gives the total and the next start. Each Bugzilla request runs in a worker thread, so it does not block the event loop. BugzillaContext takes an optional request timeout, and its connection pool keeps a connection for each worker thread. hackbot-runtime exports COMMENT_FOOTER_SEPARATOR. An agent's hooks use it to tell the agent's text from the runtime's trailer.
gecko-triage triages one Gecko bug per run, for one team or, with no team, with the shared rules only. Each run reads the whole bug, all comments included, and decides its status, what is missing and the next step. It reads the diagnostics reporters attach (profiles, logs, CI logs, crash reports, archives, memory reports and video frames, plus specs) through fx-bug-toolkit skills and its own tools, and it looks at the code before it asks a reporter for more. It records one comment and the field changes its rules allow. Record-time hooks limit every write, and a post-run check reads the bugs a write names. A broker sidecar holds the Bugzilla key and serves the agent-tools Bugzilla tools. A restricted bug is skipped. An independent, read-only verifier must confirm every close before the hooks accept it. A run with no team never applies by itself. A team can name docs from the Firefox tree to add to its rules. The media team is the first team.
The agent starts human-gated: auto_apply_actions is False and each run must vouch for itself. A run needs a bug id and a known team. The UI trigger form lets a person choose the team, or no team for a run with the shared rules only.
Wording only, no change of behavior. The shared prompts lose their media words, the log report asks for the decoded error codes, a source link may cite a range of lines, and the system prompt says that a later change by a person starts a new run. The link rule says what to link, and the docstrings of agent.py and targets.py are shorter and plain. The image notes say which builds webspec-index has. The agent description now reads "Each team can add its own rules" in hackbot-api, the docs and the README.
The user prompt wraps the bug's fields and comments in <bug_fields> and <bug_comments> tags. Their < is escaped, so bug text cannot close a tag, and the other parts of the user prompt have any forged tag removed. One regex lists the prompt tags, and a rule file must contain none. The main agent gives close_verifier its draft comment inside <draft_comment> tags, and the verifier prompt expects them.
Bugzilla notifies a person that a comment mentions as @nick, and sends nothing for a :nick. The rules now say so, and a request to a person with no real name opens with @nick. The name check accepts @nick at the start of a request, and refuses an @nick in backticks, which may notify nobody. A needinfo is still the way to ask for action.
The test with the RDD or GPU sandbox off is media specific, so it leaves the shared diagnostics rules and goes to the media team's. The team rule points to the steps in the in-tree Diagnostics.md, and keeps the fact that the GPU variable works only on Windows and macOS.
The media team now has Core :: Web Speech (dom/media/webspeech/) in its components, a Web Speech row in its bug types, and Web Speech in the web-api issue type.
hidden_env, which blanks the secrets in the CLI's environment, and make_undumpable, which stops other processes of the same user from reading the agent's environment, move out of agent.py into their own module. Their tests move to test_isolation.py. The behavior does not change.
The prompts are now Jinja templates, prompts/*.md.j2. The text that differs between a run with a team and a run with no team moves from parts.toml into the templates, as if blocks, and parts.toml is gone. The templates also format the keywords, the link hosts, the resolutions, the issue types and the needinfo experts, in place of helper functions in agent.py. The subagent descriptions move to prompts/descriptions.j2, and the bug investigator's Searchfox revision sentence moves into its template. A missing value fails the render. A snapshot test renders every prompt and subagent description, with and with no team, and compares them with a saved copy. The model reads the same text as before.
A pinned cargo-binstall, checked by SHA-256 for amd64 and arm64, installs webspec-index 0.14.0 from its release build and never builds it from source. It replaces the download by hand, which had a build for amd64 only, so the image now builds on arm64 too.
The image installs socorro-cli 0.6.0 with cargo-binstall, beside webspec-index. The crash rule now has the investigator run socorro-cli crash on a crash id, in place of curl on the ProcessedCrash API, and socorro-cli bugs to find the bugs filed for the same signature. A crash id needs no bp- prefix for socorro-cli.
On amd64, the image installs treeherder-cli 0.3.1 with cargo-binstall. It has no arm64 Linux build, so an arm64 image goes without it. The CI rule now names it: the failed jobs and crash stacks of a push, how often an intermittent happened before, and the pass and fail history of a test manifest. The Treeherder failures-by-bug query stays.
The media team now lists dom/media/docs/Specs.md beside Diagnostics.md in tree_docs, so the system prompt carries the spec of each media area. When the team's in-tree docs name the spec of an area, the main agent gives it to spec_checker. The configuration doc says how every team splits its knowledge: the facts in the docs it owns in the Firefox tree, and the triage decisions in its rule files.
The Bugzilla proxy controls which comments and attachments a key can read, so the agent no longer filters them by their privacy mark. render_bug keeps every comment that the read returns, and fetch_attachment no longer refuses an attachment marked private: its anonymous read fails on one it cannot get. visibility.py is gone.
render_bug, fit_comments and the JSON escaping are not specific to gecko-triage, so they move to agent_tools.bugzilla_prompt with their tests. The renderer takes the comment cap, the phrase that says where the data came from, and the test for an automation comment as arguments. gecko-triage keeps a thin render_bug that passes its cap and its bot test: the agent's own account never counts as automation, and the bot aliases do. The prompt text does not change, as the snapshot test shows.
The 223 local pilot runs from 2026-10-15 to 2026-11-05 that used the model ran with no limit. A turn is one model call in a session, and each tool result starts a new one; for the main session this count is the SDK's num_turns in every run. Each new limit is about 1.2 to 1.5 times the most turns seen, so a real investigation does not stop and a session that loops does. A session type with few sessions keeps 30. | Session | Sessions | Median | p95 | Most | Old limit | Over old limit | New limit | | ---------------- | -------: | -----: | --: | ---: | --------: | -------------: | --------: | | main | 223 | 12 | 21 | 26 | 60 | 0 | 40 | | bug_investigator | 144 | 35 | 71 | 132 | 50 | 38 | 160 | | profile_analyst | 61 | 24 | 37 | 51 | 30 | 15 | 60 | | duplicate_hunter | 199 | 6 | 14 | 18 | 16 | 3 | 24 | | close_verifier | 27 | 6 | 23 | 36 | 25 | 1 | 45 | | log_analyst | 34 | 12 | 20 | 34 | 30 | 1 | 40 | | investigator | 13 | 6 | 10 | 11 | 30 | 0 | 30 | | spec_checker | 4 | 10 | 11 | 11 | 30 | 0 | 30 | "Over old limit" is the number of sessions that used more turns than the old limit, so the old limits would have stopped them early. The cost of a run: median $1.73, p95 $4.32, most $9.60. 6 runs cost more than the old $5 limit, so max_usd goes to 12. The README said that parallel tool calls in one reply are one turn. The transcripts show that each tool result is a turn, so it now says that.
A model that reads an earlier comment of its agent can copy the runtime's feedback footer into its new text. add_comment then added the footer again, so the comment had it twice. add_comment now removes any copies at the end of the text with without_footer before it adds one. COMMENT_FOOTER and COMMENT_TRAILER are public, so an agent can find the footer in a comment.
The model copied the footer of the agent's earlier comment from the bug data in the user prompt. render_bug now leaves the footer out of each comment that has it, so the prompt has no footer to copy. The get_bug_comments tool still returns the footer; add_comment then keeps one. split_runtime_footer now uses without_footer, so the agent has one rule for where the footer starts.
profiler-cli 0.12.0 cannot read the profile format of new Firefox Profiler captures, so the agent could not read new profiles. 0.13.0 reads them. It also needs Node 24, which the image already copies from the official Node image.
The run-level inputs that every agent shares, such as require_review, now come from AgentInputs (mozilla#6730), as for the other agents.
gecko-triage is human-gated like the other agents that the test already covers, so the applier test needs no gecko-triage line.
Only gecko-triage uses render_bug and the comment trimmer, so bugzilla_prompt.py and its tests move back to the agent. A later PR can extract them when another agent needs them. The read_from parameter, which only this agent passed, becomes the fixed text.
The runtime footer goes away once the Bugzilla integration is ready, so the agent no longer depends on the runtime's footer code, and hackbot-runtime is as on master. The comment rules now say that Hackbot adds its own feedback line, so the model copies none. render_bug no longer filters the footer out of the bug's comments. The record-time checks keep their own copy of the line that comes before the footer.
…riage broker agent-tools drops its worker threads, connection pool and request timeout: an async-native Bugzilla client replaces bugsy there soon. The broker keeps the same behavior on its own. It mounts the 60-second timeout on the bugsy session that it makes, and it wraps each agent-tools Bugzilla tool so that each call runs in a worker thread. So one slow read does not hold the reads of the other subagents, in one process.
…mes none With no team input, the run reads the bug's product and component through the broker first, and takes the one team whose team.toml lists that component. No such team, or more than one, gives a run with the shared rules only, as before. A team input still wins. config reads TEAM at import, so __main__ is now a short entry point that sets TEAM before it imports the agent, and the run itself is in run.py.
The agent picks its team from the bug's component, so the form needs no team field and no gecko-triage code beyond its entry in the agent list.
The sessions that analyze the bug (bug_investigator, profile_analyst, log_analyst and close_verifier) keep high. The main session, which plans and drafts, and the search sessions (investigator, spec_checker, duplicate_hunter) use medium. The new [effort] table sits beside [turns] in shared/limits.toml, so one file holds every per-session setting. An EFFORT run input still overrides the main session only.
eb02618 to
e8996ef
Compare
|
Thanks for the review. I rebased the PR on the current master. The changes for your
One more change, not from the review: |
Background
The Firefox media team triages its new bugs with the
/triageskill of fx-bug-toolkit. fx-bug-toolkit is a Claude Code plugin. The media team has used this skill for weekly triage for a while.For each bug, the skill does these steps:
This PR brings that workflow into Hackbot as the
gecko-triageagent. The agent does not call/triage. Its own rules (rules/andteams/<team>/rules/) and its code now hold the triage steps. The agent still uses four helper skills of fx-bug-toolkit, at version 0.7.0, in their Hackbot mode:analyze-profilereads Firefox Profiler links.check-logreads media logs.spec-checkchecks web spec behavior.bug-startinvestigates the cause in the code.The agent records its comment and its field changes as actions.
We've been runing the agent in this PR locally every day in the past week o media bugs, and a person reviewed each result before it was posted to refine the PR to current shape. The gecko-triage dashboard shows these results.
One agent can triage for each Gecko team. A team adds its components and its rules in
teams/<team>/. The media team is the first team. A run with no team uses only the shared rules, in the component of the bug.Architecture
Each box names its main file. The table below lists all the files for each box. The agent's files are in
agents/gecko-triage/hackbot_agents/gecko_triage/.flowchart LR ui[Hackbot UI or API<br/>bug_id, team] api[hackbot-api<br/>app/agents.py] subgraph job[Cloud Run job] direction TB pre[Preflight<br/>preflight.py] agent[gecko-triage agent<br/>agent.py] subs[Subagents<br/>agent.py, dupes.py, toolkit.py] hooks[Record-time hooks<br/>hooks.py, closing.py] post[Post-run checks<br/>targets.py] broker[Broker sidecar<br/>broker.py] pre --> agent agent --> subs agent -- comment and field changes --> hooks hooks --> post agent -. reads .-> broker subs -. reads .-> broker end bmo[(Bugzilla)] person{{A person approves<br/>in the Hackbot UI}} ui --> api --> job broker -. read-only key .-> bmo post -- recorded actions --> api api --> person -- apply --> bmoservices/hackbot-api/app/agents.py,app/schemas.pyauto_apply_actions=False, and checks the run inputsbug_idandteam.preflight.py,state.py__main__.py,agent.py,rules/,teams/<team>/,config.py,teamconfig.pyagent.py(themake_*functions),dupes.py,toolkit.py,attachments.pytoolkit.pyloads the pinned fx-bug-toolkit skills.hooks.py,closing.py,authors.pytargets.pybroker.py,visibility.pyauto_apply_actions=False.close_verifier. This subagent starts with a fresh context, uses only read tools, and tries to prove the close wrong.Workflow
Each run triages one bug.
rules/workflow.mdgives the steps.profile_analystreads Firefox Profiler links.log_analystreads media logs.spec_checkerchecks web spec behavior.bug_investigatorreads the code.issue_type).duplicate_huntersearches for duplicates. Code runs some of the searches, and the subagent runs a concept search. The agent then links the correct meta bug.close_verifiermust confirm the close.Decisions
needs_infoanalyzedduplicateclose_verifierconfirms it.invalidclose_verifierconfirms it. Never when the reported input can cause a failure.incompleteclose_verifierconfirms it. The reporter did not answer a needinfo for 14 days.wrong_componentstalledstalledkeyword. The a cause. The bug stays open.watchingskipped