Chuyển tới nội dung
cd /blog

Quan sát Agent: Hooks, Alloy, và Grafana

[Khả năng quan sát][Grafana][OpenTelemetry][Kiến trúc]

> Chúng tôi kết nối Claude Code và Codex vào cùng một stack Grafana bằng OpenTelemetry và Alloy, sau đó dùng trace và log để tìm và sửa các vấn đề hành vi của agent tận gốc.

Các hệ thống agent hỏng theo những cách kỳ lạ.

Đôi khi mô hình là vấn đề. Đôi khi công cụ là vấn đề. Đôi khi máy chủ MCP của bạn vẫn ổn, nhưng agent lại chọn sai chuyên gia, hoặc tốn nửa phiên làm việc shell mà bạn không ngờ tới, hoặc âm thầm đốt chi phí trong một vòng lặp trông có vẻ hiệu quả từ bên ngoài.

Nếu bạn không thể thấy được sự khác biệt, bạn không thực sự đang vận hành một hệ thống agent. Bạn đang đoán mò.

Vì vậy chúng tôi đã xây dựng một stack quan sát (observability stack) cho chính quy trình làm việc của mình: Claude Code, Codex, các sự kiện hook của Claude, các sự kiện notify của Codex, OpenTelemetry gốc, Grafana Alloy, và Grafana Cloud ở phía bên kia.

Phần thú vị không phải là “chúng tôi đã làm một dashboard.” Phần thú vị là chúng tôi phải tách telemetry thành hai luồng khác nhau vì không một nguồn dữ liệu duy nhất nào cho chúng tôi bức tranh toàn cảnh.

Vấn đề: Telemetry Agent bị phân mảnh

Các agent lập trình hiện đại đã phát ra một số telemetry. Điều đó có ích, nhưng chưa đủ.

OTEL gốc giỏi trả lời những câu hỏi như:

  • Chúng ta đã gửi bao nhiêu request?
  • Một phiên tốn bao nhiêu chi phí?
  • Các span và trace nằm ở đâu?
  • Độ trễ có tăng vọt không?

Nó tệ hơn nhiều khi trả lời những câu hỏi như:

  • Agent dựa vào máy chủ MCP nào?
  • Lỗi này nằm trong Bash, một công cụ file có sẵn, hay một lệnh gọi MCP?
  • Skill nào thực sự đã kích hoạt?
  • Loại subagent nào đã được điều động?
  • Phiên làm việc có đang làm việc hữu ích, hay chỉ đang quẫy đạp vô ích?

Lớp câu hỏi thứ hai đó gần với hook hơn là trace.

Nhưng điều ngược lại cũng đúng: một số câu hỏi hiệu năng quan trọng nhất lại gần với trace hơn là hook.

Nếu bạn muốn biết độ trễ thực sự tích tụ ở đâu, span nào chậm, hay liệu phiên làm việc có tốn thời gian vào các lệnh gọi mô hình so với thực thi công cụ hay không, bạn cần dữ liệu trace cũng như các sự kiện ngữ nghĩa.

Kiến trúc mà chúng tôi đã đi đến

Chúng tôi chạy hai đường telemetry song song.

Claude Code
  native OTEL -> Alloy -> Grafana Cloud
  hooks        -> send_event.py -> Grafana Cloud Loki

Codex
  native OTEL -> Alloy -> Grafana Cloud
  notify hook -> codex_notify.py -> shared Loki schema

Sự tách biệt đó là có chủ đích.

Nó cũng bất đối xứng. Claude Code cho chúng tôi một bề mặt hook vòng đời phong phú hơn nhiều. Codex cho chúng tôi OTEL gốc cộng với một bề mặt notify, nên chúng tôi chuẩn hóa các sự kiện hoàn tất lượt (turn-completion) mỏng hơn thành cùng một schema log thay vì giả vờ rằng cả hai runtime phơi bày cùng những cơ chế kiểm soát.

