Skip to content
AgentField
Governance

Access Policies

Tag-based access control for cross-agent calls with ALLOW/DENY rules and tag approval workflow

Bounded autonomy — ALLOW and DENY policy rules

Control which agents can call which functions using tag-based ALLOW/DENY rules -- no hardcoded ACLs required.

AgentField evaluates access policies on every cross-agent call. Policies match on caller and target tags (not specific agent IDs), support function-level granularity with input constraints, and are sorted by priority with first-match-wins semantics. Within each policy, deny_functions are checked before allow_functions, but across policies the result is determined by the first matching policy in priority order. Fail-open by default: if no policy matches, the call is allowed (backward compatibility).

from agentfield import Agent

# The treasury agent — only callers with the right tags get through
app = Agent(
    node_id="treasury",
    version="1.0.0",
    tags=["finance", "transfers"],
    local_verification=True,  # Evaluate policies locally, no round-trip to CP
)

@app.reasoner()
async def high_value_transfer(amount: float, execution_context=None) -> dict:
    # Policy engine runs BEFORE this handler — if caller lacks "finance-ops" tag, they get 403
    caller_did = execution_context.caller_did
    app.note(f"Transfer ${amount:,.2f} authorized for {caller_did}", ["audit", "finance"])
    return await app.ai(system="Process this transfer.", user=str(amount))

# What callers see:
# ✅ Agent tagged ["finance-ops"] → calls treasury.high_value_transfer → 200 OK
# ❌ Agent tagged ["analytics"]   → calls treasury.high_value_transfer → 403 DENIED
# ❌ Agent tagged ["finance-ops"] → calls treasury.delete_account      → 403 DENIED (deny_functions)
import { Agent } from '@agentfield/sdk';

const agent = new Agent({
  nodeId: 'treasury',
  version: '1.0.0',
  tags: ['finance', 'transfers'],
});

agent.reasoner('highValueTransfer', async (ctx) => {
  // Policy engine runs BEFORE this handler — unauthorized callers get 403
  ctx.note(`Transfer $${ctx.input.amount} authorized for ${ctx.callerDid}`, ['audit', 'finance']);
  return await ctx.ai(
    `Process this transfer: ${ctx.input.amount}`,
    { system: 'You are a treasury transfer processor.' },
  );
});

// ✅ Agent tagged ["finance-ops"] → 200 OK
// ❌ Agent tagged ["analytics"]   → 403 DENIED
a, err := agent.New(agent.Config{
    NodeID:  "treasury",
    Version: "1.0.0",
    Tags:    []string{"finance", "transfers"},
})

a.RegisterReasoner("high_value_transfer", func(ctx context.Context, input map[string]any) (any, error) {
    // Policy engine runs BEFORE this handler — unauthorized callers get 403
    amount, _ := input["amount"].(float64)
    return map[string]any{"status": "approved", "amount": amount}, nil
},
    agent.WithDescription("Process a high-value financial transfer"),
    agent.WithReasonerTags("finance", "transfers"),
)
# Step 1: Create a policy — finance-ops can transfer, but never delete
curl -X POST http://localhost:8080/api/v1/admin/policies \
  -H "Content-Type: application/json" -d '{
    "name": "Finance ops can transfer, never delete",
    "caller_tags": ["finance-ops"],
    "target_tags": ["finance", "transfers"],
    "allow_functions": ["high_value_transfer", "balance_check"],
    "deny_functions": ["delete_account", "modify_ledger"],
    "constraints": {"region": {"operator": "==", "value": "us-east-1"}},
    "action": "allow",
    "priority": 100
  }'

# Step 2: Block analytics from touching finance entirely
curl -X POST http://localhost:8080/api/v1/admin/policies \
  -H "Content-Type: application/json" -d '{
    "name": "Analytics blocked from finance",
    "caller_tags": ["analytics"],
    "target_tags": ["finance"],
    "deny_functions": ["*"],
    "action": "deny",
    "priority": 200
  }'

# Verify: see all policies sorted by priority (highest first, first match wins)
curl http://localhost:8080/api/v1/admin/policies

# See which agents have which tags and their approval status
curl http://localhost:8080/api/ui/v1/authorization/agents

What just happened

The example created real allow and deny rules, attached them to caller and target tags, and then showed where to inspect the resulting policy state. That is the core value to surface here: authorization decisions live in the control plane and can be reasoned about as explicit policy data.

{
  "caller_tags": ["finance-ops"],
  "target_tags": ["finance", "transfers"],
  "allow_functions": ["high_value_transfer", "balance_check"],
  "deny_functions": ["delete_account", "modify_ledger"],
  "priority": 100
}