Manager/Worker Delegation¶
For multi-agent setups, a "manager" agent can auto-approve a "worker" agent's requests when those requests already fall within scope the manager itself holds — without escalating every single worker action to a human. Anything that exceeds what the manager itself is allowed to do still gets deferred (to a human, or wherever you chain next).
This whole feature is built as just another ApprovalBackend (see
Approval Workflows), so it composes with
everything else via ChainedApproval — it's one link in the chain that
either resolves a request or defers to the next one.
The building blocks¶
| Class | Purpose |
|---|---|
AgentIdentity |
A simple (name, level) label for an agent — level is a display/audit string only ("worker" / "manager" / "admin"), it doesn't gate anything by itself. |
PermissionRegistry |
Tracks which agents can approve on behalf of which other agents, and stores each agent's own SecurityContext so scope can be checked. |
ManagerDelegationApproval |
An ApprovalBackend that consults the registry: approves if the request is within the manager's own scope, defers (NotImplementedError) otherwise. |
Full example¶
from langchain_mero_tools import (
SecurityContext, PathEntry, Permission, PermissionRegistry,
ManagerDelegationApproval, ChainedApproval, CLIApproval,
)
registry = PermissionRegistry()
# The manager's own context defines the ceiling of what it can delegate.
manager_ctx = SecurityContext(
name="manager_1",
paths=[
PathEntry(
path="./workspace",
slug="root",
allowed_permission=Permission.READ | Permission.WRITE,
),
],
)
registry.register_context("manager_1", manager_ctx)
registry.grant_delegation("manager_1", can_approve_for=["worker_a", "worker_b"])
# The worker's context requires approval for WRITE; the manager gets first
# say, a human via CLI is the fallback if the manager can't decide.
worker_ctx = SecurityContext(
name="worker_a",
paths=[
PathEntry(
path="./workspace",
slug="root",
allowed_permission=Permission.READ | Permission.WRITE,
),
],
require_approval_for=Permission.WRITE,
approval=ChainedApproval([
ManagerDelegationApproval(registry, manager="manager_1"), # tries manager first
CLIApproval(), # falls back to a human
]),
)
With this wiring:
- If
worker_arequests aWRITEwithinmanager_1's own scope (inside./workspace, andmanager_1itself hasWRITE), the manager approves it on the spot — no human involved. - If the request exceeds the manager's own permissions or paths (or the
manager was never delegated authority over that worker in the first
place),
ManagerDelegationApprovaldefers, andChainedApprovalfalls through to the next backend — here,CLIApproval(), a human.
PermissionRegistry API¶
registry = PermissionRegistry()
# Record an agent's own SecurityContext (used for the scope check).
registry.register_context("manager_1", manager_ctx)
# Grant delegation: manager_1 can approve on behalf of these workers.
registry.grant_delegation("manager_1", can_approve_for=["worker_a", "worker_b"])
# Query whether a delegation exists.
registry.can_delegate("manager_1", "worker_a") # True
# Check whether a specific request would fall within the manager's scope.
registry.is_within_manager_scope(
"manager_1",
requested_permission=Permission.WRITE,
requested_path="./workspace/notes.txt",
) # True/False
is_within_manager_scope checks two things against the manager's own
registered context: that the requested permission is a subset of what the
manager holds, and (if a path is given) that manager_ctx.check_path(...)
succeeds. Both must pass for the manager to be able to approve.
ManagerDelegationApproval behavior in detail¶
class ManagerDelegationApproval(ApprovalBackend):
def __init__(self, registry: PermissionRegistry, manager: str):
...
On request(req):
- If
req.requester(the worker) isn't in the manager's delegated worker list, raiseNotImplementedError— "this manager has no authority over this worker at all." - Otherwise, figure out the requested
Permission— either fromreq.actiondirectly ("WRITE"→Permission.WRITE) or fromreq.context["permission"]if that's how it was passed. - Check
registry.is_within_manager_scope(manager, permission, path). IfTrue, approve (return True). - If
False, raiseNotImplementedError— "this specific request exceeds what the manager itself can do; a human (or the next backend in the chain) needs to decide."
The distinction between "raise NotImplementedError" and "return False"
matters: NotImplementedError means defer, not deny. A flat return
False would make ManagerDelegationApproval an unconditional denier for
anything outside the manager's scope, with no chance for a fallback
backend (like a human) to weigh in. Raising instead lets ChainedApproval
correctly move on to the next backend in the chain.
Multi-level hierarchies¶
Nothing prevents chaining ManagerDelegationApproval instances for more
than two levels — a worker's chain could try its direct manager, then that
manager's own manager, then a human, by stacking multiple
ManagerDelegationApproval entries (each pointed at a different manager
name, each backed by the same or different registries) inside one
ChainedApproval.
approval = ChainedApproval([
ManagerDelegationApproval(registry, manager="team_lead"),
ManagerDelegationApproval(registry, manager="department_head"),
CLIApproval(),
])
See also¶
- Approval Workflows — the underlying
ApprovalBackendprotocol andChainedApproval. - Security & Sandboxing —
sub_context()is often used alongside this to derive a worker's context directly from a manager's, guaranteeing the worker can never be granted more than the manager already has.