Đặc tả mã nguồn mở cho quy trình điều phối Codex: Symphony
Bởi Alex Kotliarskyi, Victor Zhu và Zach Brock
Sáu tháng trước, khi đang xây dựng một công cụ tăng năng suất nội bộ, nhóm chúng tôi đã đưa ra một quyết định gây tranh cãi (vào thời điểm đó): chúng tôi sẽ xây dựng repo mà không có bất kỳ dòng code nào do con người viết. Mọi dòng trong repo dự án đều phải do Codex viết nên.
Để làm được điều đó, chúng tôi đã thiết kế lại quy trình kỹ thuật của mình từ gốc. Chúng tôi xây dựng một repo dễ tiếp cận với tác nhân, đầu tư mạnh vào kiểm thử tự động và các cơ chế bảo vệ đồng thời xem Codex như một đồng đội thực thụ. Chúng tôi đã ghi lại hành trình đó trong bài blog trước đây về kỹ thuật dùng trong hệ thống điều khiển.
Quy trình đã có hiệu quả, nhưng rồi chúng tôi gặp phải điểm tắc nghẽn tiếp theo: chuyển đổi ngữ cảnh.
Để giải quyết vấn đề mới này, chúng tôi đã xây dựng một hệ thống gọi là Symphony. Symphony(mở trong cửa sổ mới) là một bộ điều phối tác nhân biến một bảng quản lý dự án như Linear thành lớp điều khiển dành cho các tác nhân lập trình. Mỗi tác vụ cần xử lý đều có một tác nhân, các tác nhân chạy liên tục, và con người đánh giá kết quả.
Bài viết này giải thích cách chúng tôi tạo ra Symphony—đem lại mức tăng 500% số lượng pull request được duyệt cho một số nhóm—và cách sử dụng công cụ này để biến hệ thống theo dõi issue của riêng bạn thành một bộ điều phối tác nhân luôn hoạt động.
Giới hạn của các tác nhân lập trình mang tính tương tác
Ngay cả khi chúng ngày càng dễ sử dụng hơn, các tác nhân lập trình—dù được truy cập qua ứng dụng web hay CLI—vẫn là những công cụ mang tính tương tác.
Khi quy mô công việc cần đến tác nhân tại OpenAI tăng lên, chúng tôi nhận ra một kiểu gánh nặng mới. Mỗi kỹ sư sẽ mở vài phiên Codex, giao nhiệm vụ, xem lại đầu ra, điều hướng tác nhân, rồi lặp lại. Trên thực tế, hầu hết mọi người chỉ có thể thoải mái quản lý từ ba đến năm phiên cùng lúc trước khi việc chuyển đổi ngữ cảnh trở nên nhức đầu. Vượt quá mức đó, năng suất giảm xuống. Chúng tôi quên phiên nào đang làm gì, nhảy giữa các terminal để điều chỉnh tác nhân quay lại đúng hướng, và gỡ lỗi các tác vụ chạy dài bị đình trệ giữa chừng.
Các tác nhân rất nhanh, nhưng có một nút thắt của hệ thống: sự chú ý của con người. Về cơ bản, chúng tôi đã xây dựng một đội ngũ kỹ sư cấp dưới cực kỳ năng lực, rồi giao cho các kỹ sư con người xét nét chúng từng bước. Mô hình này sẽ không thể mở rộng.
Góc nhìn đổi thay
Chúng tôi nhận ra mình tối ưu sai quy trình mất rồi. Chúng tôi đang tổ chức hệ thống xoay quanh các phiên lập trình và các PR được merge, trong khi PR và phiên thực chất chỉ là phương tiện để đạt mục đích. Quy trình phát triển phần mềm phần lớn được tổ chức quanh các đầu ra bàn giao: issue, tác vụ, ticket, cột mốc.
Vì vậy, chúng tôi tự hỏi điều gì sẽ xảy ra nếu ngừng trực tiếp giám sát các tác nhân và thay vào đó để chúng tự nhận việc từ trình theo dõi tác vụ.
Ý tưởng đó trở thành Symphony, một bản đặc tả được viết ra với vai trò làm giám sát viên điều phối công việc cần đến tác nhân.
Biến trình theo dõi issue thành bộ điều phối tác nhân
Symphony bắt đầu từ một khái niệm đơn giản: bất kỳ tác vụ đang chờ xử lý nào cũng nên được một tác nhân tiếp nhận và hoàn thành. Thay vì quản lý các phiên Codex trên nhiều thẻ, chúng tôi biến trình theo dõi issue thành lớp điều khiển.
Theo thiết lập này, mỗi issue đang chờ xử lý trong Linear tương ứng với một không gian làm việc tác nhân chuyên biệt. Symphony liên tục theo dõi bảng tác vụ và đảm bảo rằng mọi tác vụ đang được xử lý đều có một tác nhân chạy trong vòng lặp cho đến khi hoàn tất. Nếu một tác nhân bị lỗi hoặc bị treo, Symphony sẽ khởi động lại nó. Nếu có việc mới, Symphony sẽ tiếp nhận và bắt đầu tổ chức công việc.
Chúng tôi xây dựng quy trình làm việc dựa trên trạng thái ticket, sử dụng trình quản lý tác vụ Linear làm máy trạng thái.
Trên thực tế, Symphony tách công việc khỏi các phiên và pull request. Một số issue dẫn đến nhiều PR trên nhiều repo; số khác chỉ đơn thuần là công việc điều tra hoặc phân tích và không bao giờ đụng chạm gì vào codebase.
Khi công việc được trừu tượng hóa theo cách này, mỗi ticket có thể đại diện cho một đơn vị công việc lớn hơn nhiều.
Chúng tôi thường xuyên sử dụng Symphony để điều phối các tính năng phức tạp và các lần tái cấu trúc hạ tầng. Ví dụ, chúng tôi có thể tạo một tác vụ yêu cầu tác nhân phân tích codebase, Slack hoặc Notion và tạo ra một kế hoạch triển khai. Khi đã hài lòng với kế hoạch, tác nhân sẽ tạo ra một cây tác vụ, chia công việc thành các giai đoạn và xác định xem giữa các tác vụ có những phụ thuộc nào vào nhau.
Các tác nhân chỉ bắt đầu thi hành những tác vụ không bị chặn, vì vậy việc thực thi diễn ra tự nhiên và tối ưu theo kiểu song song cho đồ thị có hướng không chu trình DAG này (một chuỗi các bước thực thi). Trong ví dụ bên dưới, chúng tôi đánh dấu là việc nâng cấp React bị chặn bởi quá trình chuyển đổi sang Vite. Đúng như dự kiến, các tác nhân chỉ bắt đầu nâng cấp React sau khi việc chuyển đổi sang Vite hoàn tất.
Các tác nhân cũng có thể tự tạo công việc. Trong quá trình triển khai hoặc rà soát, chúng thường nhận ra các cải tiến nằm ngoài phạm vi của tác vụ hiện tại: một vấn đề hiệu năng, một cơ hội tái cấu trúc, hoặc một kiến trúc tốt hơn. Khi điều đó xảy ra, chúng chỉ cần tạo một issue mới để chúng tôi có thể đánh giá và lên lịch sau—nhiều tác vụ tiếp theo trong số đó cũng được các tác nhân tiếp nhận. Trong khi chúng tôi giám sát quy trình này, các tác nhân vẫn tập trung tiếp tục phát triển công việc.
Cách làm này làm giảm đáng kể chi phí nhận thức khi khởi động những công việc mơ hồ. Nếu tác nhân làm sai điều gì đó, đó vẫn là thông tin hữu ích, và chi phí đối với chúng tôi gần như bằng không. Chúng tôi có thể tạo ticket với chi phí rất thấp để tác nhân tạo bản nguyên mẫu và khám phá, rồi loại bỏ bất kỳ hướng khám phá nào chúng tôi không thích.
Bởi vì bộ điều phối chạy trên devbox và không bao giờ ngủ, chúng tôi có thể thêm tác vụ từ bất kỳ đâu và biết rằng sẽ có một tác nhân tiếp nhận. Chẳng hạn, một kỹ sư trong nhóm chúng tôi đã thực hiện ba thay đổi đáng kể từ ứng dụng Linear trên điện thoại của anh ấy khi đang ở trong một cabin ấm cúng với wifi chập chờn.
Khám phá nhiều hơn khi làm việc theo phương pháp này
Khi quan sát tác động của việc hợp tác với Symphony, thay đổi rõ ràng nhất là ở đầu ra. Với một số đội ngũ tại OpenAI, chúng tôi thấy số PR được đưa vào tăng gấp 6 lần trong ba tuần đầu tiên. Bên ngoài OpenAI, nhà sáng lập Linear là Karri Saarinen đã nhấn mạnh một số không gian làm việc được tạo ra tăng vọt(mở trong cửa sổ mới) khi chúng tôi phát hành Symphony. Tuy nhiên, sự thay đổi sâu sắc hơn nằm ở cách các đội ngũ suy nghĩ về công việc.
Khi các kỹ sư của chúng tôi không còn phải dành thời gian giám sát các phiên Codex nữa, tính kinh tế của code hoàn toàn thay đổi. Chi phí chúng tôi nhận thức cho mỗi lần thay đổi giảm xuống vì chúng tôi không còn đầu tư công sức của con người vào chính việc thúc đẩy triển khai.
Điều đó đã thay đổi hành vi của chúng tôi. Việc khởi tạo các tác vụ mang tính thử nghiệm trong Symphony trở nên vô cùng dễ dàng. Hãy thử một ý tưởng, khám phá một đợt tái cấu trúc, kiểm tra một giả thuyết, và chỉ giữ lại những kết quả trông có triển vọng.
Hệ thống này cũng mở rộng đối tượng có thể khởi xướng công việc. Người quản lý sản phẩm và nhà thiết kế của chúng tôi giờ đây có thể trực tiếp gửi yêu cầu tính năng vào Symphony. Họ không cần xem qua repo hay quản lý một phiên Codex. Họ mô tả tính năng và nhận lại một gói rà soát bao gồm video hướng dẫn cách tính năng hoạt động trong sản phẩm thật.
Symphony cũng đặc biệt hiệu quả trong các monorepo lớn (như monorepo chúng tôi có tại OpenAI), nơi mà chặng cuối để đưa một PR vào thường chậm và mong manh. Hệ thống theo dõi CI, rebase khi cần, giải quyết xung đột, thử nghiệm lại các kiểm tra không ổn định, và nói chung là dìu dắt các thay đổi đi qua pipeline. Khi một ticket đi đến trạng thái merge, chúng tôi vô cùng tự tin rằng thay đổi đó sẽ vào nhánh chính mà không cần con người trông nom.
Tiến bộ đi kèm những vấn đề mới, rất khác biệt
Vận hành ở cấp độ này luôn có đánh đổi. Khi chúng tôi chuyển từ việc điều hướng tác nhân theo cách tương tác sang giao việc cho chúng ở cấp độ ticket, chúng tôi mất đi khả năng liên tục điều chỉnh giữa chừng và sửa hướng khi cần. Đôi khi tác nhân tạo ra thứ gì đó hoàn toàn chệch mục tiêu. Điều đó vẫn hữu ích—những thất bại ấy bộc lộ các điểm yếu trong hệ thống và giúp chúng tôi xây dựng nó vững chắc hơn.
Thay vì vá kết quả bằng tay, chúng tôi thêm cơ chế bảo vệ và kỹ năng để các tác nhân có thể thành công trong lần tiếp theo. Theo thời gian, phương pháp này thúc đẩy chúng tôi bổ sung các năng lực mới cho hệ thống điều khiển, như chạy kiểm thử từ đầu chí cuối, điều khiển ứng dụng qua DevTools của Chrome và quản lý các kiểm thử khói. Chúng tôi đã cải thiện đáng kể tài liệu và làm rõ thế nào là biện pháp tốt.
Không phải mọi tác vụ đều phù hợp với phong cách làm việc của Symphony. Một số vấn đề vẫn cần kỹ sư làm việc trực tiếp với các phiên Codex tương tác, đặc biệt là các vấn đề mơ hồ hoặc công việc đòi hỏi khả năng phán đoán và chuyên môn cao. Trên thực tế, đây thường là những nhiệm vụ thú vị và hấp dẫn nhất để các kỹ sư của chúng tôi chuyên tâm.
Điểm khác biệt là Symphony có thể xử lý phần lớn công việc triển khai thường lệ. Điều đó cho phép các kỹ sư tập trung vào một vấn đề khó tại một thời điểm thay vì liên tục chuyển đổi ngữ cảnh giữa các tác vụ nhỏ hơn.
Chúng tôi cũng học được rằng việc xem tác nhân như các node cứng nhắc trong một máy trạng thái là không hề hiệu quả. Các mô hình ngày càng thông minh hơn và có thể giải quyết những vấn đề lớn hơn cái khuôn mà chúng tôi cố nhét chúng vào. Ví dụ, ở các phiên bản đầu, toàn bộ tích hợp GitHub là một phần của hệ thống điều khiển bên ngoài—chẳng hạn, các phiên bản đầu kỳ vọng Codex chỉ thực hiện thay đổi code, còn phần còn lại của quy trình (gửi thay đổi, chạy kiểm thử) được chỉ định trong mã. Các phiên bản đầu của công việc mang tính tác nhân của chúng tôi chỉ yêu cầu Codex triển khai tác vụ. Cách tiếp cận đó dường như quá hạn chế. Codex hoàn toàn có khả năng tạo nhiều PR cũng như xem xét phản hồi rà soát và xử lý chúng. Vì vậy, chúng tôi đã cung cấp cho nó một công cụ—CLI gh, kỹ năng đọc nhật ký CI, v.v.—và giờ đây chúng tôi có thể yêu cầu Codex làm nhiều việc hơn, như đóng các PR cũ hoặc tạo báo cáo về công việc đã hoàn thành và công việc bị bỏ dở. Những kiểu nhiệm vụ này nằm vượt ngoài khung triển khai tính năng ban đầu.
Vì thế, cuối cùng chúng tôi chuyển sang giao cho tác nhân mục tiêu thay vì các chuyển giao trạng thái cứng nhắc, giống như cách một người quản lý giỏi giao mục tiêu cho nhân sự trực tiếp trong nhóm của mình. Sức mạnh của các mô hình đến từ khả năng suy luận, vì vậy hãy cho chúng công cụ và ngữ cảnh rồi để chúng tự thể hiện.
Dùng Symphony để xây dựng Symphony
Khi mở repo Symphony, điều đầu tiên bạn nhận thấy là về mặt kỹ thuật Symphony chỉ là một tệp SPEC.md—một định nghĩa về vấn đề và giải pháp dự kiến. Thay vì xây dựng một hệ thống giám sát phức tạp, chúng tôi định nghĩa vấn đề và các giải pháp dự kiến, cung cấp cho tác nhân sự định hướng ở cấp độ cao.
Bản triển khai dùng để tham khảo được viết bằng Elixir—bởi vì khi việc viết code gần như miễn phí, bạn cuối cùng cũng có thể chọn ngôn ngữ dựa trên thế mạnh của chúng, ví dụ như khả năng xử lý đồng thời của Elixir—nhưng ý tưởng cốt lõi có thể được diễn đạt trong một tài liệu Markdown đơn giản. Chúng tôi khuyến khích bạn đưa bản tài liệu đặc tả cho tác nhân lập trình yêu thích của mình, để nó triển khai phiên bản riêng.
Phiên bản đầu tiên của Symphony chỉ là một phiên Codex chạy trong tmux, thăm dò Linear và khởi tạo các tác nhân phụ cho tác vụ mới. Nó chạy được, nhưng không thực sự đáng tin cậy. Phiên bản thứ hai nằm trong repo dự án chính của chúng tôi, vốn lấy tác nhân làm trọng tâm trong quá trình phát triển. Chúng tôi đã xây dựng sẵn hệ thống điều khiển tác nhân để cung cấp cho tác nhân các kỹ năng và ngữ cảnh cần thiết, nhằm tạo ra kết quả chất lượng cao trong repo này, vì vậy Symphony chỉ đơn giản kết nối tất cả lại.
Khi chức năng cơ bản đã có, chúng tôi dùng Symphony để xây dựng Symphony.
Khi chúng tôi trình diện bản mẫu cho thấy cách nội bộ hệ thống quản lý tác vụ và đính kèm video minh chứng tính năng, chúng tôi nhận được phản ứng tích cực áp đảo: kênh dự án Symphony của chúng tôi phát triển mạnh, các đội nhóm trong toàn tổ chức bắt đầu sử dụng bộ điều phối một cách tự nhiên. Tại OpenAI, độ phù hợp sản phẩm thị trường trong nội bộ là điều kiện tiên quyết để ra mắt thị trường bên ngoài. Dựa trên mức sử dụng chúng tôi thấy ở OpenAI, rõ ràng là chúng tôi nên chia sẻ Symphony vượt ra ngoài phạm vi công ty.
Vì vậy, chúng tôi tách ý tưởng này thành một tệp SPEC.md độc lập và yêu cầu Codex triển khai nó. Đối với bản triển khai dùng để tham khảo, chúng tôi chọn Elixir, một ngôn ngữ tương đối ngách nhưng có các thành phần cơ bản (primitive) rất tốt để điều phối và giám sát các tiến trình đồng thời. Codex đã xây dựng bản triển khai Elixir chỉ trong một lần viết, và từ đó chúng tôi tiếp tục cập nhật và hoàn thiện cả tài liệu đặc tả tính năng lẫn bản triển khai. Để trau chuốt tài liệu đặc tả, chúng tôi thậm chí còn yêu cầu Codex triển khai nó bằng một số ngôn ngữ khác—TypeScript, Go, Rust, Java, Python—và dùng kết quả để xác định chỗ nào còn mơ hồ cũng như đơn giản hóa hệ thống. Dù là bằng ngôn ngữ nào, kết quả cũng là thành công.
Thông qua quá trình xây dựng Codex, chúng tôi đã loại bỏ rất nhiều sự phức tạp phát sinh ngẫu nhiên, như việc phụ thuộc vào các repo cụ thể hoặc MCP của Linear. Symphony không còn phụ thuộc vào các repo hay quy trình làm việc nội bộ của chúng tôi nữa. Cách tiếp cận cốt lõi trở nên đơn giản:
Với mỗi tác vụ đang chờ xử lý, hãy đảm bảo có một tác nhân đang chạy trong không gian làm việc riêng của nó.
Ngoài việc hỗ trợ công việc đang diễn ra, quy trình phát triển giờ đây cũng là điều mà các tác nhân biết và tuân theo. Quy trình phát triển—làm việc trên một issue, xem xét repo, chuyển nó sang trạng thái "In Progress" để người quản lý sản phẩm biết issue đang được xử lý, thêm PR, chuyển sang trạng thái "Review", đính kèm video, v.v.—giờ được ghi lại trong một tệp WORKFLOW.md đơn giản. Tất cả những điều này là một quy trình mà con người từng làm theo, nhưng chưa bao giờ được ghi chép lại. Thay vì dựa vào các bước được ngầm quy định đó, giờ đây chúng tôi ghi chép lại nó, và Symphony đảm bảo các tác nhân tuân theo. Điều này cho phép xây dựng các tác nhân hợp tác với chúng tôi. Nếu chúng tôi quyết định rằng các tác nhân cũng nên đính kèm phần tự nhận xét về chính mình vào công việc đã hoàn thành, chúng tôi sẽ thêm yêu cầu đó vào WORKFLOW.md, và Symphony sẽ hướng dẫn các tác nhân thực hiện bước đó.
Chúng tôi cũng đã sử dụng Codex ở chế độ server của ứng dụng(mở trong cửa sổ mới), một chế độ headless tích hợp sẵn cho Codex. Chế độ này cho phép chúng tôi chạy Codex và giao tiếp với nó theo hướng được lập trình thông qua một API JSON-RPC có tài liệu ghi chú kỹ càng cho những việc như khởi tạo một luồng hoặc phản hồi theo lượt. Phương pháp này thuận tiện và có khả năng mở rộng hơn nhiều so với việc cố tương tác với Codex qua CLI hoặc các phiên tmux trực tiếp.
Codex App Server cực kỳ phù hợp với trường hợp sử dụng của chúng tôi: chúng tôi tận dụng hệ thống điều khiển mà Codex cung cấp, đồng thời có các nút điều chỉnh và hook để cắm vào. Ví dụ, để tránh làm lộ token truy cập Linear cho các tác nhân phụ, chúng tôi sử dụng lệnh gọi công cụ linh động(mở trong cửa sổ mới) để phơi bày hàm linear_graphql thô thực thi các yêu cầu tùy ý với Linear, mà không cần dựa vào MCP hay để lộ token truy cập cho các container.
Tiếp theo là gì
Symphony là một lớp điều phối được chủ ý giữ ở mức tối giản. Chúng tôi công khai mã nguồn của nó để minh chứng sức mạnh của Codex App Server khi được tác hợp với các công cụ quy trình làm việc khác nhau, như Linear. Vì vậy, chúng tôi không có kế hoạch duy trì Symphony như một sản phẩm độc lập. Hãy xem nó như một bản triển khai mang tính tham khảo. Tương tự như cách nhiều nhà phát triển đã chỉ hướng tác nhân lập trình của họ tới bài viết về kỹ thuật hệ thống điều khiển để dựng khuôn khổ cho repo của họ, chúng tôi hy vọng bạn sẽ đưa tác nhân lập trình yêu thích của mình xemtài liệu đặc tả(mở trong cửa sổ mới) và repo(mở trong cửa sổ mới) Symphony để xây dựng các phiên bản riêng phù hợp với môi trường của bạn.
Sức mạnh đến từ Codex và server ứng dụng đi cùng. Symphony là cách để kết nối Codex với Linear, hai thứ chúng tôi vốn đã dùng, nhằm giải quyết bài toán quản lý công việc. Khi các tác nhân lập trình ngày càng giỏi suy luận và làm theo chỉ dẫn, chúng tôi nghi ngờ rằng nút thắt ở các công ty khác cũng sẽ chuyển từ việc viết code sang quản lý công việc cần đến tác nhân. Điều thú vị là rào cản để thử nghiệm với các hệ thống tác nhân lập trình này giờ đây thấp đến bất ngờ. Bạn có thể xây dựng bất cứ thứ gì với Codex.
Lời cảm ơn cộng đồng
Chúng tôi rất vui khi thấy cộng đồng kỹ thuật sử dụng Symphony trong những tuần sau khi phát hành, đạt hơn 15 nghìn sao trên GitHub(mở trong cửa sổ mới) tính đến ngày 23 tháng 4.