Skip to content

Chapter 17 · Capstone 1: GitHub Auto-Triage Agent ​

Chapter goals: Build a complete bug auto-triage agent from scratch, using the official README's Triage example as a blueprint — connecting skills, tools, and sandbox into one end-to-end pipeline.

17.1 Goal and Blueprint ​

The agent we build autonomously completes end-to-end triage upon receiving a bug report:

text
bug report → reproduce → locate root cause → judge if expected → attempt fix → open issue report

The official example skeleton (starting point for this chapter):

typescript
// agents/triage.ts — official README original example
'use agent';
import { useModel, useSandbox, useSkill, useTool } from '@flue/runtime';
import { local } from '@flue/runtime/node';
import triage from '../skills/triage/SKILL.md';
import verify from '../skills/verify/SKILL.md';
import { openIssue, searchCode } from '../tools/github.ts';

export function Triage() {
  useModel('anthropic/claude-sonnet-4-6');
  useSandbox(local());
  useSkill(triage);
  useSkill(verify);
  useTool(openIssue);
  useTool(searchCode);
  return `Triage a bug report end-to-end: reproduce the bug,
diagnose the root cause, verify whether the behavior is
intentional, and attempt a fix.`;
}

17.2 Step One: The Tool Layer (tools/github.ts) ​

Tools are typed external actions. Start with two minimal tools:

typescript
// tools/github.ts
'use flue-tool';
import { defineTool } from '@flue/runtime';

// Search code in a repo (read-only, safe)
export const searchCode = defineTool({
  name: 'search_code',
  description: 'Search code in a GitHub repo; returns matching file paths',
  inputSchema: {
    type: 'object',
    properties: {
      repo: { type: 'string', description: 'Repo in owner/name form' },
      query: { type: 'string', description: 'Search keyword' },
    },
    required: ['repo', 'query'],
  },
  async execute({ repo, query }) {
    const url = `https://api.github.com/search/code?q=${encodeURIComponent(query)}+repo:${repo}`;
    const res = await fetch(url, {
      headers: {
        Authorization: `Bearer ${process.env.GITHUB_TOKEN}`,
        Accept: 'application/vnd.github+json',
      },
    });
    const data = (await res.json()) as { items?: Array<{ path: string }> };
    return (data.items ?? []).slice(0, 10).map((i) => i.path); // Paths only; save tokens
  },
});
typescript
// tools/github.ts (continued) — open issue (has side effects; idempotent)
let reported = new Set<string>(); // In production, swap for persistent dedup (Chapter 12)

export const openIssue = defineTool({
  name: 'open_issue',
  description: 'Create a triage-conclusion issue in the repo',
  durable: true, // Declare durable: won't re-create on recovery
  inputSchema: {
    type: 'object',
    properties: {
      title: { type: 'string' },
      body: { type: 'string', description: 'Includes reproduction steps, root cause, fix suggestion' },
      labels: { type: 'array', items: { type: 'string' } },
    },
    required: ['title', 'body'],
  },
  async execute(args) {
    const key = args.title;
    if (reported.has(key)) return { duplicated: true }; // Idempotency guard
    reported.add(key);
    // ...POST to GitHub Issues API
  },
});

17.3 Step Two: Skills (skills/triage/SKILL.md, skills/verify/SKILL.md) ​

Two skills encapsulate the domain methodology:

markdown
<!-- skills/triage/SKILL.md -->
---
name: triage
description: End-to-end bug triage methodology. Use when a bug report arrives.
---

Triage流程:
1. 复现:确认 bug 可稳定复现
2. 定位:grep 相关代码,找到根因
3. 判断:是否为预期行为(查阅文档/历史 issue)
4. 修复:尝试最小修复 PR
5. 汇报:用 open_issue 工具创建分诊 issue
markdown
<!-- skills/verify/SKILL.md -->
---
name: verify
description: Verify whether a behavior is expected. Use after triage定位.
---

Verify流程:
1. 查文档确认预期行为
2. 查历史 issue 看是否已知
3. 输出:is_expected: true/false + 依据

17.4 Step Three: Assembly and Testing ​

typescript
// agents/triage.ts — final assembly
'use agent';
import { useModel, useSandbox, useSkill, useTool } from '@flue/runtime';
import { local } from '@flue/runtime/node';
import triage from '../skills/triage/SKILL.md';
import verify from '../skills/verify/SKILL.md';
import { openIssue, searchCode } from '../tools/github.ts';

export function Triage() {
  useModel('anthropic/claude-sonnet-4-6');
  useSandbox(local());
  useSkill(triage);
  useSkill(verify);
  useTool(openIssue);
  useTool(searchCode);
  return `Triage a bug report end-to-end: reproduce the bug,
diagnose the root cause, verify whether the behavior is
intentional, and attempt a fix.`;
}

Test with:

bash
npx flue run src/agents/triage.ts --message "Bug: /search returns 500 on null query" --verbose

17.5 Chapter Summary ​

  • End-to-end triage: reproduce → locate → verify → fix → report;
  • Tools: searchCode (read-only) + openIssue (durable, idempotent);
  • Skills: triage methodology + verify methodology;
  • Sandbox: local() for file/shell access;
  • Assembly: one agent function, six hooks, one return value.

🧪 随堂测验

点击你认为正确的选项。答错时会展示正确答案与原因解析。

1. What does durable: true on openIssue mean?

2. Why use a Set for deduplication in the demo openIssue tool?

3. What is the role of the verify skill in this capstone?

4. Why is local() sandbox used for the triage agent?

🛠️ Hands-On Exercises ​

  1. Build the full triage agent: implement searchCode and openIssue tools, write triage and verify skills, assemble in one agent function.
  2. Test with a real GitHub issue; verify the issue is created and the dedup guard prevents duplicates on retry.
  3. Add a fourth tool (e.g., getIssue) and verify the agent can read existing issues before deciding to create a new one.

Capstone 1 complete. Next: Capstone 2: Slack On-Call Assistant.