Decision Trace Graph

Complete guide to tracking, auditing, and querying agent decisions.

Why Decision Traces Matter

Traditional AI systems operate as black boxes:

Input → [???] → Output

ContextGraph OS makes every decision transparent:

Input → Context Assembly → Policy Check → Evidence Linking → Execution → Audit Trail
           │                   │                │              │            │
           └───────────────────┴────────────────┴──────────────┴────────────┘
                            All recorded in Decision Trace Graph

Decision Lifecycle

Every decision follows a strict lifecycle:

┌──────────┐     ┌──────────┐     ┌──────────┐     ┌──────────┐
│ PROPOSED │────►│ APPROVED │────►│ EXECUTED │────►│COMPLETED │
└──────────┘     └──────────┘     └──────────┘     └──────────┘
      │                │                │                │
      │          ┌─────┴─────┐    ┌─────┴─────┐    ┌─────┴─────┐
      │          │ REJECTED  │    │ CANCELLED │    │  FAILED   │
      │          └───────────┘    └───────────┘    └───────────┘
      │
      └─────────► NEEDS_REVIEW ────► Human Queue

State Definitions

StateDescriptionCan Transition To
PROPOSEDDecision submitted by agentAPPROVED, REJECTED, NEEDS_REVIEW
APPROVEDPassed policy or human reviewEXECUTED, CANCELLED
REJECTEDDenied by policy or reviewer(terminal)
NEEDS_REVIEWAwaiting human approvalAPPROVED, REJECTED
EXECUTEDAction dispatchedCOMPLETED, FAILED
COMPLETEDSuccessfully finished(terminal)
FAILEDExecution error(terminal)
CANCELLEDWithdrawn before execution(terminal)

Decision Anatomy

interface Decision {
  // Identity
  id: DecisionId;
  type: string;                    // e.g., "publish_report"

  // Current state
  status: DecisionStatus;

  // Who proposed this?
  proposedBy: AgentId;
  proposedAt: Timestamp;

  // What action?
  action: {
    type: string;
    target: string;
    parameters: Record<string, unknown>;
  };

  // What evidence was considered?
  evidenceIds: ClaimId[];

  // Risk assessment
  risk: {
    level: "LOW" | "MEDIUM" | "HIGH" | "CRITICAL";
    score: number;              // 0-1
    factors: RiskFactor[];
  };

  // Policy evaluation
  policyResult: {
    effect: "allow" | "deny";
    matchedPolicies: PolicyId[];
    conditions: EvaluatedCondition[];
  };

  // Approval (if needed)
  approvedBy?: AgentId | UserId;
  approvedAt?: Timestamp;
  approvalReason?: string;

  // Rejection (if denied)
  rejectedBy?: AgentId | UserId;
  rejectedAt?: Timestamp;
  rejectionReason?: string;

  // Execution result
  executedAt?: Timestamp;
  completedAt?: Timestamp;
  result?: unknown;
  error?: string;

  // Full history
  history: DecisionEvent[];
}

Creating Decisions

Basic Decision

import { DecisionTraceGraph } from '@contextgraph/dtg';

const dtg = new DecisionTraceGraph(storage);

// Create a decision
const decision = await dtg.createDecision({
  type: "publish_report",
  proposedBy: agentId,
  action: {
    type: "publish",
    target: "external_audience",
    parameters: {
      reportId: "report_123",
      format: "pdf",
    },
  },
  evidenceIds: [claim1.id, claim2.id, claim3.id],
});

console.log(decision.status); // "proposed"

With Risk Assessment

const decision = await dtg.createDecision({
  type: "delete_customer_data",
  proposedBy: agentId,
  action: {
    type: "delete",
    target: "customer_records",
    parameters: { customerId: "cust_456" },
  },
  risk: {
    level: "HIGH",
    score: 0.85,
    factors: [
      { type: "data_sensitivity", weight: 0.4, description: "PII data" },
      { type: "irreversibility", weight: 0.3, description: "Permanent deletion" },
      { type: "volume", weight: 0.15, description: "Multiple records" },
    ],
  },
  evidenceIds: [deletionRequestClaim.id],
});

