agent-governance/tests/mocks/mock_blackboard.py
profit 77655c298c Initial commit: Agent Governance System Phase 8
Phase 8 Production Hardening with complete governance infrastructure:

- Vault integration with tiered policies (T0-T4)
- DragonflyDB state management
- SQLite audit ledger
- Pipeline DSL and templates
- Promotion/revocation engine
- Checkpoint system for session persistence
- Health manager and circuit breaker for fault tolerance
- GitHub/Slack integrations
- Architectural test pipeline with bug watcher, suggestion engine, council review
- Multi-agent chaos testing framework

Test Results:
- Governance tests: 68/68 passing
- E2E workflow: 16/16 passing
- Phase 2 Vault: 14/14 passing
- Integration tests: 27/27 passing

Coverage: 57.6% average across 12 phases

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-23 22:07:06 -05:00

395 lines
12 KiB
Python

"""
MockBlackboard - Simulates shared memory for multi-agent coordination testing.
Provides deterministic blackboard operations for testing multi-agent
scenarios without DragonflyDB.
"""
from typing import Dict, List, Any, Optional, Set
from dataclasses import dataclass, field
from datetime import datetime
from enum import Enum
import json
import threading
class BlackboardSection(Enum):
"""Standard blackboard sections"""
PROBLEM = "problem"
SOLUTIONS = "solutions"
PROGRESS = "progress"
CONSENSUS = "consensus"
@dataclass
class BlackboardEntry:
"""A single entry in a blackboard section"""
key: str
value: Any
author: str
timestamp: datetime
version: int = 1
@dataclass
class ConsensusVote:
"""A vote in the consensus section"""
agent: str
proposal_id: str
vote: str # ACCEPT, REJECT, ABSTAIN
reasoning: str
timestamp: datetime
class MockBlackboard:
"""
Mock Blackboard implementation for multi-agent testing.
Simulates:
- Section-based shared memory
- Read/write operations
- Consensus voting
- History tracking
- Conflict detection
"""
def __init__(self, task_id: str = "test-task"):
self.task_id = task_id
self._sections: Dict[str, Dict[str, BlackboardEntry]] = {
section.value: {} for section in BlackboardSection
}
self._votes: List[ConsensusVote] = []
self._history: List[Dict[str, Any]] = []
self._watchers: Dict[str, List[callable]] = {}
self._lock = threading.Lock()
def write(self, section: str, key: str, value: Any, author: str) -> bool:
"""
Write to a blackboard section.
Args:
section: Section name (problem, solutions, progress, consensus)
key: Entry key
value: Entry value
author: Agent ID writing the entry
"""
with self._lock:
if section not in self._sections:
self._sections[section] = {}
existing = self._sections[section].get(key)
version = existing.version + 1 if existing else 1
entry = BlackboardEntry(
key=key,
value=value,
author=author,
timestamp=datetime.utcnow(),
version=version
)
self._sections[section][key] = entry
# Record in history
self._history.append({
"operation": "write",
"section": section,
"key": key,
"value": value,
"author": author,
"version": version,
"timestamp": datetime.utcnow().isoformat()
})
# Notify watchers
self._notify_watchers(section, key, value, author)
return True
def read(self, section: str, key: str = None) -> Optional[Any]:
"""
Read from a blackboard section.
Args:
section: Section name
key: Optional specific key (if None, returns all entries)
"""
with self._lock:
if section not in self._sections:
return None
if key:
entry = self._sections[section].get(key)
return entry.value if entry else None
else:
return {
k: v.value
for k, v in self._sections[section].items()
}
def read_with_metadata(self, section: str, key: str) -> Optional[Dict[str, Any]]:
"""Read entry with full metadata"""
with self._lock:
if section not in self._sections:
return None
entry = self._sections[section].get(key)
if not entry:
return None
return {
"key": entry.key,
"value": entry.value,
"author": entry.author,
"version": entry.version,
"timestamp": entry.timestamp.isoformat()
}
def delete(self, section: str, key: str, author: str) -> bool:
"""Delete an entry from a section"""
with self._lock:
if section not in self._sections:
return False
if key not in self._sections[section]:
return False
del self._sections[section][key]
self._history.append({
"operation": "delete",
"section": section,
"key": key,
"author": author,
"timestamp": datetime.utcnow().isoformat()
})
return True
def list_keys(self, section: str) -> List[str]:
"""List all keys in a section"""
with self._lock:
if section not in self._sections:
return []
return list(self._sections[section].keys())
# === Consensus Operations ===
def submit_proposal(self, proposal_id: str, proposal: Any, author: str) -> bool:
"""Submit a proposal for consensus"""
return self.write(
BlackboardSection.SOLUTIONS.value,
proposal_id,
{"proposal": proposal, "status": "pending", "votes": []},
author
)
def vote(self, proposal_id: str, agent: str, vote: str, reasoning: str = "") -> bool:
"""
Vote on a proposal.
Args:
proposal_id: ID of the proposal
agent: Voting agent ID
vote: ACCEPT, REJECT, or ABSTAIN
reasoning: Optional explanation
"""
with self._lock:
if vote not in ["ACCEPT", "REJECT", "ABSTAIN"]:
return False
consensus_vote = ConsensusVote(
agent=agent,
proposal_id=proposal_id,
vote=vote,
reasoning=reasoning,
timestamp=datetime.utcnow()
)
self._votes.append(consensus_vote)
# Update proposal with vote
solutions = self._sections.get(BlackboardSection.SOLUTIONS.value, {})
if proposal_id in solutions:
proposal_entry = solutions[proposal_id]
proposal_data = proposal_entry.value
if "votes" not in proposal_data:
proposal_data["votes"] = []
proposal_data["votes"].append({
"agent": agent,
"vote": vote,
"reasoning": reasoning
})
self._history.append({
"operation": "vote",
"proposal_id": proposal_id,
"agent": agent,
"vote": vote,
"timestamp": datetime.utcnow().isoformat()
})
return True
def check_consensus(self, proposal_id: str, required_agents: List[str] = None) -> Dict[str, Any]:
"""
Check consensus status for a proposal.
Returns:
{
"reached": bool,
"votes": {"ACCEPT": n, "REJECT": n, "ABSTAIN": n},
"missing_votes": [agent_ids],
"result": "ACCEPT"|"REJECT"|"PENDING"
}
"""
with self._lock:
proposal_votes = [v for v in self._votes if v.proposal_id == proposal_id]
vote_counts = {"ACCEPT": 0, "REJECT": 0, "ABSTAIN": 0}
voted_agents = set()
for v in proposal_votes:
vote_counts[v.vote] += 1
voted_agents.add(v.agent)
missing = []
if required_agents:
missing = [a for a in required_agents if a not in voted_agents]
all_voted = len(missing) == 0 if required_agents else True
# Determine result
if not all_voted:
result = "PENDING"
elif vote_counts["ACCEPT"] > vote_counts["REJECT"]:
result = "ACCEPT"
elif vote_counts["REJECT"] > vote_counts["ACCEPT"]:
result = "REJECT"
else:
result = "TIE"
return {
"reached": all_voted and result in ["ACCEPT", "REJECT"],
"votes": vote_counts,
"missing_votes": missing,
"result": result
}
# === Progress Tracking ===
def update_progress(self, agent: str, phase: str, step: str, details: Dict = None) -> bool:
"""Update agent progress"""
return self.write(
BlackboardSection.PROGRESS.value,
agent,
{
"phase": phase,
"step": step,
"details": details or {},
"updated_at": datetime.utcnow().isoformat()
},
agent
)
def get_all_progress(self) -> Dict[str, Any]:
"""Get progress for all agents"""
return self.read(BlackboardSection.PROGRESS.value) or {}
# === Watchers ===
def watch(self, section: str, callback: callable):
"""Watch a section for changes"""
with self._lock:
if section not in self._watchers:
self._watchers[section] = []
self._watchers[section].append(callback)
def unwatch(self, section: str, callback: callable = None):
"""Remove a watcher"""
with self._lock:
if section not in self._watchers:
return
if callback:
self._watchers[section] = [
cb for cb in self._watchers[section] if cb != callback
]
else:
del self._watchers[section]
def _notify_watchers(self, section: str, key: str, value: Any, author: str):
"""Notify watchers of a change (called with lock held)"""
callbacks = self._watchers.get(section, []).copy()
# Release lock before calling callbacks
for callback in callbacks:
try:
callback(section, key, value, author)
except Exception:
pass
# === Test Helpers ===
def reset(self):
"""Reset all state for testing"""
with self._lock:
self._sections = {
section.value: {} for section in BlackboardSection
}
self._votes.clear()
self._history.clear()
self._watchers.clear()
def get_history(self) -> List[Dict[str, Any]]:
"""Get operation history for assertions"""
with self._lock:
return self._history.copy()
def get_all_state(self) -> Dict[str, Any]:
"""Get complete state for test assertions"""
with self._lock:
return {
"sections": {
section: {
k: {"value": v.value, "author": v.author, "version": v.version}
for k, v in entries.items()
}
for section, entries in self._sections.items()
},
"votes": [
{"proposal": v.proposal_id, "agent": v.agent, "vote": v.vote}
for v in self._votes
]
}
def inject_state(self, section: str, entries: Dict[str, Any], author: str = "test"):
"""Inject state for testing"""
with self._lock:
if section not in self._sections:
self._sections[section] = {}
for key, value in entries.items():
self._sections[section][key] = BlackboardEntry(
key=key,
value=value,
author=author,
timestamp=datetime.utcnow()
)
def simulate_conflict(self, section: str, key: str, agents: List[str],
values: List[Any]) -> List[Dict[str, Any]]:
"""
Simulate a write conflict for testing.
Multiple agents write to the same key in rapid succession.
Returns the sequence of writes.
"""
writes = []
for agent, value in zip(agents, values):
self.write(section, key, value, agent)
entry = self._sections[section][key]
writes.append({
"agent": agent,
"value": value,
"version": entry.version
})
return writes