OTEL gốc cho chúng tôi luồng cơ sở: log và trace từ chính runtime, cộng với các metric ở nơi runtime thực sự phát ra chúng.

Sự kiện hook và notify cho chúng tôi tầng ngữ nghĩa: những thứ như PreToolUse, PostToolUse, PostToolUseFailure, UserPromptSubmit, SubagentStop, SkillActivated, và metadata đã phân loại mà chúng tôi thực sự quan tâm khi debug hành vi agent. Claude Code đóng góp luồng sự kiện phong phú hơn ở đây. Codex đóng góp một luồng đã chuẩn hóa mỏng hơn nhưng vẫn hữu ích.

Vì sao hook tồn tại

Pipeline hook của chúng tôi làm giàu các sự kiện trước khi chúng chạm đến Loki.

Thay vì chỉ nói “một công cụ đã chạy,” chúng tôi phân loại sự kiện vào các trường như:

  • tool_type: builtin, mcp, skill, agent, bash
  • mcp_server: backend MCP nào đã xử lý lệnh gọi
  • bash_cli: họ lệnh shell
  • subagent_type: loại chuyên gia nào đã được điều động
  • agent_tool: nguồn là Claude Code hay Codex

Điều đó có nghĩa là chúng tôi có thể đặt những câu hỏi quan trọng về mặt vận hành:

{service_name="claude-code-hooks"} | agent_tool="codex-cli"
{service_name="claude-code-hooks"} | tool_type="mcp"
{service_name="claude-code-hooks"} | json | bash_cli="git"

Đó không phải là những trường mang tính phù phiếm. Chúng là sự khác biệt giữa “agent có cảm giác chậm” và “agent đã tốn mười phút cuối vào các thao tác git nặng shell với tỷ lệ công cụ thất bại cao.”

Một chi tiết triển khai trông kỳ lạ hơn thực tế của nó: luồng Loki dùng chung vẫn dùng service_name="claude-code-hooks" làm nhãn ngay cả khi sự kiện đến từ Codex. Sự tách biệt thực sự giữa các runtime xảy ra ở agent_tool.

Vì sao Alloy nằm ở giữa

Grafana Alloy không chỉ là một bộ chuyển tiếp trong thiết lập này. Nó là ranh giới chính sách.

Chúng tôi trỏ luồng OTEL gốc từ Claude Code và Codex đến một Alloy proxy cục bộ trên localhost:4318, sau đó để Alloy dọn dẹp payload trước khi chuyển tiếp nó đến Grafana Cloud.

Điều đó quan trọng vì telemetry agent thô đầy ắp các trường có độ phân tán cao (high-cardinality), hữu ích cho phân tích nhưng tệ hại khi làm nhãn được lập chỉ mục:

  • session_id
  • prompt_id
  • số lượng token
  • thời lượng
  • các blob tham số công cụ

Nếu bạn lập chỉ mục mọi thứ, bạn sẽ có một vụ bùng nổ nhãn và một ngày tồi tệ.

Vì vậy Alloy làm ba việc cho chúng tôi:

  1. Giữ lại một tập rất nhỏ các nhãn có độ phân tán thấp được lập chỉ mục.
  2. Chuyển các trường ồn ào nhưng hữu ích vào metadata có cấu trúc.
  3. Loại bỏ hoàn toàn nhiễu thuần túy.

Ý tưởng quan trọng rất đơn giản: quan sát nhiều hơn, lập chỉ mục ít hơn.

Vì sao luồng hook bỏ qua Alloy

Luồng hook đã được định hình sẵn cho Loki.

Vào lúc send_event.py đẩy một sự kiện, chúng tôi đã quyết định sẵn trường nào xứng đáng được xử lý như nhãn và trường nào thuộc về phần thân JSON có cấu trúc. Luồng đó đi thẳng đến cổng OTLP của Grafana Cloud thay vì đi qua thêm một lượt Alloy nữa.

