Skip to content

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_a requests a WRITE within manager_1's own scope (inside ./workspace, and manager_1 itself has WRITE), 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), ManagerDelegationApproval defers, and ChainedApproval falls 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):

  1. If req.requester (the worker) isn't in the manager's delegated worker list, raise NotImplementedError — "this manager has no authority over this worker at all."
  2. Otherwise, figure out the requested Permission — either from req.action directly ("WRITE"Permission.WRITE) or from req.context["permission"] if that's how it was passed.
  3. Check registry.is_within_manager_scope(manager, permission, path). If True, approve (return True).
  4. If False, raise NotImplementedError — "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 ApprovalBackend protocol and ChainedApproval.
  • Security & Sandboxingsub_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.