Reference Implementations

Operations

meeting-notes-agent

intermediate

Event-triggered extraction of meeting notes from transcripts. Uses prompt registry for all LLM calls, execution memory for transcript buffer, and Fourth Law escalation.

APIs Used

ctx.promptsctx.resourcesctx.memory.executionctx.llmctx.escalate()ctx.provenancectx.telemetry.emit

Capabilities Required

meetings/notes/extract

What this demonstrates

  • 1ctx.prompts.load() for extract and owner-resolution — never inline LLM strings
  • 2ctx.resources.load() for core.meeting.transcript, ctx.resources.save() for core.meeting.notes
  • 3ctx.memory.execution for transcript buffer and re-read during owner resolution
  • 4Fourth Law: ctx.escalate() when confidence < 0.65 or transcript too short
  • 5ctx.provenance.log() with cost for cost benchmarking pipeline
  • 6ctx.telemetry.emit via emitReferenceAuthorSignal when no transcript is available (skip path)
typescript
/**
* Meeting Notes Agent — Reference Implementation
*
* Canon: kb/105_agent_sdk_architecture.md, kb/130_agent_design_patterns.md
* Principles demonstrated: P1, P5, P7, P10 (Human-in-Loop, Fourth Law)
*
* SDK surfaces demonstrated:
* ctx.prompts.load() — extract + owner-resolution prompts (no inline LLM strings)
* ctx.resources.load() — loading core.meeting.transcript as primary input
* ctx.resources.save() — writing core.meeting.notes with ledger anchoring
* ctx.llm.complete() — factual extraction at temperature 0.2
* ctx.escalate() — Fourth Law escalation when confidence < 0.65 or transcript too short
* ctx.provenance.log() — cryptographic audit trail with cost: result.cost
* ctx.memory.execution — transcript buffer and re-read during owner resolution
*
* Event triggers:
* meeting.transcript.created — dispatched when a transcript resource is created
*
* This agent is a template. Copy it. Change the kinds and LLM prompt.
* Everything else — the memory pattern, the HITL gate, the provenance call —
* follows the same structure for any event-triggered processing agent.
*/
import { handler, withProvenanceContext } from '@human/agent-sdk';
import type { ExecutionContext } from '@human/agent-sdk';
import { emitReferenceAuthorSignal } from '../../lib/reference-author-telemetry.js';
const AGENT_ID = 'meeting-notes-agent';
const VERSION = '1.0.0';
/** Transcript resource shape (metadata from core.meeting.transcript) */
interface TranscriptMetadata {
content?: string;
duration_seconds?: number;
participants?: Array<{ name?: string; did?: string }>;
source?: string;
}
/** Extraction result from the extract prompt (JSON output) */
interface ExtractionResult {
summary: string;
key_decisions: string[];
action_items: Array< { description: string; owner_name?: string }>;
follow_up_questions: string[];
confidence: number;
}
const execute = async (ctx: ExecutionContext, _input: unknown): Promise<{ saved_id: string; escalated: boolean }> => {
// 1. Load transcript from Resource Graph
const { data: transcripts } = await ctx.resources.load('core.meeting.transcript', { limit: 1 });
if (!transcripts.length) {
await emitReferenceAuthorSignal(ctx, 'meeting_notes_skipped_no_transcript', {});
await ctx.provenance.log(
withProvenanceContext(ctx, {
action: 'meeting.notes.skipped',
status: 'success',
input: { reason: 'no_transcript' },
})
);
return { saved_id: '', escalated: false };
}
const transcriptRecord = transcripts[0]!;
const meta = (transcriptRecord.metadata ?? {}) as TranscriptMetadata;
const content = meta.content ?? '';
const durationSeconds = meta.duration_seconds ?? 0;
const participants = meta.participants ?? [];
const wordCount = content.split(/\s+/).filter(Boolean).length;
// 2. Buffer to execution memory (correct pattern: no module-level or local variable for intermediate state)
await ctx.memory.execution.set('transcript', JSON.stringify({ content, durationSeconds, participants, wordCount }));
// 3. Minimum viability — escalate if transcript too short
if (durationSeconds < 120 || wordCount < 50) {
await ctx.escalate({
reason: 'transcript_too_short',
context: { duration_seconds: durationSeconds, word_count: wordCount },
requiredCapability: 'meetings/notes/review',
});
await ctx.provenance.log(
withProvenanceContext(ctx, {
action: 'meeting.notes.escalated',
status: 'success',
input: { reason: 'transcript_too_short' },
})
);
return { saved_id: '', escalated: true };
}
// 4. LLM extraction via ctx.prompts (never inline template strings)
const extractPrompt = await ctx.prompts.load('core/agents/meeting-notes/extract');
const participantsStr = participants.map((p) => `${p.name ?? 'Unknown'}: ${p.did ?? '—'}`).join('\n');
const renderedExtract = extractPrompt.render({
transcript: content,
participants: participantsStr,
});
const extractResult = await ctx.llm.complete({
prompt: renderedExtract,
promptMetadata: extractPrompt.toCallMetadata(),
temperature: 0.2,
maxTokens: 2000,
});
let extraction: ExtractionResult;
try {
const raw = extractResult.content.replace(/^[\s\S]*?(\{[\s\S]*\})[\s\S]*$/m, '$1');
extraction = JSON.parse(raw) as ExtractionResult;
} catch {
await ctx.provenance.log(
withProvenanceContext(ctx, {
action: 'meeting.notes.extraction_failed',
status: 'error',
cost: extractResult.cost,
})
);
throw new Error('Meeting notes extraction returned invalid JSON');
}
// 5. Fourth Law (P10): low confidence → human review
let escalated = false;
if (extraction.confidence < 0.65) {
await ctx.escalate({
reason: 'low_confidence_extraction',
context: {
confidence: extraction.confidence,
draft_summary: extraction.summary,
draft_action_items: extraction.action_items,
},
requiredCapability: 'meetings/notes/review',
});
escalated = true;
}
// 6. Owner resolution — re-read from execution memory (not re-load resource)
const bufferedRaw = await ctx.memory.execution.get('transcript');
const buffered = bufferedRaw ? (JSON.parse(bufferedRaw) as { content: string; participants: TranscriptMetadata['participants'] }) : null;
const actionItemsWithOwners = extraction.action_items.map((item) => {
const ownerName = item.owner_name ?? '';
const participant = (buffered?.participants ?? participants).find(
(p) => p.name && ownerName && p.name.toLowerCase().includes(ownerName.toLowerCase())
);
return {
description: item.description,
owner_name: ownerName,
owner_did: participant?.did ?? null,
};
});
// 7. Save notes to Resource Graph
const notesPayload = {
summary: extraction.summary,
key_decisions: extraction.key_decisions,
action_items: actionItemsWithOwners,
follow_up_questions: extraction.follow_up_questions,
confidence: extraction.confidence,
source_transcript_uri: transcriptRecord.uri,
};
const saveResponse = await ctx.resources.save('core.meeting.notes', { data: notesPayload });
const provenanceRef = saveResponse.provenance_ref ?? undefined;
// 8. Provenance log (with cost for marketplace cost_profile pipeline)
await ctx.provenance.log(
withProvenanceContext(ctx, {
action: 'meeting.notes.generated',
status: 'success',
output: {
resource_id: saveResponse.resource_id,
confidence: extraction.confidence,
escalated,
},
cost: extractResult.cost,
})
);
return {
saved_id: saveResponse.resource_id,
escalated,
};
};
export default handler({
name: AGENT_ID,
id: AGENT_ID,
version: VERSION,
capabilities: ['meetings/notes/generate'],
manifest: {
operations: [
{
name: 'generate',
description: 'Generate meeting notes from a transcript; escalates on short transcript or low confidence',
paramsSchema: {},
resultKind: 'core.meeting.notes',
},
],
resources: {
consumes: [{ kind: 'core.meeting.transcript' }],
produces: [{ kind: 'core.meeting.notes' }],
},
on: [
{ event: 'meeting.transcript.created', filter: { source: 'google-calendar' } },
{ event: 'meeting.transcript.created', filter: { source: 'zoom' } },
],
},
execute,
});

Run the tests

From monorepo root

$ pnpm test:agents:reference

$ pnpm test:agents:reference:verbose

The reference suite runs all 23 agents with createMockExecutionContext(), verifying every ctx.* API call and output shape.

See Also