Vì vậy hệ thống có một sự phân chia lao động rõ ràng:

  • Alloy thuần hóa luồng OTEL gốc thô.
  • Việc làm giàu hook làm cho các sự kiện ngữ nghĩa có thể truy vấn được.

Điều đó giữ kiến trúc đơn giản hơn so với việc cố ép mọi thứ đi qua một đường duy nhất.

Dashboard thực sự cho thấy gì

Ảnh chụp màn hình bên dưới là từ một trong những dashboard quan sát đứng sau quy trình agent của chúng tôi. Đó không phải là một benchmark, và các con số chỉ là một lát cắt tại một thời điểm. Điểm mấu chốt là hình dạng của dữ liệu: activity feed, lệnh gọi công cụ, thất bại, prompt, và các phân tích chi tiết theo agent, công cụ có sẵn, việc sử dụng MCP, lệnh shell, và skill.

Phần hữu ích là dashboard này sống trên cùng một stack Grafana với phần còn lại của telemetry agent. Chúng tôi có thể lọc theo agent nguồn và họ công cụ, và nhìn xuyên suốt các runtime mà không cần phát minh ra một câu chuyện quan sát khác nhau cho mỗi hệ thống.

Dashboard Grafana hiển thị activity feed, số lượng lệnh gọi công cụ, thất bại, prompt, và các phân tích chi tiết theo agent, công cụ có sẵn, việc sử dụng MCP, lệnh CLI, và skill cho một quy trình agent.
Một trong những dashboard trực tiếp đứng sau quy trình agent của chúng tôi. Ảnh chụp màn hình này chỉ là một lát cắt của một stack Grafana dùng chung, cũng nhận telemetry từ các runtime và hệ thống agent khác của chúng tôi. Nhấp vào ảnh để xem phiên bản độ phân giải đầy đủ.

Vì sao trace quan trọng hơn vẻ ngoài ban đầu của chúng

Log cho chúng tôi biết loại công việc nào đã xảy ra. Trace cho chúng tôi biết công việc đó diễn ra như thế nào theo thời gian.

Sự khác biệt đó quan trọng trong các hệ thống agent vì “chậm” quá thô để có ích.

Một trace có thể cho chúng tôi biết liệu sự đau đầu đến từ:

  • độ trễ mô hình
  • thời gian thực thi công cụ
  • các lần thử lại lặp đi lặp lại
  • một tương tác MCP đặc biệt tốn kém
  • một dải dài các thao tác nhỏ trông vô hại khi xét riêng lẻ

Trong thực tế chúng tôi dùng luồng hook và trace Tempo cùng nhau.

  • Log hook trả lời: loại việc gì đã xảy ra?
  • Trace trả lời: thời gian đã đi đâu?

Sự kết hợp đó là thứ biến quan sát từ một dashboard thành một lời giải thích.

Codex khớp vào đâu

Codex là một phần của cùng một stack, nhưng nó không giống hệt Claude Code.

Đối với Codex, chúng tôi kết nối hai phần:

  • OTEL gốc từ Codex vào Alloy
  • Một notify webhook vào codex_notify.py, ánh xạ các lượt hoàn tất thành cùng một schema Loki mà chúng tôi dùng cho các sự kiện hook

Điều đó cho chúng tôi một bộ lọc thống nhất như agent_tool="codex-cli" bên trong cùng một luồng log.

Lời cảnh báo thành thật: payload notify của Codex hiện đang mỏng hơn payload hook của Claude Code vì nó không phải là cùng loại bề mặt tích hợp. Trong thiết lập hiện tại của chúng tôi, các lượt hoàn tất của Codex có thể được chuẩn hóa vào schema dùng chung, nhưng việc trích xuất chi tiết theo từng công cụ vẫn tốt hơn trong luồng OTEL gốc so với cầu nối notify.

Đó không phải là lý do để tránh viết bài này. Đó chính là ý nghĩa của bài viết. Các hệ thống quan sát thực sự được lắp ráp từ những tín hiệu không hoàn hảo.

