AgentFieldbuild

Permissions & access control

Tag-based ACL policies and permission enforcement for cross-agent authorization.

Tag-based permissions for agent access control

Control which agents can call which, who can read shared memory, and what external connectors are allowed to do.

In production multi-agent systems, not every agent should be able to call every other agent. The finance agent should not invoke the admin agent. External connectors should only access specific skills. AgentField provides tag-based access control policies that govern cross-agent calls, memory access, and connector permissions -- evaluated at the control plane before any execution begins.

from agentfield import Agent

app = Agent(
    node_id="trading-agent",
    tags=["team:finance", "env:production", "sensitivity:high"],
    local_verification=True,  # Evaluate policies locally
)

@app.reasoner()
async def execute_trade(trade: dict) -> dict:
    # Policy engine evaluates access BEFORE this handler runs —
    # callers without matching tags are denied at the control plane
    result = await app.call("order-executor.submit", input=trade)
    return result

@app.reasoner()
async def sensitive_operation(input: dict) -> dict:
    """Only agents with the right tags can invoke this reasoner."""
    return await app.ai(
        system="Process this sensitive financial data.",
        user=str(input),
    )

What just happened

The examples showed the core enforcement model: tag-based policies evaluated at the control plane (or locally with local_verification) before a protected action runs. Authorization decisions live outside the agent process -- agents declare their tags, and the policy engine determines access.

{
  "target": "order-executor",
  "action": "call",
  "granted": false,
  "reason": "missing policy match"
}
What you get
  • Tag-based ACL -- assign tags to agents, write policies that reference tags. No hardcoded agent IDs in policies.
  • Local verification -- agents can evaluate policies locally using cached data from the control plane.
  • Function-level granularity -- allow_functions and deny_functions control which specific reasoners and skills are accessible.
  • Control plane evaluation -- policies are evaluated at the control plane middleware, not in agent code. Agents cannot bypass checks.
  • Session ingress tags -- tags=[...] on an @app.session declaration uses the same tag and approval model as reasoner and skill tags. Write policies against those tags to control who can start a live session.
Tag-based policies

Policies match agents by tags and control what actions they can perform. Tags use a key:value format.

Defining Policies

# Create a policy: finance team agents can call other finance agents
curl -X POST http://localhost:8080/api/v1/admin/policies \
  -H "Content-Type: application/json" \
  -d '{
    "name": "finance-internal-calls",
    "description": "Finance team agents can call each other",
    "caller_tags": ["team:finance"],
    "target_tags": ["team:finance"],
    "allow_functions": [],
    "action": "allow"
  }'

# Deny external connectors from accessing sensitive agents
curl -X POST http://localhost:8080/api/v1/admin/policies \
  -H "Content-Type: application/json" \
  -d '{
    "name": "block-external-sensitive",
    "description": "External connectors cannot call high-sensitivity agents",
    "caller_tags": ["type:connector"],
    "target_tags": ["sensitivity:high"],
    "deny_functions": ["*"],
    "action": "deny",
    "priority": 100
  }'

Policy Fields

FieldTypeRequiredDescription
namestringYesUnique policy name
descriptionstringNoHuman-readable description
caller_tagsstring[]YesTags the calling agent must have (any must match)
target_tagsstring[]YesTags the target agent must have (any must match)
allow_functionsstring[]NoFunctions explicitly allowed (empty = all allowed)
deny_functionsstring[]NoFunctions explicitly denied (checked before allow)
actionstringYes"allow" or "deny"
priorityintNoHigher priority policies override lower (default: 0)
constraintsobjectNoParameter constraints with operator/value pairs

Policy Evaluation

Policies are evaluated in priority order (highest first). The first matching policy determines the outcome. If no policy matches, the default is allow (backward compatibility for untagged agents).

1. Collect all policies where caller_tags ∩ caller's tags ≠ ∅ AND target_tags ∩ target's tags ≠ ∅
2. Sort by priority (descending)
3. For each matching policy: check deny_functions first, then allow_functions
4. Return the action of the first fully matching policy
5. If no policy matches → allow
Permission check API

How Policies Are Enforced

Access policies are evaluated automatically by the control plane when an agent calls another agent. You do not need to call a permission check API in your handler code -- the policy engine runs before your handler is invoked. If a caller's tags do not match any allow policy, the call is denied with a 403 response.

With local_verification enabled, agents cache policies from the control plane and evaluate them locally on incoming requests. This avoids a round-trip to the control plane on every call.

REST API

EndpointMethodDescription
/api/v1/policiesGETRead-only policy distribution endpoint (used by agents for local caching)
/api/v1/admin/policiesGETList all policies (admin)
/api/v1/admin/policiesPOSTCreate a new policy
/api/v1/admin/policies/{id}GETGet a specific policy by numeric ID
/api/v1/admin/policies/{id}PUTUpdate a policy
/api/v1/admin/policies/{id}DELETEDelete a policy

Permission checking is handled automatically by the control plane middleware -- there is no separate permission check endpoint. When an agent calls another agent, the middleware evaluates access policies before the handler runs.

Patterns

Team-Based Isolation

Isolate teams so their agents can only call within their team boundary.

# Each team's agents are tagged: team:finance, team:engineering, team:support

# Allow intra-team calls
curl -X POST http://localhost:8080/api/v1/admin/policies -d '{
  "name": "finance-internal", "caller_tags": ["team:finance"],
  "target_tags": ["team:finance"], "allow_functions": [],
  "action": "allow"
}'

# Allow cross-team discovery only (can see, but not call)
curl -X POST http://localhost:8080/api/v1/admin/policies -d '{
  "name": "cross-team-discovery", "caller_tags": [],
  "target_tags": [], "allow_functions": ["discover"],
  "action": "allow", "priority": -10
}'

Environment Gating

Prevent staging agents from calling production agents.

# Production agents can call production agents
curl -X POST http://localhost:8080/api/v1/admin/policies -d '{
  "name": "prod-to-prod", "caller_tags": ["env:production"],
  "target_tags": ["env:production"], "allow_functions": [],
  "action": "allow"
}'

# Block staging from calling production
curl -X POST http://localhost:8080/api/v1/admin/policies -d '{
  "name": "block-staging-to-prod", "caller_tags": ["env:staging"],
  "target_tags": ["env:production"], "deny_functions": ["*"],
  "action": "deny", "priority": 100
}'

Least-Privilege Connectors

Grant external connectors only the minimum permissions they need.

# The policy engine ensures only authorized connectors/agents can call this
@app.reasoner()
async def get_metrics(input: dict) -> dict:
    """Only callers matching an allow policy can invoke this reasoner."""
    metrics = await app.memory.get("metrics.latest")
    return {"metrics": metrics}
# The connector's policy only allows read access to analytics agents
curl -X POST http://localhost:8080/api/v1/admin/policies -d '{
  "name": "dashboard-read-analytics",
  "caller_tags": ["type:connector", "connector:analytics-dashboard"],
  "target_tags": ["team:analytics"],
  "allow_functions": [],
  "action": "allow"
}'