【文章翻譯】Architects, testers, and coders: Building multi-agent development teams

【文章內容使用 Gemini 2.5 Flash 自動翻譯產生】

原文:https://flutter.dev/blog/building-multi-agent-dev-teams

架構師、測試人員和程式設計師:建立多代理開發團隊

如何讓在 Antigravity 中運行的多代理開發團隊使用測試驅動開發將 Python 函式庫移植到慣用的 Dart 套件。

當我第一次開始嘗試 AI 編碼助手時,我只使用一個代理來處理所有事情:架構程式碼、編寫單元測試和偵錯堆疊追蹤。雖然這種方法對於小任務運作良好,但對於複雜的軟體工程問題,它很快就會退化。隨著我的對話歷史記錄增長,代理的上下文視窗會被日誌和搜尋結果飽和,導致幻覺、遺漏連結、馬虎的程式碼和錯誤。

幾週前,我偶然發現了一篇關於多代理、TDD 編碼工作流程的部落格文章。我以前聽說過這種方法,但從未嘗試過。由於 Antigravity 的 Agent Hub 包含了多代理設定的工具,我決定對這種技術進行測試。

為了在真正的挑戰中測試它,我著手將流行的 python-statemachine 函式庫移植到靜態類型、無反射的 Dart 套件。儘管 pub.dev 已經有穩定的狀態機選項,但 python-statemachine 是一個絕佳的基準。它嚴重依賴於動態 Python 功能(例如元類和執行時回呼),這些功能必須針對 Dart 強大的靜態類型和 Flutter 對不使用 dart:mirrors 的要求進行徹底重新架構。

這比看起來要複雜,我發誓!

我學到的第一件事是,有效代理團隊的基礎是一組明確定義的角色,這些角色對代理可以做的事情有特定的限制。為了實施這些,我建立了一個總體工作流程技能(tdd-dart-workflow)來建立共用的 TDD 規則和權限,以及四個定義每個代理的角色特定技能:

  • **架構師 (tdd-dart-architect)**:分析原始碼,生成前端的 architecture_blueprint.md (階段 0),並在 specs/ 下編寫有針對性的模組規範 (階段 1)。架構師不能寫入 lib/test/example/
  • **測試人員 (tdd-dart-tester)**:根據架構師的規範 (階段 2a) 在 test/ 下編寫全面的失敗單元測試。測試人員不能查看或寫入 lib/specs/
  • **程式設計師 (tdd-dart-coder)**:在 lib/src/ 下建立編譯骨架並實作函式庫程式碼以使失敗的測試通過 (階段 2b 和 3)。程式設計師不能編輯 test/specs/
  • **協調者 (tdd-dart-coordinator)**:協調者父代理。協調者管理 Git 分支提交,執行 dart analyze && dart test 驗證,並將任務委派給子代理。

透過強制執行嚴格的角色分離,沒有任何單一代理可以同時修改測試斷言和底層程式碼。協調者充當守門員,僅在子代理完成任務時收到簡短的完成報告。這建立了一個「認知防火牆」。除了在工作面試中是一個有趣的詞語之外,這意味著每個子代理的試錯標記歷史記錄會在任務完成後被丟棄。因此,父協調者的上下文視窗保持乾淨、專注,並且沒有標記飽和。

需要澄清的是,我在此處省略了一些潛在問題,例如「如果子代理陷入循環會發生什麼?」和「是否應該設定一個時間截止,在此之後子代理會被終止並重新創建或通知人類?」不過,這是我首次涉足多代理團隊,這些問題我將留到後續的部落格文章中再討論!

為了防止競爭條件和未經證實的程式碼提交,檔案系統寫入權限在工具層級的 tdd-dart-workflow 中受到限制,具體取決於代理的指定角色:

代理角色 允許讀取路徑 允許寫入路徑 禁止操作
協調者 任何地方 任何地方 直接程式碼修改 (委派給程式設計師/測試人員)
架構師 任何地方 specs/, skills/ 不能寫入 lib/test/example/
測試人員 任何地方 test/, example/ 不能寫入 lib/specs/
程式設計師 任何地方 lib/, example/ 不能寫入 test/specs/

在我的版本中,這些限制包含在代理的說明中,因此它們技術上可能會被違反。然而,隨著多代理系統的演進,我預計我們將開始看到以更安全的方式實施限制的方法。

一個挑戰範例

在像 Dart 這樣的編譯語言中操作多代理團隊帶來了一個有趣的挑戰:靜態編譯和為尚不存在的程式碼編寫測試之間的緊張關係。在像 Python 這樣的動態語言中,TDD 以一個拋出運行時錯誤的失敗測試(紅)開始。然而,在靜態語言中,為尚不存在的方法編寫單元測試會導致編譯時失敗。編譯器會中止,阻止測試執行器執行並證明測試本身是有效的。

