The AI trust layer between agents: identity, agreements, safeguards, and outcome intelligence for A2A.

Commit f87848a4

f87848a4fcb8Merged

aryaamin <[email protected]> · 2 months ago · 20 files · +11 added · ~9 modified

Description

feat: add interaction intelligence protocol

Details

Commit
f87848a4fcb8
Parent
1a7911da5a29

packages/core/src/index.tsmodifiedView file

@@ -1,6 +1,7 @@
11 import { randomUUID } from 'node:crypto';
22 import {
33 AgentIdentitySchema,
4+ CounterpartyBriefSchema,
45 canonicalHash,
56 createKeyPair,
67 DelegationCredentialSchema,
@@ -14,17 +15,195 @@
1415 verifyNamed,
1516 verifyObject,
1617 type AgentIdentity,
18+ type CounterpartyBrief,
1719 type DelegationCredential,
1820 type FactCheckResult,
1921 type Feedback,
2022 type InteractionContract,
2123 type InteractionEvent,
2224 type KeyPair,
25+ type LiveSessionInsight,
26+ type PublicAgentCard,
2327 type Receipt,
2428 type RiskDecision,
2529 type TrustEnvelope,
2630 } from '../../protocol/src/index.js';
2731
32+const REQUIREMENT_STOP_WORDS = new Set([
33+ 'about',
34+ 'agent',
35+ 'available',
36+ 'from',
37+ 'have',
38+ 'into',
39+ 'must',
40+ 'provide',
41+ 'return',
42+ 'should',
43+ 'that',
44+ 'their',
45+ 'this',
46+ 'with',
47+]);
48+
49+function normalized(value: string): string {
50+ return value
51+ .trim()
52+ .toLowerCase()
53+ .replace(/[^a-z0-9]+/g, ' ')
54+ .trim();
55+}
56+
57+function meaningfulTerms(value: string): string[] {
58+ return [
59+ ...new Set(
60+ normalized(value)
61+ .split(' ')
62+ .filter((term) => term.length >= 4 && !REQUIREMENT_STOP_WORDS.has(term)),
63+ ),
64+ ];
65+}
66+
67+function assessDeclaration(
68+ requirement: string,
69+ declarations: string[],
70+): { status: 'match' | 'partial' | 'mismatch' | 'unknown'; reason: string; confidence: number } {
71+ const normalizedRequirement = normalized(requirement);
72+ const normalizedDeclarations = declarations.map(normalized);
73+ if (
74+ normalizedDeclarations.some(
75+ (declaration) => declaration === '*' || declaration === normalizedRequirement,
76+ )
77+ )
78+ return {
79+ status: 'match',
80+ reason: 'The counterparty explicitly declares this capability.',
81+ confidence: 0.95,
82+ };
83+
84+ const terms = meaningfulTerms(requirement);
85+ const negative = normalizedDeclarations.find((declaration) =>
86+ terms.some(
87+ (term) =>
88+ declaration.includes(`no ${term}`) ||
89+ declaration.includes(`not ${term}`) ||
90+ declaration.includes(`cannot ${term}`),
91+ ),
92+ );
93+ if (negative)
94+ return {
95+ status: 'mismatch',
96+ reason: `The counterparty declares a conflicting limitation: ${negative}`,
97+ confidence: 0.95,
98+ };
99+
100+ if (terms.length) {
101+ const declarationText = normalizedDeclarations.join(' ');
102+ const overlap = terms.filter((term) => declarationText.includes(term)).length / terms.length;
103+ if (overlap >= 0.6)
104+ return {
105+ status: 'match',
106+ reason: 'The counterparty declarations strongly match this requirement.',
107+ confidence: 0.8,
108+ };
109+ if (overlap > 0)
110+ return {
111+ status: 'partial',
112+ reason: 'The counterparty declarations only partially cover this requirement.',
113+ confidence: 0.65,
114+ };
115+ }
116+ return {
117+ status: 'unknown',
118+ reason: 'The published agent card does not establish this requirement.',
119+ confidence: 0.35,
120+ };
121+}
122+
123+export function buildCounterpartyBrief(input: {
124+ interactionId: string;
125+ contractHash: string;
126+ contract: InteractionContract;
127+ recipientAgentId: string;
128+ subject: PublicAgentCard;
129+ historyInsights: LiveSessionInsight[];
130+ relevantSampleSize: number;
131+ historyConfidence: number;
132+ generatedAt?: string;
133+ expiresAt: string;
134+}): CounterpartyBrief {
135+ const declarations = [
136+ input.subject.description,
137+ ...input.subject.capabilities,
138+ ...input.subject.limitations,
139+ ];
140+ const requirements = [
141+ {
142+ requirementId: 'task-category',
143+ requirement: input.contract.taskCategory,
144+ kind: 'capability' as const,
145+ },
146+ {
147+ requirementId: 'requested-outcome',
148+ requirement: input.contract.requestedOutcome,
149+ kind: 'scope' as const,
150+ },
151+ ...input.contract.allowedActions.map((requirement, index) => ({
152+ requirementId: `allowed-action-${index + 1}`,
153+ requirement,
154+ kind: 'capability' as const,
155+ })),
156+ ...input.contract.successCriteria.map((requirement, index) => ({
157+ requirementId: `success-criterion-${index + 1}`,
158+ requirement,
159+ kind: 'success_criterion' as const,
160+ })),
161+ ...input.contract.evidenceRequirements.map((requirement, index) => ({
162+ requirementId: `evidence-${index + 1}`,
163+ requirement,
164+ kind: 'evidence' as const,
165+ })),
166+ ].map((requirement) => ({
167+ ...requirement,
168+ ...assessDeclaration(requirement.requirement, declarations),
169+ sources: ['contract', 'self_declared'] as const,
170+ evidenceReferences: [input.subject.cardUrl],
171+ }));
172+ const requirementReferences = requirements
173+ .filter((requirement) => requirement.status !== 'match')
174+ .map((requirement) => requirement.requirementId);
175+ const insights = input.historyInsights.map((insight) => ({
176+ ...insight,
177+ requirementReferences:
178+ insight.requirementReferences.length > 0
179+ ? insight.requirementReferences
180+ : requirementReferences,
181+ }));
182+ const needsChallenge =
183+ input.relevantSampleSize === 0 ||
184+ requirements.some((requirement) => requirement.status === 'mismatch') ||
185+ insights.some((insight) => insight.severity === 'high');
186+ return CounterpartyBriefSchema.parse({
187+ briefId: randomUUID(),
188+ interactionId: input.interactionId,
189+ contractHash: input.contractHash,
190+ recipientAgentId: input.recipientAgentId,
191+ subjectAgentId: input.subject.agentId,
192+ taskCategory: input.contract.taskCategory,
193+ decision: needsChallenge ? 'CHALLENGE' : 'ALLOW',
194+ requirements,
195+ insights,
196+ relevantSampleSize: input.relevantSampleSize,
197+ historyConfidence: input.historyConfidence,
198+ subjectAgentVersion: input.subject.agentVersion,
199+ recommendedContractChanges: requirements
200+ .filter((requirement) => requirement.status === 'mismatch')
201+ .map((requirement) => `Resolve mismatch: ${requirement.requirement}`),
202+ generatedAt: input.generatedAt ?? new Date().toISOString(),
203+ expiresAt: input.expiresAt,
204+ });
205+}
206+
28207 export interface AuditStore {
29208 append(kind: string, id: string, value: unknown): void;
30209 list(kind: string): unknown[];