Grafana qua MCP thay đổi cuộc chơi

Sự thay đổi lớn hơn là Grafana không chỉ là một nơi con người ghé thăm trong trình duyệt.

Trong repo này chúng tôi cũng phơi bày Grafana qua MCP. Điều đó nghĩa là một agent có thể truy vấn trực tiếp Loki, Prometheus, và Tempo thay vì chờ một con người kiểm tra dashboard thủ công trước.

Điều đó biến quan sát thành một đầu vào chủ động cho quy trình làm việc.

Một agent có thể hỏi:

  • Họ công cụ nào thất bại nhiều nhất trong giờ qua?
  • Máy chủ MCP nào chiếm ưu thế trong một phiên?
  • Các thay đổi gần đây có giảm thất bại công cụ, hay chỉ đơn giản là chuyển công việc sang các đường nặng shell hơn với cùng những lỗi cũ?
  • Trace nào cho thấy độ trễ cao nhất hoặc các lần thử lại lặp đi lặp lại?

Một khi bạn có được điều đó, bạn đã rất gần với một vòng lặp tự cải thiện.

Từ Dashboard đến vòng lặp phản hồi

Đây là phần chúng tôi thấy thú vị nhất.

Một khi stack quan sát có thể truy vấn được từ tầng agent, telemetry không còn là một bề mặt báo cáo thụ động nữa mà trở thành một tín hiệu điều khiển.

Vòng lặp trông như thế này:

  1. Hoạt động agent phát ra trace, metric, và log hook đã làm giàu.
  2. Grafana lưu trữ bằng chứng trong Loki, Tempo, và Prometheus ở những nơi có metric.
  3. Agent truy vấn bằng chứng đó qua Grafana MCP.
  4. Hệ thống xác định sự pha trộn công cụ tệ, skill dễ vỡ, định tuyến yếu, hoặc quy trình nặng shell liên tục tạo ra những lỗi có thể tránh được.
  5. Agent hoặc người vận hành điều chỉnh prompt, cấu hình agent, mô tả skill, quy tắc định tuyến, hoặc quyền truy cập công cụ.
  6. Phiên tiếp theo tạo ra một hình dạng telemetry mới, và chu trình lặp lại.

Đó là cách bạn đi từ “dashboard thú vị” đến “hệ thống cải thiện có thể đo lường được.”

Mục tiêu không phải là tối đa hóa một danh mục công cụ. Đó là đạt được đúng sự pha trộn giữa CLI, công cụ có sẵn, lệnh gọi MCP, và skill cho công việc thực sự đang được thực hiện.

Điều này cho phép chúng tôi trả lời gì

Một khi cả hai runtime cùng nằm trên một stack Grafana, chúng tôi có thể trả lời các câu hỏi vận hành nhanh hơn nhiều:

  • Thất bại có tập trung vào một họ công cụ không?
  • Các quy trình nặng shell có đang tạo ra những lỗi có thể tránh được ở nơi lẽ ra nên có một công cụ cấp cao hơn không?
  • Máy chủ MCP nào đang gánh phần lớn khối lượng công việc?
  • Chúng ta có đang trả tiền cho hoạt động agent không tạo ra tiến bộ có ý nghĩa hay không?
  • Một phiên có kém lành mạnh vì mô hình, công cụ, hay tầng điều phối?

Điều này đặc biệt hữu ích trong các quy trình đa agent, nơi “agent đang bận” gần như không nói lên điều gì cả.

Nếu một chuyên gia liên tục được điều động và tạo ra tỷ lệ thất bại cao, đó là một vấn đề định tuyến hoặc định hình prompt.

Nếu một máy chủ MCP chiếm ưu thế trong mọi lệnh gọi, đó có thể là một kiến trúc tốt hoặc là dấu hiệu mọi thứ khác đều là gánh nặng chết.

Nếu thất bại công cụ tăng vọt trong khi chi phí vẫn cao, bạn có một vấn đề vận hành, không phải vấn đề chất lượng.

