Mở đầu: Một kiến trúc AI từ thập niên 1980 vẫn sống trong framework agent 2026

Bài 10 cho các agent giao tiếp qua message điểm-tới-điểm — Planner biết chính xác phải gửi cho Coder, Coder biết phải gửi cho Critic. Nhưng nếu có 10 agent thay vì 3, mỗi agent phải "biết tên" 9 agent còn lại để gửi đúng message — không mở rộng được. Giải pháp cho vấn đề này đã có từ những năm 1980, lâu trước khi LLM tồn tại: blackboard pattern.

Bài này mở rộng thêm aisys-orchestrator.js với class Blackboard — và quan trọng hơn, phơi bày một lớp lỗi hoàn toàn mới chỉ xuất hiện khi nhiều agent cùng ghi vào 1 nơi: race condition.


📚 Điều kiện tiên quyết
Nên đọc Bài 10 trước để so sánh trực tiếp 2 mô hình giao tiếp: message passing (Bài 10) vs blackboard (bài này).

1. Kiến Trúc Blackboard Cổ Điển

Ý tưởng gốc (từ hệ thống AI symbolic HEARSAY-II thập niên 1970–80): nhiều "chuyên gia" (knowledge sources) độc lập, không biết về nhau, cùng nhìn vào MỘT không gian dữ liệu chung (blackboard) — mỗi chuyên gia đọc phần thông tin liên quan đến mình, đóng góp thêm hiểu biết, và cứ thế bài toán được giải dần từng mảnh mà không cần 1 nhạc trưởng điều phối từng bước:

blackboard-core.js
class Blackboard {
  constructor() { this.state = {}; this.log = []; }
  read(key) {
    const entry = this.state[key];
    return entry ? entry.value : undefined;
  }
  write(agentName, key, value) {
    this.state[key] = { value, owner: agentName };
    this.log.push({ type: "write", agent: agentName, key, value });
  }
}
// Agent A không cần biết Agent B tồn tại — cả hai chỉ cần biết TÊN KEY chung
// ("draft_answer", "task_status"...) để phối hợp gián tiếp qua blackboard.

2. So Sánh Với Shared-Memory Kiểu CrewAI/AutoGen

Các framework agent hiện đại đều có một biến thể của ý tưởng này — CrewAI gọi là "shared context", AutoGen truyền trạng thái qua lịch sử group chat mà mọi agent cùng thấy. Điểm chung: một "sự thật dùng chung" (shared source of truth) thay vì mỗi agent giữ bản sao riêng.

Tiêu chí Message Passing (Bài 10) Blackboard (bài này)
Agent có cần biết tên nhau? Có — gửi đích danh Không — chỉ cần biết tên KEY chung
Thêm agent mới có dễ không? Khó hơn — phải sửa logic routing Dễ hơn — agent mới tự đọc key liên quan
Rủi ro đặc trưng Message thất lạc/sai đích Race condition khi ghi đồng thời (mục 3)

3. Race Condition Khi Nhiều Agent Ghi Cùng Lúc

Đây là cái giá phải trả cho sự tiện lợi "không cần biết tên nhau": nếu 2 agent cùng ghi vào 1 key mà không có cơ chế phối hợp, agent ghi SAU sẽ âm thầm xoá mất công sức của agent ghi TRƯỚC — không có lỗi nào được ném ra, không ai biết dữ liệu đã mất:

race-condition.js
blackboard.write("AgentA", "draft_answer", "Bản nháp A...");
// ... AgentB không hay biết AgentA vừa ghi ...
blackboard.write("AgentB", "draft_answer", "Bản nháp B...");
// Kết quả: "Bản nháp A" biến mất VĨNH VIỄN, không log lỗi, không cảnh báo.

Chiến lược khắc phục phổ biến nhất: khoá (lock) — agent ghi trước "khoá" key lại, agent ghi sau bị từ chối rõ ràng thay vì âm thầm ghi đè, buộc phải đọc lại giá trị mới nhất trước khi thử lại:

write-with-lock.js
write(agentName, key, value, { requireLock = false } = {}) {
  const existing = this.state[key];
  if (requireLock && this.locks.has(key) && existing && existing.owner !== agentName) {
    return { accepted: false, reason: `Key "${key}" đang bị khoá bởi ${existing.owner}` };
  }
  this.state[key] = { value, owner: agentName };
  if (requireLock) this.locks.add(key);
  return { accepted: true };
}

4. Cạm Bẫy: Blackboard Phình To & Stale Read

🕳️ Cạm bẫy 1: Blackboard phình to không kiểm soát
Vì MỌI agent đều có thể ghi vào blackboard mà không cần xin phép ai, một hệ thống chạy lâu dễ tích luỹ hàng trăm key không còn ai dùng tới — không có cơ chế "dọn dẹp" tự nhiên như trong message passing (nơi message chỉ tồn tại tạm thời). Cần chủ động đặt TTL (thời gian sống) hoặc quy ước dọn key theo giai đoạn task, nếu không blackboard sẽ trở thành một "biến toàn cục" khổng lồ khó kiểm soát.
⚠️ Cạm bẫy 2: Stale read — agent hành động dựa trên dữ liệu đã lỗi thời
Ngay cả khi không mất dữ liệu (nhờ lock), một agent đã ĐỌC giá trị cũ TRƯỚC khi agent khác ghi giá trị mới vẫn có thể tiếp tục hành động dựa trên bản đọc cũ đó (nó không tự động "biết" giá trị vừa đổi) — đây gọi là stale read. Demo mục 5 minh hoạ trực tiếp: AgentC đọc bản nháp A, sau đó AgentB ghi đè thành bản nháp B, nhưng AgentC vẫn đang cầm bản nháp A trong "đầu" nó.

5. Thực hành tương tác: Blackboard Lab

Bật/tắt "khoá trước khi ghi" và chạy lại kịch bản để so sánh hai hậu quả: ghi đè âm thầm (mất dữ liệu) so với từ chối rõ ràng (an toàn nhưng agent phải tự retry):

📋 Blackboard Lab

                
aisys-blackboard-lab.js (trích)
import { Blackboard } from "./aisys-orchestrator.js";
const bb = new Blackboard();
bb.write("AgentA", "draft_answer", "...", { requireLock });
const cached = bb.read("draft_answer"); // AgentC lưu bản cache
bb.write("AgentB", "draft_answer", "...", { requireLock }); // race condition xảy ra ở đây

Bài 12 sẽ đi sâu hơn vào 1 loại lỗi phối hợp khác — deadlock, khi 2 agent CHỜ NHAU thay vì ghi đè nhau — và cách circuit-breaker phá vỡ tình huống đó.

Tải file code thực hành minh họa bài học

File JavaScript aisys-orchestrator.js — class Blackboard vừa thêm ở bài này, cùng aisys-blackboard-lab.js wiring demo:

Tải về aisys-orchestrator.js

📖 Tài liệu tham khảo

Bài viết liên quan trong series

Bài 10: Kiến Trúc Multi-Agent — Vai Trò & Giao Tiếp Bài 12: Orchestration Nâng Cao — Xung Đột & Deadlock Quay lại Lộ trình Kỹ Thuật Hệ Thống AI

Bình luận