Skip to main content

Context Awareness

Context-aware mode changes how agents experience constraint violations. Instead of receiving a thrown error, the agent gets a structured result envelope — a ConstraintAwareResult — that describes exactly what went wrong and what to do next. This lets the agent reason about its own permissions and participate in the approval flow.

Enabling It​

Set constraintAware: true in AppChain.create:

const chain = await AppChain.create({
providerName: 'sms-service',
issuer: 'https://sms.example.com',
capabilities: [sendSmsCapability],
constraintAware: true,
});

When constraintAware is false (the default), constraint violations throw a ChainAuthError. Enabling this flag keeps backward-compatible behavior for unconstrained paths, but switches violations to return structured results.

The ConstraintAwareResult Envelope​

Every capability call returns a ConstraintAwareResult when this mode is on:

type ConstraintAwareResult<T = unknown> = {
success: boolean; // true if the call succeeded
result?: T; // the actual return value (when success=true)
permission: PermissionStatus;
violations?: ConstraintViolationDetail[];
grant?: PermissionGrant; // details when permission='approved'
guidance: string; // AI-readable next-step instructions
capability: string;
activeConstraints?: Record<string, unknown>;
};

Permission Statuses​

StatusMeaning
not_requiredCall passed all constraints — result is populated
constraint_violatedA constraint check failed — violations describe what
approvedA human approved this call via request_permission
deniedA human denied the access request
expiredThe approval request timed out

Receiving Tool Output and Structured Violations​

When a constraint is violated, the envelope includes a violations array with per-field detail:

type ConstraintViolationDetail = {
field: string; // which input field failed
constraint: 'in' | 'not_in' | 'max' | 'min' | 'exact';
expected: unknown; // the constraint definition
actual: unknown; // what the agent passed
message: string; // human/AI-readable explanation
};

Example: an agent tries to send an SMS to a number not in the approved list:

const result = await secured.send_sms({ to: '+9999999', body: 'hello' });
// result.success → false
// result.permission → 'constraint_violated'
// result.violations → [{ field: 'to', constraint: 'in', actual: '+9999999', expected: ['+254700000001'] }]
// result.activeConstraints → { to: { in: ['+254700000001'] } }
// result.guidance → "Constraint violated ... call request_permission ..."

The agent can inspect violations and activeConstraints to understand exactly which values are allowed without having to ask for help blindly.

Telling the Agent Its Constraints Upfront​

Use getConstraintContext(grants) to generate a system-prompt block that describes all active constraints in natural language:

const systemPromptBlock = chain.getConstraintContext(grants);

This produces text the agent can read at startup:

You are operating under capability constraints enforced by the agents-chain protocol.
When a call violates a constraint, you will receive a structured violation result.
You have access to a "request_permission" tool that lets you request human approval for blocked calls.

Active constraints:
- send_sms:
to: must be one of ["+254700000001", "+254700000002"]

If you need to use a value outside these constraints, call request_permission with the capability name, args, and reason.
A human operator will review your request.

Include this in your agent's system prompt so it understands the rules before making any calls — reducing unnecessary violations.

The request_permission Capability​

When constraintAware: true and accessRequests are both configured, a request_permission capability is automatically registered. The agent can call it explicitly after receiving a constraint_violated result:

const result = await secured.request_permission({
capability: 'send_sms',
args: { to: '+9999999', body: 'User asked me to notify this number' },
reason: 'The user explicitly requested this number be contacted',
});

The call suspends until a human approves or denies it via chain.approve() / chain.deny(). When approved, the capability executes immediately and the result is returned inside the ConstraintAwareResult.

Full flow​

After approval​

When a human approves with scope: 'value', the approval is stored and subsequent calls with the same argument value pass without re-requesting:

// Human approved '+9999999' with scope: 'value'
chain.approve({ requestId, code, scope: 'value' });

// Later calls with the same value go straight through
const result = await secured.send_sms({ to: '+9999999', body: 'second message' });
// result.permission → 'not_required' ← no re-approval needed

Setting Up Context-Aware Mode​

import { AppChain } from 'agents-chain';

const chain = await AppChain.create({
providerName: 'sms-service',
issuer: 'https://sms.example.com',
capabilities: [
{
name: 'send_sms',
description: 'Send an SMS message',
inputSchema: {
type: 'object',
required: ['to', 'body'],
properties: {
to: { type: 'string' },
body: { type: 'string' },
},
},
outputSchema: { type: 'object' },
execute: async ({ to, body }) => {
// your actual SMS logic
return { sent: true, to, body };
},
},
],
constraintAware: true,
accessRequests: {
approvalSecret: process.env.APPROVAL_SECRET!,
requestTTLMs: 60_000,
notifier: {
async notify(request) {
// send Slack/email to human reviewer
},
async onResolved(request, outcome) {
// optional: log the decision
},
},
},
});

// Build grants for this agent session
const grants = [
{ capability: 'send_sms', status: 'active', constraints: { to: { in: ['+254700000001'] } } },
];

// Inject constraint context into your AI agent's system prompt
const systemPrompt = chain.getConstraintContext(grants);

// Wrap your service
const secured = chain.wrap(myService, grants);

// The agent uses `secured` — on violation it gets structured results, not thrown errors

Comparison: Default vs Context-Aware​

BehaviorconstraintAware: false (default)constraintAware: true
Constraint violationThrows ChainAuthErrorReturns ConstraintAwareResult
Agent sees violation detailVia err.structuredViolationsVia result.violations
Agent guidanceMust be coded manuallyIncluded in result.guidance
request_permission toolNot availableAuto-registered
System prompt helperNot availablegetConstraintContext()
Backward compatibleYes (default)Must opt in

Without Access Requests​

You can use constraintAware: true without enabling accessRequests. In this case, violations still return structured results, but the guidance will say that the access request system is not available — the agent must adjust its arguments instead:

const chain = await AppChain.create({
providerName: 'service',
issuer: 'https://example.com',
capabilities: [...],
constraintAware: true,
// no accessRequests
});

The request_permission capability is not registered in this mode.