DEV Community

Oluwaseyi Ogunsola
Oluwaseyi Ogunsola

Posted on

Designing agent-broker: Collision-Safe Redis Keys, Budget Integrity, and Zero-Trust Delegation

A permission broker for concurrent AI agents must do more than check whether a request is allowed.

It needs to coordinate shared budgets, enforce concurrency limits, manage reservations, and prevent delegated agents from bypassing the constraints imposed on their parent or root agent.

These responsibilities become difficult when several processes operate concurrently and when external failures leave shared state in unexpected conditions.

While building agent-broker, I focused on several less obvious failure modes: ambiguous composite Redis keys, budget pools that disappear before reservations are resolved, and caller-controlled identity or delegation metadata.

This article examines the design decisions behind those cases.

  1. Composite Redis Keys Need Unambiguous Boundaries

Redis keys are often constructed by concatenating several values with a delimiter.

For example:

"concurrency:{target}:{budgetKey}"

This is convenient until the values themselves contain the delimiter.

Suppose two requests use these inputs:

  • "target = "a:b"" and "budgetKey = "c""
  • "target = "a"" and "budgetKey = "b:c""

A naive delimiter-based scheme can serialize both combinations into the same string:

"concurrency🅰️b:c"

The application intended these to represent different logical resources, but Redis sees identical keys.

In a system that tracks concurrency or budget state by key, this ambiguity can cause unrelated operations to share counters or interfere with one another.

Length-prefixing the segments

agent-broker addresses this problem through explicit length-prefixing.

Each caller-supplied string segment is encoded as:

"seg(value) = "${value.length}:${value}""

For example:

  • "target = "a:b"" becomes "3🅰️b"
  • "budgetKey = "c"" becomes "1:c"

The resulting composite key is:

"concurrency:3:a:b1:c"

For the second input:

  • "target = "a"" becomes "1:a"
  • "budgetKey = "b:c"" becomes "3🅱️c"

The key becomes:

"concurrency:1:a3🅱️c"

The segment boundaries remain distinguishable even when values contain colons.

This is the central property of the approach: serialization preserves the boundaries between components rather than relying on the assumption that their contents will never contain a separator.

The implementation is documented in ADR-0008.

  1. Handling a Missing Budget Pool During Reservation Resolution

A second problem arises when a budget pool disappears while an agent still has an outstanding reservation.

agent-broker uses budget pools that do not have a TTL by design, allowing them to outlive individual agent lifetimes. However, the absence of a TTL does not make a key impossible to delete. An operator, an administrative operation, or an unexpected Redis state change can still remove it.

This matters when resolving a reservation.

Redis's "INCRBY" command creates a missing key with the requested increment. That behavior can be dangerous if the increment is intended to refund previously reserved budget.

If the original pool has disappeared, applying a refund without checking the pool's existence can recreate a budget key whose state no longer represents the original pool.

The resulting value may look valid at the Redis level while being inconsistent with the broker's intended accounting.

Detecting the missing pool atomically

agent-broker's reservation-resolution Lua script checks whether the budget pool exists before processing a refund.

If the pool is missing, the script skips the refund instead of allowing "INCRBY" to recreate the key.

The reservation still resolves its other responsibilities: concurrency slots are released and applicable expiring entries are cleaned up. This avoids leaving unrelated concurrency state stranded merely because the budget pool is missing.

The response explicitly reports:

"poolMissing: true"

That signal gives the caller or observability layer a way to detect the unexpected state and investigate it.

There is an important distinction here: the broker can detect that the pool is missing, but that observation alone does not establish whether it was deleted intentionally, evicted, or removed by another mechanism.

The design prevents a missing pool from being silently recreated through the refund path while keeping the reservation lifecycle recoverable.

This behavior is documented in ADR-0003.

  1. Broker-Issued Identity and Computed Delegation Depth

A permission system loses much of its value if callers can supply the identity or hierarchy metadata used to decide whether their requests are allowed.

Consider a delegation tree with a maximum permitted depth.

If a caller can register a child agent and claim that its depth is zero, the broker may evaluate that child against the wrong position in the hierarchy.

Similarly, accepting caller-supplied parent or root identifiers without validating them against stored relationships can undermine the intended delegation boundaries.

The broker owns identity and hierarchy

agent-broker applies three related rules:

  1. Agent identity UUIDs are issued by the broker during registration rather than accepted from callers.
  2. Delegation depth, parent ID, and root ID are derived by the broker from the referenced parent's stored state.
  3. Callers attempting to supply protected fields such as "depth", "rootId", "budgetKey", or "agentId" during child registration receive a "BrokerArgumentError".

These decisions make the broker responsible for establishing identity and hierarchy instead of trusting clients to describe their own permissions.

The distinction is fundamental: clients can request an operation, but they should not be allowed to redefine the security context in which that operation is evaluated.

This is one part of the broker's trust-boundary design, not a claim that these rules alone constitute a complete security model.

The relevant decisions are recorded in ADR-0012.

  1. The Broader Design Principle

These three problems look unrelated at first:

  • Composite keys can collapse distinct resources into the same identifier.
  • A missing budget pool can be recreated by an otherwise ordinary Redis operation.
  • Caller-controlled hierarchy metadata can undermine permission enforcement.

But each problem has the same underlying structure: the system is relying on an assumption that may not hold when inputs are adversarial or state changes unexpectedly.

The response is to make the assumption explicit and enforce the relevant invariant at the layer that owns it.

For key construction, that means unambiguous serialization.

For reservation resolution, that means checking shared state within the atomic script before applying a refund.

For delegation, that means deriving identity and hierarchy from broker-controlled state rather than trusting caller-provided values.

These mechanisms do not eliminate every possible failure. They make specific failure modes harder to trigger and easier to detect.

Conclusion

agent-broker is a distributed concurrency and resilience library for coordinating agent permissions, budget reservations, and delegation.

Building it highlighted an important lesson: correctness depends on more than the happy path. It also depends on how a system represents identity, serializes resource names, and behaves when shared state no longer matches expectations.

Length-prefixed Redis keys, missing-pool detection during reservation resolution, and broker-controlled delegation metadata are three examples of turning those concerns into explicit architectural rules.

The most valuable design decisions are often the ones that remove ambiguity before it becomes a production incident.

Top comments (0)