State Transitions

Approve Decision

// After policy evaluation or human review
await dtg.transitionDecision(decision.id, "approved", {
  approvedBy: reviewerId,
  reason: "Verified customer deletion request is legitimate",
});

Reject Decision

await dtg.transitionDecision(decision.id, "rejected", {
  rejectedBy: policyEngineId,
  reason: "Policy 'pii-protection' denies deletion without manager approval",
});

Execute Decision

// After approval
await dtg.transitionDecision(decision.id, "executed");

// When execution completes
await dtg.transitionDecision(decision.id, "completed", {
  result: { deletedRecords: 15 },
});

// Or if execution fails
await dtg.transitionDecision(decision.id, "failed", {
  error: "Database connection timeout",
});

Querying Decisions

By Status

// Get all pending decisions
const pending = await dtg.queryDecisions({
  status: "proposed",
});

// Get decisions needing review
const needsReview = await dtg.queryDecisions({
  status: "needs_review",
});

By Agent

// All decisions by a specific agent
const agentDecisions = await dtg.queryDecisions({
  proposedBy: agentId,
});

// High-risk decisions by agent
const highRisk = await dtg.queryDecisions({
  proposedBy: agentId,
  riskLevel: ["HIGH", "CRITICAL"],
});

By Time Range

// Decisions from last 24 hours
const recent = await dtg.queryDecisions({
  proposedAfter: createTimestamp(Date.now() - 24 * 60 * 60 * 1000),
});

// Decisions in Q4
const q4Decisions = await dtg.queryDecisions({
  proposedAfter: createTimestamp('2024-10-01'),
  proposedBefore: createTimestamp('2024-12-31'),
});

By Type

// All publish decisions
const publishes = await dtg.queryDecisions({
  type: "publish_report",
});

// Multiple types
const modifications = await dtg.queryDecisions({
  types: ["update", "delete", "create"],
});

Decision History

Every state change is recorded:

const decision = await dtg.getDecision(decisionId);

for (const event of decision.history) {
  console.log(`${event.timestamp}: ${event.fromStatus} → ${event.toStatus}`);
  console.log(`  By: ${event.actor}`);
  console.log(`  Reason: ${event.reason}`);
}

// Output:
// 2024-03-15T10:00:00Z: null → proposed
//   By: agent:report-generator
//   Reason: null
// 2024-03-15T10:00:05Z: proposed → needs_review
//   By: policy:high-risk-guard
//   Reason: Action risk level HIGH requires human approval
// 2024-03-15T14:30:00Z: needs_review → approved
//   By: user:compliance-officer
//   Reason: Verified report accuracy and authorization
// 2024-03-15T14:30:01Z: approved → executed
//   By: system:executor
//   Reason: null
// 2024-03-15T14:30:05Z: executed → completed
//   By: system:executor
//   Reason: Successfully published to external audience

Evidence Linking

Decisions are linked to their supporting evidence:

// Get evidence for a decision
const evidence = await dtg.getDecisionEvidence(decision.id);

for (const claim of evidence) {
  console.log(`Claim: ${claim.value}`);
  console.log(`  Confidence: ${claim.confidence}`);
  console.log(`  Source: ${claim.source}`);
  console.log(`  Valid: ${claim.validFrom} - ${claim.validUntil}`);
}

Evidence Sufficiency

Check if decision has sufficient evidence:

const sufficiency = await dtg.checkEvidenceSufficiency(decision.id, {
  minClaims: 2,
  minConfidence: 0.8,
  requiredSources: ["internal", "verified"],
});

if (!sufficiency.sufficient) {
  console.log(`Missing: ${sufficiency.gaps}`);
  // e.g., "Missing: verified source claim"
}