我的代理團隊在 紅色階段 循環中解決了這個問題。首先,測試人員根據架構師的規範編寫測試。接下來,程式設計師在 lib/src/ 下建立一個編譯骨架,其中包含返回虛擬值或拋出 UnimplementedError 的類和方法存根。一旦骨架滿足編譯器,協調者會運行 dart analyze && dart test 以驗證測試因正確的原因(未實現的邏輯而不是語法或匯入錯誤)而失敗。註釋邊界確保臨時骨架存根與永久測試工具隔離,允許協調者在程式設計師完成綠色階段後自動清除它們:

1
2
3
4
5
6
7
8
9
// TEST UTILITIES - KEEP PERMANENTLY
class MockListener extends Mock implements StateMachineListener {}

// SKELETON STUBS FOR COMPILATION - DELETE ONCE SKELETON IS IMPLEMENTED
class StateMachine<T extends StateModel> {
dynamic get currentState => throw UnimplementedError();
dynamic get currentStateValue => throw UnimplementedError();
void send(String eventId) => throw UnimplementedError();
}

出了什麼問題:摩擦點和飛行中的技能更新

使用代理確實加快了移植函式庫的過程,但它也帶來了自己的挑戰。然而,工作流程中斷的時刻提供了最有價值的工程教訓。在移植過程中,我不得不暫停並更新我的技能檔案(SKILL.md)以解決三個問題:

  • 意外刪除測試工具:一旦程式設計師在 lib/src/ 中實作了真實的函式庫類別,協調器就必須清理測試檔案中留下的臨時編譯存根。它總是將存根與真實的東西混淆,因此需要清晰的註釋標頭才能使真實的實作清晰。
  • 展開運算子 (...) 類型不匹配:Python 和 Dart 處理類型的方式不同,Python 中預設可迭代的東西在 Dart 中不一定如此。我不得不針對需要實作 Iterable 的東西新增一些明確的說明。
  • 「帶有外來口音」的動態類型:再次地,不同的類型系統造成了問題。原始的 Python 函式庫在幾個地方使用了 hasattr 檢查。我不得不改進 tdd-dart-architect/SKILL.md 以強制架構師使用慣用的 Dart 和明確的回呼委派,而不是動態屬性查找。

這些技能改進直接塑造了最終的設計。產生的 Dart 套件 (state_machine) 保留了 python-statemachine 的完整功能集,程式設計師代理實作了流暢的建構器以支援 Dart 的原生展開運算子 (...)。以下是原始 Python 類別定義與代理團隊產生的最終 Dart 狀態機之間的比較:

舊方式:

1
2
3
4
5
6
7
8
9
10
11
12
# python-statemachine syntax (declarative via metaclasses)
from statemachine import StateMachine, State

class TrafficLightMachine(StateMachine):
green = State("Green", initial=True)
yellow = State("Yellow")
red = State("Red")

cycle = green.to(yellow) | yellow.to(red) | red.to(green)

def on_enter_green(self):
print("Entered Green state")

新方式:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
// state_machine Dart syntax (declarative constructor DSL)
import 'package:state_machine/state_machine.dart';

class TrafficLightMachine extends StateMachine<TrafficLightModel> {
final green = State('green', initial: true);
final yellow = State('yellow');
final red = State('red');

late final Event cycle;

TrafficLightMachine(TrafficLightModel model) : super(model: model) {
cycle = event('cycle');

initialize(
states: [green, yellow, red],
transitions: [
...green.to(yellow).on(cycle).transitions,
...yellow.to(red).on(cycle).transitions,
...red.to(green).on(cycle).transitions,
],
);

green.onEnter((EventData data) {
print('Entered Green state');
});
}
}

所有這些的重點更多是為我自己產生一套可重複使用的技能,而不是建立和發布一個新的 Dart 套件。我目前無法監測報告的問題並長期維護這個套件。我無法保證所有建立的程式碼都沒問題(儘管在我測試時它確實有效),而且生態系統中已經有幾個維護良好的套件。然而,希望這能讓您了解在 Antigravity 中運行的多代理系統能為 Dart 和 Flutter 開發人員做些什麼。

如果您想在自己的專案中探索多代理工作流程,請嘗試一下!

  • 下載 Antigravity
  • 造訪 AGY 入門文件,了解如何定義自己的代理和自訂開發人員技能。
  • 查看我在這個專案中建立的實際技能檔案,根據您的需求修改它們,並讓我們知道進展如何!

Flutter 的更多內容