Decisions as Data

In ContextGraph OS, decisions are first-class data objects with full lifecycle tracking. Every significant decision made by agents or users is recorded, tracked, and auditable.

Why Track Decisions?

AI agents make countless decisions. Without tracking:

  • You can't explain why something happened
  • You can't audit agent behavior
  • You can't learn from past decisions
  • You can't ensure compliance

ContextGraph solves this by treating decisions as data.

Decision Structure

interface Decision {
  id: DecisionId;
  type: DecisionType;
  title: string;
  description?: string;
  status: DecisionStatus;
  riskLevel: RiskLevel;
  proposedBy: AgentId;
  proposedAt: Timestamp;
  approvedBy?: AgentId;
  approvedAt?: Timestamp;
  rejectedBy?: AgentId;
  rejectedAt?: Timestamp;
  executedAt?: Timestamp;
  completedAt?: Timestamp;
  context: DecisionContext;
  outcome?: DecisionOutcome;
}

type DecisionStatus =
  | 'proposed'
  | 'approved'
  | 'rejected'
  | 'executed'
  | 'completed'
  | 'failed';

type RiskLevel = 'low' | 'medium' | 'high' | 'critical';

Decision Lifecycle

┌──────────┐
│ PROPOSED │ ─────────────────────────────┐
└────┬─────┘                              │
     │                                    │
     ▼                                    ▼
┌──────────┐                        ┌──────────┐
│ APPROVED │                        │ REJECTED │
└────┬─────┘                        └──────────┘
     │
     ▼
┌──────────┐
│ EXECUTED │
└────┬─────┘
     │
     ├──────────────────┐
     ▼                  ▼
┌──────────┐      ┌──────────┐
│COMPLETED │      │  FAILED  │
└──────────┘      └──────────┘

Recording Decisions

Basic Decision

const decision = await client.recordDecision({
  type: 'data_processing',
  title: 'Process customer data batch',
  proposedBy: agentId,
  riskLevel: 'low',
});

Decision with Context

const decision = await client.recordDecision({
  type: 'deployment',
  title: 'Deploy version 2.0.0 to production',
  description: 'Release includes new auth system and performance improvements',
  proposedBy: agentId,
  riskLevel: 'high',
  context: {
    environment: 'production',
    affectedServices: ['api', 'web', 'worker'],
    rollbackPlan: 'Revert to v1.9.5',
    estimatedDowntime: '0 minutes',
  },
});

Approving and Rejecting

Approval

await client.approveDecision(
  decisionId,
  approverId,
  'Approved after security review'
);

Rejection

await client.rejectDecision(
  decisionId,
  reviewerId,
  'Needs additional testing before deployment'
);

Conditional Approval

// Check conditions before approving
const decision = await dtg.get(decisionId);

if (decision.value.data.riskLevel === 'critical') {
  // Critical decisions need multiple approvers
  const approvals = await dtg.getApprovals(decisionId);
  if (approvals.length < 2) {
    throw new Error('Critical decisions require 2 approvals');
  }
}

await client.approveDecision(decisionId, approverId);

Executing Decisions

Once approved, decisions can be executed:

// Mark as executed
await dtg.updateStatus(decisionId, 'executed', {
  executedAt: createTimestamp(),
  executor: agentId,
});

// Perform the actual work
const result = await performDeployment(decision.context);

// Mark as completed or failed
if (result.success) {
  await dtg.updateStatus(decisionId, 'completed', {
    completedAt: createTimestamp(),
    outcome: {
      success: true,
      metrics: result.metrics,
    },
  });
} else {
  await dtg.updateStatus(decisionId, 'failed', {
    completedAt: createTimestamp(),
    outcome: {
      success: false,
      error: result.error,
    },
  });
}

Querying Decisions

Get Pending Decisions

const pending = await client.getPendingDecisions();

for (const decision of pending.value) {
  console.log(`${decision.data.title} - ${decision.data.riskLevel}`);
}

Query by Status

const approved = await dtg.queryDecisions({
  status: 'approved',
  from: startOfMonth,
  to: endOfMonth,
});

Query by Agent

const agentDecisions = await dtg.queryDecisions({
  proposedBy: agentId,
  limit: 50,
});

Query by Risk Level

const highRisk = await dtg.queryDecisions({
  riskLevel: ['high', 'critical'],
  status: 'proposed',
});

Decision Types

Define decision types for your domain:

type DecisionType =
  // Operational
  | 'deployment'
  | 'rollback'
  | 'scaling'
  | 'maintenance'

  // Data
  | 'data_processing'
  | 'data_deletion'
  | 'data_export'

  // Access
  | 'access_grant'
  | 'access_revoke'

  // Business
  | 'approval'
  | 'exception'
  | 'escalation';

Risk Assessment

Risk Levels

// Low: Routine, easily reversible
{ riskLevel: 'low' }

// Medium: Some impact, recoverable
{ riskLevel: 'medium' }

// High: Significant impact, careful execution needed
{ riskLevel: 'high' }

// Critical: Major impact, requires multiple approvals
{ riskLevel: 'critical' }

Automatic Risk Escalation

import { createRecommendationEngine } from '@contextgraph/recommendations';

const engine = createRecommendationEngine(dtg, storage);

// Check risk before executing
const assessment = await engine.assessRisk({
  action: 'delete',
  entityType: 'User',
  resource: '/api/users/bulk',
  attributes: { count: 1000 },
});

if (assessment.riskLevel === 'high') {
  // Escalate for review
  await decision.escalate('Bulk deletion requires manager approval');
}

Decision Recommendations

Use historical decisions to inform new ones:

const recommendation = await engine.recommend({
  action: 'deploy',
  entityType: 'Service',
  resource: 'api-gateway',
  attributes: {
    environment: 'production',
    changeType: 'configuration',
  },
});

console.log(`Recommendation: ${recommendation.action}`);
console.log(`Confidence: ${recommendation.confidence}`);
console.log(`Based on ${recommendation.precedents.length} similar decisions`);

Audit Integration

Decisions integrate with the audit system:

// Get full decision history
const history = await dtg.getDecisionHistory(decisionId);

// Includes all state changes with timestamps and actors
for (const event of history) {
  console.log(`${event.timestamp}: ${event.previousStatus} → ${event.newStatus}`);
  console.log(`  By: ${event.actor}`);
  console.log(`  Reason: ${event.reason}`);
}

Best Practices

1. Be Specific About Risk

// Good - clear risk assessment
{
  riskLevel: 'high',
  context: {
    reason: 'Affects production database',
    mitigations: ['Backup taken', 'Rollback script ready']
  }
}

// Avoid - vague risk
{ riskLevel: 'medium' }  // Why medium?

2. Always Record Outcomes

// Good - complete outcome
await dtg.updateStatus(id, 'completed', {
  outcome: {
    success: true,
    duration: 45000,
    affectedRecords: 1523,
    metrics: { cpu: '12%', memory: '45%' }
  }
});

// Avoid - missing outcome
await dtg.updateStatus(id, 'completed');
// Reference related decisions
await client.recordDecision({
  type: 'rollback',
  title: 'Rollback v2.0.0 deployment',
  context: {
    relatedDecisionId: originalDeploymentId,
    reason: 'Performance degradation detected'
  }
});

Next Steps