Audit Reports

Decision Summary

const summary = await dtg.generateSummary({
  timeRange: {
    from: createTimestamp('2024-01-01'),
    to: createTimestamp('2024-03-31'),
  },
  groupBy: "agent",
});

// Output:
// {
//   "agent:report-generator": {
//     total: 150,
//     approved: 120,
//     rejected: 25,
//     failed: 5,
//     avgRiskScore: 0.45,
//   },
//   "agent:data-processor": {
//     total: 500,
//     approved: 480,
//     rejected: 15,
//     failed: 5,
//     avgRiskScore: 0.25,
//   }
// }

Export for Compliance

const report = await dtg.exportForCompliance({
  format: "json",
  timeRange: { from: q4Start, to: q4End },
  includeEvidence: true,
  includeHistory: true,
  redactPII: true,
});

// Save for compliance records
await writeFile("q4-decisions.json", JSON.stringify(report));

Visualization

import { visualizeDecisions } from '@contextgraph/viz';

// Generate Mermaid diagram of decision flow
const diagram = await visualizeDecisions(dtg, decision.id, {
  format: "mermaid",
  includeEvidence: true,
  includePolicy: true,
});

// Output:
// ```mermaid
// flowchart TD
//     P[Proposed] --> |policy check| NR[Needs Review]
//     NR --> |human approval| A[Approved]
//     A --> |dispatch| E[Executed]
//     E --> |success| C[Completed]
//
//     subgraph Evidence
//         E1[Claim: Revenue +15%]
//         E2[Claim: Q4 Verified]
//     end
// ```

Best Practices

// Good: Decision with evidence
const decision = await dtg.createDecision({
  type: "approve_loan",
  evidenceIds: [creditScore.id, income.id, history.id],
  // ...
});

// Bad: Decision without evidence
const decision = await dtg.createDecision({
  type: "approve_loan",
  evidenceIds: [], // No audit trail!
  // ...
});

2. Use Meaningful Types

// Good: Specific types
type: "publish_quarterly_report"
type: "delete_customer_pii"
type: "escalate_support_ticket"

// Bad: Vague types
type: "action"
type: "do_thing"
type: "process"

3. Include Risk Assessment

// For any action that modifies data or has external effects
risk: {
  level: calculateRiskLevel(action),
  score: calculateRiskScore(action),
  factors: identifyRiskFactors(action),
}

4. Provide Transition Reasons

// Always explain why a transition occurred
await dtg.transitionDecision(id, "rejected", {
  rejectedBy: policyId,
  reason: "Confidence threshold not met (required: 0.8, actual: 0.65)",
});

Integration with Policy Engine

Decisions integrate with policy evaluation:

import { PolicyEngine } from '@contextgraph/policy';
import { DecisionTraceGraph } from '@contextgraph/dtg';

const policy = new PolicyEngine(policyLedger);
const dtg = new DecisionTraceGraph(storage);

// Create decision
const decision = await dtg.createDecision({
  type: "publish",
  action: { type: "publish", target: "external" },
  // ...
});

// Evaluate against policies
const result = await policy.evaluate({
  subject: decision.proposedBy,
  action: decision.action.type,
  resource: decision.action.target,
  context: { risk: decision.risk },
});

// Transition based on result
if (result.effect === "allow") {
  await dtg.transitionDecision(decision.id, "approved", {
    approvedBy: "policy:auto-approve",
    reason: `Allowed by policy ${result.matchedPolicies[0]}`,
  });
} else if (result.requiresApproval) {
  await dtg.transitionDecision(decision.id, "needs_review", {
    reason: `Requires human approval: ${result.reason}`,
  });
} else {
  await dtg.transitionDecision(decision.id, "rejected", {
    rejectedBy: result.matchedPolicies[0],
    reason: result.reason,
  });
}

Next Steps