1. Per-Phase Provenance Schema Design for Financial Agent Pipelines
The schema isn't the boring part — it's the compliance contract, and if you design it per execution phase rather than per tool call, auditors can reconstruct the agent's reasoning without needing access to the model itself.
- Phase boundary is the right granularity: a provenance record per tool call is too noisy to audit, per workflow run is too coarse to dispute — phase boundaries give compliance teams a natural unit of review.
- Immutable evidence fields should capture the inputs that caused a decision, not the decision alone — the rule match, the retrieved context chunk, the confidence threshold crossed, all frozen at write time.
- Correlation IDs must thread through every phase record so a single transaction can be reconstructed across async hops, retries, and supervisor re-entries without gap.
TECHNICAL DEEP DIVE & CODE ARTIFACTS Expand [+]
A financial transaction agent running through phases like eligibility check, risk scoring, limit validation, and approval routing needs a provenance record that timestamps the entry and exit of each phase, captures the exact input state handed in, records which tool or model produced the phase output, and flags whether any human override occurred. That record needs to be written before the phase result is acted on — not after — so that a failure mid-pipeline doesn't leave the audit log in an ambiguous 'maybe completed' state. The schema should version itself so that when the underlying prompt or model changes, existing records remain interpretable against the schema version that was active when they were written.
AIThe pattern that emerges across reliable agentic system design is that compliance-grade provenance requires treating the log as a first-class output of the agent, not a side effect. This means the provenance writer sits at the same architectural level as the state store — not bolted on as middleware — and every phase transition is a transactional write: phase result and provenance record commit together or neither does.
import { z } from 'zod';
import { randomUUID } from 'crypto';
const PhaseProvenanceRecord = z.object({
record_id: z.string().uuid(),
correlation_id: z.string().uuid(), // threads the full transaction
transaction_id: z.string(),
phase_name: z.string(),
phase_schema_version: z.string(), // e.g. 'eligibility_check_v2'
entered_at: z.string().datetime(),
exited_at: z.string().datetime().nullable(),
input_snapshot: z.record(z.unknown()), // frozen copy of inputs at phase entry
model_id: z.string().nullable(), // null if rule-based phase
tool_calls: z.array(z.object({
tool_name: z.string(),
input: z.record(z.unknown()),
output: z.record(z.unknown()),
latency_ms: z.number()
})),
phase_outcome: z.enum(['pass', 'deny', 'escalate', 'error']),
outcome_rationale: z.string(), // human-readable, written by agent
human_override: z.boolean().default(false),
human_override_actor: z.string().nullable(),
tamper_hash: z.string() // SHA-256 of record minus this field
});
type PhaseProvenanceRecord = z.infer<typeof PhaseProvenanceRecord>;
async function emitPhaseProvenance(
partial: Omit<PhaseProvenanceRecord, 'record_id' | 'tamper_hash'>,
store: ProvenanceStore
): Promise<PhaseProvenanceRecord> {
const record_id = randomUUID();
const withoutHash = { record_id, ...partial };
const tamper_hash = await sha256(JSON.stringify(withoutHash));
const record = PhaseProvenanceRecord.parse({ ...withoutHash, tamper_hash });
await store.writeAtomic(record); // write before acting on phase result
return record;
}2. Reliable Agentic System Design: Evidence-First Architecture
The Bayer pharmaceutical research assistant succeeded not because the LLM was powerful, but because the team treated every retrieved chunk and every generated claim as a traceable artifact — that same discipline is what makes a financial agent auditable.
- Evidence emission must be synchronous with the reasoning step that produced it — agents that log asynchronously create windows where a crash leaves the audit trail incomplete and the decision already executed.
- Retrieval context is part of the provenance, not separate from it — if an agent retrieved a document to justify a risk decision, that retrieval is a phase artifact and belongs in the record alongside the decision.
- Graceful degradation paths should write explicit provenance records too — a 'no context found, defaulting to conservative rule' outcome is as important to capture as a successful decision.
TECHNICAL DEEP DIVE & CODE ARTIFACTS Expand [+]
The evolution described in Bayer's system — from keyword search to an agent capable of drafting regulatory documents — is a roadmap for what happens when you don't build evidence-first from the start: you end up retrofitting audit capability onto a system that was never designed to expose its reasoning. In a financial transaction context, retrofitting is not an option; the audit trail must be a design constraint from day zero, not a feature added before go-live. The practical consequence is that every RAG retrieval step needs to log not just what was returned but what query produced it and what version of the retrieval index was active at query time.
AIThis connects directly to the provenance schema design in block_1: the tool_calls array in the schema is where retrieval artifacts live. A compliance team asking 'why did the agent approve this transaction despite a borderline risk score?' should be able to pull the provenance record, find the retrieval tool call, and see exactly which policy document was fetched and what passage the agent cited. Without that, the audit is a black box with timestamps.
// TypeScript: phase wrapper that guarantees provenance before state advance
async function runPhaseWithProvenance<TInput, TOutput>(
phaseName: string,
schemaVersion: string,
input: TInput,
executor: (input: TInput) => Promise<{
outcome: 'pass' | 'deny' | 'escalate' | 'error';
rationale: string;
toolCalls: ToolCall[];
result: TOutput;
}>,
ctx: { correlationId: string; transactionId: string; store: ProvenanceStore }
): Promise<TOutput> {
const enteredAt = new Date().toISOString();
let executorResult;
try {
executorResult = await executor(input);
} catch (err) {
// still emit provenance on error — do not swallow the audit trail
await emitPhaseProvenance({
correlation_id: ctx.correlationId,
transaction_id: ctx.transactionId,
phase_name: phaseName,
phase_schema_version: schemaVersion,
entered_at: enteredAt,
exited_at: new Date().toISOString(),
input_snapshot: input as Record<string, unknown>,
model_id: null,
tool_calls: [],
phase_outcome: 'error',
outcome_rationale: `Phase failed: ${(err as Error).message}`,
human_override: false,
human_override_actor: null
}, ctx.store);
throw err;
}
await emitPhaseProvenance({
correlation_id: ctx.correlationId,
transaction_id: ctx.transactionId,
phase_name: phaseName,
phase_schema_version: schemaVersion,
entered_at: enteredAt,
exited_at: new Date().toISOString(),
input_snapshot: input as Record<string, unknown>,
model_id: null,
tool_calls: executorResult.toolCalls,
phase_outcome: executorResult.outcome,
outcome_rationale: executorResult.rationale,
human_override: false,
human_override_actor: null
}, ctx.store);
return executorResult.result;
}3. Multi-Agent Handoff Traceability in Support Triage Pipelines
CrewAI's support triage case makes visible the core handoff problem: when agents pass work between each other, the provenance boundary has to move with the work, not stay attached to the originating agent.
- Handoff records are a distinct provenance type from phase records — they capture the routing decision, the criteria that triggered it, and the receiving agent's identity, separate from what either agent actually did.
- Routing accuracy degrades silently in multi-agent pipelines unless each handoff writes a confidence score and the criteria used — without that, you can't tell the difference between correct routing and lucky routing.
- The correlation ID from block_1 is what makes handoff records joinable to phase records — a shared root ID threaded through every agent in the chain is non-negotiable for reconstruction.
TECHNICAL DEEP DIVE & CODE ARTIFACTS Expand [+]
The 'Ticket Triage Triangle' problem CrewAI describes — incomplete data, slow routing, and missed real-time events — maps almost exactly to the three failure modes in financial agent pipelines: missing transaction context, misrouted escalations, and stale risk signals. The engineering fix in both domains is the same: observable handoffs with explicit routing criteria captured at the moment of transfer, not inferred afterward from logs. In a financial pipeline, a handoff from a risk scoring agent to a human review queue is a compliance event, not just an operational one — and it needs a record that says 'this transaction was escalated because field X exceeded threshold Y at time Z, not just that it landed in the queue.'
Source: How a Mid-Tier Enterprise SaaS Provider Automates Cloud Support Triage — CrewAI Blog (https://blog.crewai.com/mid-tier-enterprise-saas-automates-cloud-support-triage/)
interface AgentHandoffRecord {
handoff_id: string;
correlation_id: string; // same root as phase records
transaction_id: string;
from_agent: string;
to_agent: string;
handoff_at: string; // ISO datetime
routing_criteria: {
field: string;
operator: string;
threshold: unknown;
observed_value: unknown;
}[];
routing_confidence: number | null; // null for deterministic rule-based routing
human_override: boolean;
payload_snapshot: Record<string, unknown>; // what was handed off
}
// Usage at handoff point inside a CrewAI task or LangGraph edge
const handoff: AgentHandoffRecord = {
handoff_id: randomUUID(),
correlation_id: ctx.correlationId,
transaction_id: ctx.transactionId,
from_agent: 'risk_scoring_agent',
to_agent: 'human_review_queue',
handoff_at: new Date().toISOString(),
routing_criteria: [{
field: 'risk_score',
operator: 'gt',
threshold: 80,
observed_value: riskScore
}],
routing_confidence: null, // deterministic rule, not probabilistic
human_override: false,
payload_snapshot: transactionSnapshot
};
await provenanceStore.writeHandoff(handoff);4. Infrastructure Drift Governance as a Provenance Integrity Threat
If the infrastructure running your agent pipeline drifts from its declared state — a different model endpoint, an untracked Lambda version, an undeclared policy store — then provenance records become unreliable even if the logging code is perfect.
- Infrastructure drift invalidates provenance by breaking the chain between 'what the record says was used' and 'what was actually running' — a risk score computed by model-v3 when the record says model-v2 is a compliance failure.
- IaC state and agent configuration should be treated as provenance inputs — the deployed model version, retrieval index commit, and policy document hash all belong in the phase schema's execution_context block.
- Drift detection pipelines for agentic infrastructure should emit alerts to the same observability plane as agent provenance — keeping them separate creates blind spots where infrastructure changes are invisible to compliance teams.
TECHNICAL DEEP DIVE & CODE ARTIFACTS Expand [+]
HashiCorp's framing of organizations 'losing visibility into AI infrastructure' describes exactly what happens in regulated environments when teams move fast: resources get created outside standard IaC workflows, model endpoints get updated without a version bump in the deployment manifest, and the provenance record dutifully logs a model ID that no longer maps to what was actually running. The fix isn't just better Terraform discipline — it's treating the infrastructure state as an input to the provenance schema, so every phase record includes a hash of the infrastructure configuration that was active when it ran. That hash can then be validated against the IaC state store at audit time.
Source: Discover, govern, and scale Azure infrastructure in the AI era — HashiCorp Blog (https://www.hashicorp.com/blog/discover-govern-and-scale-azure-infrastructure-in-the-ai-era)
// Inject infrastructure execution context into every phase provenance record
interface ExecutionContext {
model_id: string;
model_version: string;
retrieval_index_commit: string;
policy_document_hash: string;
infrastructure_config_hash: string; // SHA-256 of IaC-resolved config
agent_image_digest: string; // container image digest
}
async function resolveExecutionContext(
configStore: ConfigStore
): Promise<ExecutionContext> {
const [model, index, policy, infra, image] = await Promise.all([
configStore.get('ACTIVE_MODEL_VERSION'),
configStore.get('RETRIEVAL_INDEX_COMMIT'),
configStore.getHash('POLICY_DOCUMENTS'),
configStore.getInfraHash(), // from IaC state API
configStore.get('AGENT_IMAGE_DIGEST')
]);
return {
model_id: model.id,
model_version: model.version,
retrieval_index_commit: index,
policy_document_hash: policy,
infrastructure_config_hash: infra,
agent_image_digest: image
};
}
// Add to PhaseProvenanceRecord schema:
// execution_context: ExecutionContext <-- frozen at phase entry, not phase exit5. Secret and Credential Hygiene Inside Agent Provenance Pipelines
Your provenance records will contain sensitive financial data and your pipeline will hold API keys, model tokens, and database credentials — treating those the same way GitHub treated secret hygiene (find everything, assign ownership, remediate) is the only path to a defensible audit posture.
- Provenance records must be scrubbed of PII and credentials before being written to any log store — the schema should have explicit redaction markers, not rely on the caller to sanitize correctly.
- Tool call inputs in the provenance record are the highest-risk field — they can contain raw transaction data, customer identifiers, and model prompts that include retrieved context with sensitive content.
- Secret scanning applied to agent configuration repositories catches credential leakage before it contaminates the provenance store — a leaked API key in a config file means every provenance record referencing that endpoint is potentially compromised.
TECHNICAL DEEP DIVE & CODE ARTIFACTS Expand [+]
GitHub's discovery of 20,000 secrets across 15,000 repositories is a useful benchmark for how badly credential hygiene scales without systematic enforcement — and agentic pipeline repositories are worse than average because they accumulate model API keys, retrieval database credentials, and policy store access tokens in configuration files, environment variable templates, and test fixtures. The provenance schema needs a first-class redaction strategy: fields like input_snapshot and tool_calls.input should be filtered through a PII and secret detector before write, with a redaction_applied boolean and a list of redacted field paths added to the record so auditors know what was sanitized rather than wondering why fields are empty.
Source: How GitHub used secret scanning to reach inbox zero — GitHub Blog (https://github.blog/security/application-security/how-github-used-secret-scanning-to-reach-inbox-zero/)
// TypeScript: provenance-safe redaction before write
const REDACT_PATTERNS: RegExp[] = [
/\bsk-[A-Za-z0-9]{32,}\b/g, // OpenAI-style API keys
/\b[A-Z0-9]{20}:[A-Za-z0-9+/]{40}\b/g, // AWS-style credentials
/\b\d{4}[- ]?\d{4}[- ]?\d{4}[- ]?\d{4}\b/g // card numbers
];
function redactSensitiveFields(
snapshot: Record<string, unknown>
): { sanitized: Record<string, unknown>; redacted_paths: string[] } {
const redacted_paths: string[] = [];
function walk(obj: Record<string, unknown>, path: string): Record<string, unknown> {
return Object.fromEntries(
Object.entries(obj).map(([k, v]) => {
const currentPath = path ? `${path}.${k}` : k;
if (typeof v === 'string') {
const hadSecret = REDACT_PATTERNS.some(r => r.test(v));
if (hadSecret) {
redacted_paths.push(currentPath);
return [k, '[REDACTED]'];
}
}
if (v && typeof v === 'object' && !Array.isArray(v)) {
return [k, walk(v as Record<string, unknown>, currentPath)];
}
return [k, v];
})
);
}
return { sanitized: walk(snapshot, ''), redacted_paths };
}6. Agentic Loop Engineering and Context Discipline for Long Audit Chains
When your agent runs inside a loop — a retry, a re-evaluation, a human correction cycle — each iteration needs its own provenance record with a loop sequence number, because 'attempt 3 succeeded' is not auditable if you can't see what attempts 1 and 2 produced.
- Loop iterations are not retries in the traditional sense — they may change the input, the retrieved context, or the model parameters, all of which must be captured distinctly per iteration rather than collapsed into a single record.
- Context window management inside long agentic loops is a provenance integrity concern: if the agent's context is truncated between iterations, the provenance record must flag which prior context was dropped and why.
- Codex and Claude Code style agentic loops — with memory, tools, planning, and execution stages — map cleanly to per-phase provenance if you treat each stage as a named phase boundary rather than internal model state.
TECHNICAL DEEP DIVE & CODE ARTIFACTS Expand [+]
Addy Osmani's framing of the agentic loop as five building blocks plus a memory store maps well onto the provenance schema: planning, tool selection, execution, reflection, and memory update are each a phase boundary, and the memory store is the context that must be versioned and hashed at each phase entry. In a financial agent running a correction loop — where a human reviewer sends a transaction back for re-evaluation — the loop sequence number in the provenance record is the mechanism that lets a compliance auditor distinguish 'original decision' from 'post-human-review recompute' without having to diff timestamps manually. That distinction is often the entire question in a regulatory dispute.
Source: Loop Engineering — Addy Osmani (https://addyosmani.com/blog/loop-engineering/)
// Extend PhaseProvenanceRecord for loop-aware provenance
interface LoopAwarePhaseRecord extends PhaseProvenanceRecord {
loop_sequence: number; // 1-indexed iteration within this phase
is_correction_iteration: boolean; // true if triggered by human feedback
prior_iteration_id: string | null; // links to previous loop iteration record
context_truncation_occurred: boolean;
truncated_context_paths: string[]; // which context fields were dropped
}
// Utility to emit loop-aware record and link to predecessor
async function emitLoopPhaseProvenance(
base: Omit<PhaseProvenanceRecord, 'record_id' | 'tamper_hash'>,
loopMeta: {
loop_sequence: number;
is_correction_iteration: boolean;
prior_iteration_id: string | null;
context_truncation_occurred: boolean;
truncated_context_paths: string[];
},
store: ProvenanceStore
): Promise<LoopAwarePhaseRecord> {
const record_id = randomUUID();
const merged = { record_id, ...base, ...loopMeta };
const tamper_hash = await sha256(JSON.stringify(merged));
const record = { ...merged, tamper_hash } as LoopAwarePhaseRecord;
await store.writeAtomic(record);
return record;
}7. EKS Rollback as a Model for Safe Agent Infrastructure Version Management
AWS EKS version rollback is a direct analogy for what agent infrastructure governance needs: the ability to confidently advance a version knowing you can return to a known-good state if provenance divergence is detected post-deployment.
- Agent infrastructure upgrades — new model versions, retrieval index rebuilds, policy document updates — need rollback mechanics that restore not just the binaries but the provenance context those binaries produced.
- Provenance records written under a new infrastructure version should be flagged with a canary deployment marker so compliance teams can segment 'records from the new model' from 'records from the production model' during a controlled rollout.
- Rollback decisions in agentic infrastructure should be triggered by provenance divergence signals — outcome distribution shifts, unexpected escalation rate changes, or tamper hash verification failures — not just cluster health metrics.
TECHNICAL DEEP DIVE & CODE ARTIFACTS Expand [+]
The EKS rollback capability addresses a real operational fear: committing to a version upgrade without a recovery path. The same fear exists in agentic financial pipelines — upgrading the risk scoring model mid-week without a rollback plan means that if the new model produces systematically different outcomes, you have no clean way to partition the audit trail and no mechanism to restore the prior model state. The provenance schema's execution_context block from block_4 is the tool that makes rollback auditable: if you can hash the infrastructure configuration at phase entry and compare it against the IaC rollback snapshot, you can tell compliance exactly which transactions ran under which model version, and which ran during the transition window.
Source: Announcing Amazon EKS Rollback for safe and reliable management of cluster upgrades — AWS Blog (https://aws.amazon.com/blogs/containers/announcing-amazon-eks-rollback-for-safe-and-reliable-management-of-cluster-upgrades/)
// Mark provenance records during canary deployment windows
interface DeploymentMarker {
deployment_id: string;
is_canary: boolean;
canary_percentage: number | null; // null when not canary
infrastructure_config_hash: string;
rollback_target_hash: string | null; // hash of the version to restore to
}
// Injected at startup from deployment config, shared across all phase records
const deploymentMarker: DeploymentMarker = await configStore.getDeploymentMarker();
// Divergence check: compare outcome distributions between canary and stable
async function detectProvenanceDivergence(
store: ProvenanceStore,
canaryHash: string,
stableHash: string,
windowMinutes: number = 30
): Promise<{ diverged: boolean; escalation_rate_delta: number }> {
const since = new Date(Date.now() - windowMinutes * 60_000).toISOString();
const [canary, stable] = await Promise.all([
store.getOutcomeDistribution(canaryHash, since),
store.getOutcomeDistribution(stableHash, since)
]);
const delta = canary.escalation_rate - stable.escalation_rate;
return { diverged: Math.abs(delta) > 0.05, escalation_rate_delta: delta };
}