Nếu công việc shell liên tục thất bại theo những cách có thể dự đoán và tránh được ở nơi lẽ ra nên có một công cụ cấp cao hơn, đó là một tín hiệu sản phẩm.

Nếu một skill liên tục kích hoạt nhưng không cải thiện kết quả, đó là một tín hiệu prompt hoặc định tuyến.

Bài học thực sự

Bài học sâu sắc hơn ở đây là quan sát agent cần cả telemetry runtime lẫn telemetry quy trình làm việc.

Telemetry runtime cho bạn biết hệ thống đã làm gì.

Telemetry quy trình làm việc cho bạn biết agent nghĩ nó đang làm gì.

Chúng tôi cần cả hai.

Nếu bạn chỉ giữ trace và bộ đếm, bạn bỏ lỡ tầng ngữ nghĩa. Nếu bạn chỉ giữ sự kiện hook, bạn bỏ lỡ độ trễ, span, và bức tranh runtime rộng hơn.

Và nếu bạn giữ cả hai nhưng không bao giờ đưa chúng trở lại tầng agent, bạn có giám sát, không phải sự thích nghi.

Sự kết hợp đó là thứ khiến hệ thống đủ có thể giải thích được để vận hành và đủ có thể tinh chỉnh được để cải thiện.

Những gì vẫn chưa hoàn hảo

Vẫn còn những góc cạnh thô ráp.

  • Không phải mọi sự kiện hook đều bao gồm dữ liệu thời lượng và token mà chúng tôi mong muốn.
  • Một số khung nhìn thời gian tốt nhất vẫn đến từ trace Tempo, không phải log hook.
  • Codex hiện nay kém phong phú về mặt ngữ nghĩa hơn Claude Code trong luồng sự kiện đã làm giàu.
  • Ảnh chụp màn hình dashboard là một bề mặt vận hành trực tiếp, không phải một tác phẩm marketing bóng bẩy.

Điểm cuối cùng đó là có chủ đích. Chúng tôi thà cho xem bảng điều khiển thật còn hơn giả vờ rằng các hệ thống agent tự giải thích được bằng phép màu.

Vì sao điều này quan trọng với Maguyva

Maguyva là về việc cho agent trí tuệ mã nguồn tốt hơn. Nhưng một khi agent thực sự đang làm việc hữu ích, một yêu cầu mới xuất hiện ngay lập tức: bạn cần thấy chúng đang hành xử như thế nào.

Chất lượng tìm kiếm, chất lượng định tuyến, việc chọn công cụ, và hiệu quả ngữ cảnh đều trở thành những vấn đề có thể quan sát được.

Đó là lý do chúng tôi nghĩ điều này đáng để viết. Stack agent của tương lai không chỉ là prompt và công cụ. Đó là prompt, công cụ, và tầng đo lường cho bạn biết liệu toàn bộ hệ thống có đang hoạt động hay không.

Nếu bạn đang xây dựng các quy trình agent nghiêm túc, quan sát không phải là hạ tầng tùy chọn. Nó là một phần của sản phẩm.

Đọc thêm liên quan

Thêm từ nhật ký xây dựng Maguyva

Tự cải thiện đệ quy theo ngôn ngữ: Mài giũa trí tuệ mã nguồn trên khoảng 280 ngôn ngữ_

Chúng tôi hỗ trợ trí tuệ mã nguồn cho khoảng 280 ngôn ngữ. Không con người nào có thể tự tay rà soát hết được. Vì vậy chúng tôi xây dựng một vòng lặp tự cải thiện đệ quy theo ngôn ngữ — kiểm tra ngẫu nhiên, dùng LLM làm giám khảo, sửa từng thứ một, xác thực lại — và chạy nó với một đội quân agent cách ly cho đến khi việc trích xuất thực sự đúng, chứ không chỉ xanh (green).

[Kiến trúc][Ngôn ngữ][Tác nhân]