Skip to content
tnsaiDocs
GitHub

Principal and authority

Every agent acts as a known principal, within an authority scope you give it.

AgentBuilder.build() requires both. They are the agent's accountability wiring: the principal says who acts, and the scope says what that actor may do and for how long. A liability sink, which records each action, is optional.

What the builder requires

  • principal(AgentPrincipal): the identity the agent acts as. LocalIdentityProvider.issue(AgentDescriptor) creates one for local use.
  • authorityScope(AuthorityScope): what the agent may do. AuthorityScope.unrestricted(Duration) allows every action type and target for a time window. AuthorityScope.permanent() never expires.
  • liabilitySink(LiabilitySink): optional. Without one, no liability records are built.

Without a principal or a scope, build() throws IllegalStateException and names the missing part. See Create an agent for a full builder chain.

Wire a direct Agent subclass

A direct subclass does not go through the builder, so it wires its own ActionExecutor. Call setPrincipal and setDefaultScope on it and pass it to the four-argument Agent constructor. The annotated agent example shows this.

An executor that is missing either one throws AccountabilityWiringException when an action is dispatched, before the action's body runs. It is not retried. In a chat, the turn ends on that tool call.

Narrow the scope

package com.example.tnsai.docs;

import com.tnsai.accountability.AuthorityScope;
import com.tnsai.enums.ActionType;

import java.math.BigDecimal;
import java.time.Duration;
import java.time.Instant;
import java.util.Optional;
import java.util.Set;

public final class AuthorityScopeExample {
    private AuthorityScopeExample() {}

    public static AuthorityScope localOnly() {
        return new AuthorityScope(
            Set.of(ActionType.LOCAL),
            Set.of("billing"),
            Optional.of(new BigDecimal("5.00")),
            Duration.ofHours(8),
            Instant.now());
    }
}
FieldMeaning
allowedActionsAction types the agent may run. Empty allows all
targetSystemsSystems the agent may act on. Empty allows all
spendCeilingUSDSpending limit, read by the x402 payment broker
validForHow long the scope lasts from issuedAt. null never expires
issuedAtWhen the scope starts

When the scope is checked

When a liability sink is set, the scope is recorded in each liability record. It is enforced only for actions that declare an access policy, with @Security or @ExecutionPolicy on the method or its class. For those actions:

  • With tnsai-quality on the classpath, its SecurityEnforcer denies the call with a SecurityException when the scope has expired, does not permit the action type, or lists target systems that exclude the action's target.
  • Without tnsai-quality, the call is refused, because no provider can evaluate the declared policy.

An action with neither annotation runs whatever its scope says. To bound one, declare a policy on it. See Enforcement and Execution policy.