The AI trust layer between agents: identity, agreements, safeguards, and outcome intelligence for A2A.
Commit 8a15fe87
8a15fe87b720Merged
aryaamin <[email protected]> · 2 months ago · 51 files · +4 added · ~47 modified
Description
Add guided first interaction onboarding
Details
- Commit
- 8a15fe87b720
- Parent
- ce192002f08a
apps/api/src/app.tsmodifiedView file
import { toA2AAgentCard } from '../../../packages/sidecar/src/index.js';import { approveAgentSetup, createDashboardAgent, createHostedProviderAgent, getOnboardingState, rejectAgentSetup,} from '../../../packages/persistence/src/onboarding.js';import { FixedWindowRateLimiter } from './security.js';import { buildOwnerCompletionReport, buildOwnerFeedback, buildQuickstartInteraction,} from './quickstart.js';type DashboardRepository = Pick< HostedRepository, throw error; } }); router.post('/v0.1/quickstart/agent', async (request) => { const owner = operatorId(request); if (!repository || !owner) throw new Error('Hosted persistence is not configured'); const value = z .object({ agentName: z.string().trim().min(1).max(100), projectName: z.string().trim().min(1).max(100).default('My agents'), description: z.string().trim().min(1).max(500), capabilities: z.array(z.string().trim().min(1).max(100)).min(1).max(20), limitations: z.array(z.string().trim().min(1).max(300)).max(20).default([]), }) .strict() .parse(request.body); const created = await createDashboardAgent(repository, owner, value); const card = await repository.publishAgent( owner, buildPublicAgentCard(created.agent, publicBaseUrl(request)), ); await repository.upsert(owner, 'publication', created.agent.agentId, { agentId: created.agent.agentId, published: true, updatedAt: card.updatedAt, }); return { ...created, card }; }); router.post('/v0.1/agents', async (request) => { const owner = operatorId(request); const current = await scopedEngine(request); throw new Error('Federated interactions are not configured'); return repository.listFederatedInteractions(owner); }); router.post('/v0.1/federated-interactions/start', async (request) => { const owner = operatorId(request); if (!repository?.createFederatedInteraction || !owner) throw new Error('Federated interactions are not configured'); const value = z .object({ initiatorAgentId: z.string().min(1), responderAgentId: z.string().min(1), task: z.string().trim().min(10).max(2000), requestedOutcome: z.string().trim().min(3).max(1000), successCriterion: z.string().trim().min(3).max(1000), taskCategory: z.string().trim().min(1).max(100).optional(), deadline: z.string().datetime().optional(), }) .strict() .refine((input) => !input.deadline || Date.parse(input.deadline) > Date.now(), { message: 'Deadline must be in the future', path: ['deadline'], }) .parse(request.body); const [initiator, responder] = await Promise.all([ repository.getPublishedAgent(value.initiatorAgentId), repository.getPublishedAgent(value.responderAgentId), ]); if (!initiator) throw new Error('Publish the initiating agent before starting an agreement'); if (!responder) throw new Error('The selected counterparty is no longer available'); if (repository.listFederatedInteractions) { const existing = (await repository.listFederatedInteractions(owner)).find( (candidate) => ['pending', 'active'].includes(candidate.status) && candidate.initiatorAgentId === value.initiatorAgentId && candidate.responderAgentId === value.responderAgentId && candidate.contract.purpose === value.task.slice(0, 500) && candidate.contract.requestedOutcome === value.requestedOutcome && candidate.contract.successCriteria[0] === value.successCriterion, ); if (existing) return existing; } return repository.createFederatedInteraction( owner, buildQuickstartInteraction(initiator, responder, value), ); }); router.get('/v0.1/federated-interactions/:id', async (request) => { const owner = operatorId(request); if (!repository?.getFederatedInteraction || !owner) value.agentId, ); }); router.post('/v0.1/federated-interactions/:id/complete', async (request) => { const owner = operatorId(request); if (!repository?.getFederatedInteraction || !repository.submitCompletionReport || !owner) throw new Error('Interaction completion is not configured'); const interactionId = (request.params as { id: string }).id; const value = z .object({ agentId: z.string().min(1), outcome: z.enum(['success', 'partial', 'failure', 'cancelled']), summary: z.string().trim().min(3).max(2000), evidenceReferences: z.array(z.string().url().max(2048)).max(20).default([]), }) .strict() .parse(request.body); const [interaction, rows] = await Promise.all([ repository.getFederatedInteraction(owner, interactionId), repository.list(owner), ]); if (!interaction) throw new Error('Interaction not found'); if (!['active', 'completed'].includes(interaction.status)) throw new Error('The agreement must be active before reporting its outcome'); const duplicate = rows.find( (row) => row.kind === 'completion_report' && row.payload?.interactionId === interactionId && row.payload?.reportingAgentId === value.agentId, ); if (duplicate) return { report: duplicate.payload, deduplicated: true }; const agent = rows.find( (row) => row.kind === 'agent_profile' && row.recordId === value.agentId, )?.payload as AgentProfile | undefined; if (!agent || agent.status !== 'active') throw new Error('Active owned agent not found'); const session = repository.getLiveSession ? await repository.getLiveSession(owner, interactionId, value.agentId) : undefined; const report = buildOwnerCompletionReport(interaction, agent, value, { ...(session?.activatedAt ? { startedAt: session.activatedAt } : {}), }); return repository.submitCompletionReport(owner, value.agentId, report, 'oauth_account'); }); router.post('/v0.1/federated-interactions/:id/completion-reports', async (request) => { const owner = operatorId(request); if (!repository?.submitCompletionReport || !owner) : 'oauth_installation', ); }); router.post('/v0.1/feedback-requests/:id/respond', async (request) => { const owner = operatorId(request); if (!repository?.listFeedbackRequests || !repository.submitInteractionFeedback || !owner) throw new Error('Interaction feedback is not configured'); const value = z .object({ agentId: z.string().min(1), rating: z.number().int().min(1).max(5), wouldWorkAgain: z.enum(['yes', 'no', 'unsure']), privateComment: z.string().trim().max(1000).optional(), }) .strict() .parse(request.body); const [requests, rows] = await Promise.all([ repository.listFeedbackRequests(owner, value.agentId), repository.list(owner), ]); const duplicate = rows.find( (row) => row.kind === 'interaction_feedback' && row.payload?.requestId === (request.params as { id: string }).id && row.payload?.reviewerAgentId === value.agentId, ); if (duplicate) return { feedback: duplicate.payload, deduplicated: true }; const feedbackRequest = requests.find( (candidate) => candidate.requestId === (request.params as { id: string }).id, ); if (!feedbackRequest || feedbackRequest.status !== 'pending') throw new Error('Pending feedback request not found'); const agent = rows.find( (row) => row.kind === 'agent_profile' && row.recordId === value.agentId, )?.payload as AgentProfile | undefined; if (!agent || agent.status !== 'active') throw new Error('Active owned agent not found'); const feedback = buildOwnerFeedback(feedbackRequest, agent, value); return repository.submitInteractionFeedback(owner, value.agentId, feedback, 'oauth_account'); }); router.get('/v0.1/feedback-requests', async (request) => { const owner = operatorId(request); if (!repository?.listFeedbackRequests || !owner)apps/api/src/quickstart.tsaddedView file
Loading diff…
apps/dashboard/src/components/first-run-guide.tsxaddedView file
Loading diff…
apps/dashboard/src/main.tsxmodifiedView file
Loading diff…
apps/dashboard/src/styles.cssmodifiedView file
Loading diff…
docs/GO_LIVE_PLAN.mdmodifiedView file
Loading diff…
docs/PRODUCTION_VERIFICATION.mdmodifiedView file
Loading diff…
packages/mcp-server/src/server.tsmodifiedView file
Loading diff…
packages/persistence/src/hosted.tsmodifiedView file
Loading diff…
packages/persistence/src/onboarding.tsmodifiedView file
Loading diff…
packages/protocol/src/index.tsmodifiedView file
Loading diff…
schemas/v0.1/agent_identity.schema.jsonmodifiedView file
Loading diff…
schemas/v0.1/agent_mode.schema.jsonmodifiedView file
Loading diff…
schemas/v0.1/agent_presence.schema.jsonmodifiedView file
Loading diff…
schemas/v0.1/agent_resolution.schema.jsonmodifiedView file
Loading diff…
schemas/v0.1/agent_transport.schema.jsonmodifiedView file
Loading diff…
schemas/v0.1/behavioural_profile_delta.schema.jsonmodifiedView file
Loading diff…
schemas/v0.1/completion_outcome.schema.jsonmodifiedView file
Loading diff…
schemas/v0.1/contextual_reliability_summary.schema.jsonmodifiedView file
Loading diff…
schemas/v0.1/contract_revision.schema.jsonmodifiedView file
Loading diff…
schemas/v0.1/counterparty_brief.schema.jsonmodifiedView file
Loading diff…
schemas/v0.1/delegation_credential.schema.jsonmodifiedView file
Loading diff…
schemas/v0.1/expectation_manifest.schema.jsonmodifiedView file
Loading diff…
schemas/v0.1/fact_check_result.schema.jsonmodifiedView file
Loading diff…
schemas/v0.1/federated_interaction.schema.jsonmodifiedView file
Loading diff…
schemas/v0.1/feedback.schema.jsonmodifiedView file
Loading diff…
schemas/v0.1/feedback_request.schema.jsonmodifiedView file
Loading diff…
schemas/v0.1/hosted_message.schema.jsonmodifiedView file
Loading diff…
schemas/v0.1/hosted_thread.schema.jsonmodifiedView file
Loading diff…
schemas/v0.1/interaction_completion_report.schema.jsonmodifiedView file
Loading diff…
schemas/v0.1/interaction_conclusion.schema.jsonmodifiedView file
Loading diff…
schemas/v0.1/interaction_contract.schema.jsonmodifiedView file
Loading diff…
schemas/v0.1/interaction_event.schema.jsonmodifiedView file
Loading diff…
schemas/v0.1/interaction_feedback.schema.jsonmodifiedView file
Loading diff…
schemas/v0.1/learning_eligibility_decision.schema.jsonmodifiedView file
Loading diff…
schemas/v0.1/live_session_activation.schema.jsonmodifiedView file
Loading diff…
schemas/v0.1/live_session_event.schema.jsonmodifiedView file
Loading diff…
schemas/v0.1/live_session_offer.schema.jsonmodifiedView file
Loading diff…
schemas/v0.1/live_session_state_record.schema.jsonmodifiedView file
Loading diff…
schemas/v0.1/public_agent_card.schema.jsonmodifiedView file
Loading diff…
schemas/v0.1/receipt.schema.jsonmodifiedView file
Loading diff…
schemas/v0.1/record_attestation.schema.jsonmodifiedView file
Loading diff…
schemas/v0.1/requirement_assessment.schema.jsonmodifiedView file
Loading diff…
schemas/v0.1/risk_decision.schema.jsonmodifiedView file
Loading diff…
schemas/v0.1/source_record_envelope.schema.jsonmodifiedView file
Loading diff…
schemas/v0.1/success_criterion_assessment.schema.jsonmodifiedView file
Loading diff…
schemas/v0.1/trust_envelope.schema.jsonmodifiedView file
Loading diff…
tests/api.test.tsmodifiedView file
Loading diff…
tests/onboarding.test.tsmodifiedView file
Loading diff…
tests/quickstart-api.test.tsaddedView file
Loading diff…
tests/quickstart.test.tsaddedView file
Loading diff…