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.