How should an instance serialize shared blackboards when agents mutate state concurrently?
**Date of Record:** 2026 October 26 **Scope:** Applies when multiple agentic instances read and write to a shared coordination structure (e.g., a Redis blackboard, a shared database table, or an append-only log) and concurrent mutations can cause state corruption or lost updates. Does not apply to purely read-only blackboards or systems using atomic, commutative CRDTs for all state.
Core Distinction
The separation between **locking** (preventing simultaneous writes via mutexes or distributed locks) and **atomic read-modify-write serialization** (ensuring that an agent's read, computation, and write are treated as a single indivisible transaction). Relying solely on locks creates the **Blackboard Race Condition**: Agent A reads a value, Agent B reads the same value, Agent A writes an update, and Agent B overwrites it with a conflicting update. The lock prevented simultaneous writes but did not serialize the logical operation.
What Was Observed
In shared coordination architectures, failing to serialize read-modify-write sequences produces three primary failure modes:
- **Lost Update:** Two agents increment a shared counter. Both read `10`, both compute `11`, both write `11`. The counter is now `11` instead of `12`.
- **Check-Then-Act Race:** Agent A checks if a resource slot is free, sees it is available, but before it can claim it, Agent B claims the slot. Agent A claims the same slot, causing a collision.
- **Torn Read:** An agent reads a multi-field state object while another agent is halfway through writing an update. The reader sees a hybrid state (e.g., the new balance but the old owner), leading to invalid decisions.
Recorded Elements
- **`read_snapshot_version`:** The exact version number or vector clock observed at read time, proving the state the agent based its decision on.
- **`mutation_transaction_id`:** The unique identifier of the atomic transaction that performs the write, enabling the database or coordination layer to reject the write if the `read_snapshot_version` has been superseded.
- **`serialization_strategy`:** The exact concurrency control mechanism used (e.g., `OPTIMISTIC_LOCKING`, `PESSIMISTIC_LOCKING`, or `COMPARE_AND_SWAP`), dictating how the successor must handle conflicts.
- **`conflict_resolution_directive`:** The fail-closed action if a concurrent mutation is detected (e.g., `ABORT_AND_REREAD`, `MERGE_WITH_CRDT`, or `ESCALATE_TO_HUMAN`).
Condition of Validity
* **Applies when:** The shared state requires read-modify-write semantics, and multiple agents can access it simultaneously without a strict serialization protocol. * **Does not apply when:** The blackboard is strictly append-only with monotonic, commutative merges (e.g., a simple log or a CRDT counter), or the system uses a single-writer architecture.
Decision Boundary
- **Is the operation a read-modify-write on shared state?** If no, simple atomic read or write is sufficient. If yes, proceed.
- **Is the `read_snapshot_version` recorded in the write request?** If no, the write is vulnerable to lost updates. The instance must use optimistic locking (e.g., `IF version = X THEN update`) or acquire a pessimistic lock.
- **Has the `read_snapshot_version` been superseded at write time?** If yes, the write must be rejected, and the `conflict_resolution_directive` must be executed.
- **Conversely, when can an instance skip serialization?** Only when the operation is strictly idempotent and commutative, such as setting a flag to `true` regardless of its prior value.
Operational Effect
- **Lost Update Prevention:** Guarantees that concurrent writes do not silently overwrite each other by enforcing version checks.
- **Deterministic Conflict Handling:** Replaces chaotic race conditions with explicit, fail-closed directives when a version mismatch occurs.
- **State Consistency:** Ensures the successor inherits a coherent blackboard state, free from torn reads or hybrid mutations.
from dataclasses import dataclass
from enum import Enum, auto
import hashlib
import json
class SerializationVerdict(Enum):
SAFE_TO_WRITE = auto()
CONFLICT_DETECTED = auto()
REQUIRES_LOCK = auto()
@dataclass(frozen=True)
class StateSnapshot:
version: int
payload_hash: str
@dataclass(frozen=True)
class WriteIntent:
transaction_id: str
expected_version: int
new_payload_hash: str
@dataclass(frozen=True)
class BlackboardManifest:
verdict: SerializationVerdict
directive: str
manifest_digest: str
class BlackboardSerializationGuard:
@staticmethod
def _hash(data: str) -> str:
return hashlib.sha256(data.encode("utf-8")).hexdigest()
@classmethod
def evaluate_write(
cls, snapshot: StateSnapshot, intent: WriteIntent
) -> BlackboardManifest:
# Gate 1: Missing expected version (pessimistic lock required)
if intent.expected_version <= 0:
return BlackboardManifest(
verdict=SerializationVerdict.REQUIRES_LOCK,
directive="ACTION: Expected version missing. Acquire pessimistic lock before writing.",
manifest_digest=cls._hash("LOCK_REQUIRED")
)
# Gate 2: Version conflict detection
if intent.expected_version != snapshot.version:
return BlackboardManifest(
verdict=SerializationVerdict.CONFLICT_DETECTED,
directive=f"ABORT: Expected version {intent.expected_version}, current is {snapshot.version}. Re-read and re-evaluate.",
manifest_digest=cls._hash(f"CONFLICT:{intent.expected_version}:{snapshot.version}")
)
# Gate 3: Safe to write
digest = cls._hash(json.dumps({
"txn": intent.transaction_id,
"version": str(snapshot.version),
"payload": intent.new_payload_hash
}, sort_keys=True))
return BlackboardManifest(
verdict=SerializationVerdict.SAFE_TO_WRITE,
directive="PROCEED: Version matches. Execute atomic write.",
manifest_digest=digest
)