ContextGraph OS
A provenance-first, time-aware, decision-trace substrate for auditable AI agents.
What is ContextGraph OS?
ContextGraph OS provides the foundational infrastructure for building AI agent systems where every action, decision, and piece of knowledge is fully traceable and auditable. It's designed for organizations that need:
- Complete audit trails for AI agent decisions
- Provenance tracking for all data and claims
- Temporal awareness with explicit validity periods
- Policy enforcement with deny-takes-precedence semantics
- Compliance support for regulatory requirements (GDPR, SOC2, etc.)
Core Principles
1. Provenance First
Every claim in the system requires a source. No orphan data. This ensures you can always trace back to where information came from.
// Every claim has provenance
await client.addClaim({
subjectId: entityId,
predicate: 'status',
value: 'active',
// Provenance is automatically tracked
});
// Verify the chain
const verification = await client.verifyProvenance();
console.log(`Chain valid: ${verification.value.valid}`);
2. Time as First-Class Citizen
All data is temporally qualified with explicit validity periods. Query data as it was at any point in time.
await client.addClaim({
subjectId: entityId,
predicate: 'temperature',
value: 72,
context: {
validFrom: createTimestamp('2024-01-01'),
validUntil: createTimestamp('2024-12-31'),
},
});
3. Decisions as Data
Agent decisions are tracked with full lifecycle and audit trails. Every decision goes through a defined state machine.
// Record a decision
const decision = await client.recordDecision({
type: 'workflow_step',
title: 'Deploy to production',
proposedBy: agentId,
riskLevel: 'high',
});
// Approve it
await client.approveDecision(decision.value.data.id, approverId);
4. Context Filtering
Query data by time, jurisdiction, scope, and confidence level.
const context = await client.assembleContext(entityId, {
asOf: createTimestamp('2024-06-15'),
jurisdiction: 'EU',
minConfidence: 0.8,
});
5. Policy Enforcement
Deny-takes-precedence evaluation with approval workflows.
await client.createPolicy({
name: 'Restrict PII Access',
effect: 'deny',
subjects: ['*'],
actions: ['read'],
resources: ['pii/*'],
priority: 100,
});
Package Overview
ContextGraph OS is organized into layered packages:
| Layer | Packages | Purpose |
|---|---|---|
| Foundation | core, storage, ontology | Types, storage, schemas |
| Knowledge | ckg, provenance, retrieval | Knowledge graph, provenance, context |
| Governance | dtg, policy, exceptions, rbac, compliance | Decisions, policies, access control |
| Agent | agent, execution | Agent registry, action execution |
| Advanced | viz, reasoning, recommendations, telemetry | Visualization, inference, insights |
| Integration | sdk, api, cli, webhooks | APIs, CLI tools |
Quick Example
import { ContextGraph } from '@contextgraph/sdk';
// Create a client
const result = await ContextGraph.create();
const client = result.value;
// Create an entity
const person = await client.createEntity({
type: 'person',
name: 'Alice',
properties: { department: 'Engineering' },
});
// Add claims with provenance
await client.addClaim({
subjectId: person.value.data.id,
predicate: 'has_skill',
value: 'TypeScript',
});
// Create an agent
const agent = await client.createAgent({
name: 'assistant',
description: 'Research assistant',
});
// Execute actions with audit trail
await client.execute({
agentId: agent.value.data.id,
action: 'read',
resourceType: 'document',
resourceId: 'doc_123',
});
// Get audit trail
const audit = await client.getAuditTrail({ limit: 10 });
Next Steps
- Quick Start - Get up and running in 5 minutes
- Installation - Detailed installation guide
- Architecture - Understand the system design
- Core Concepts - Deep dive into principles
Quick Start
Get up and running with ContextGraph OS in 5 minutes.
Prerequisites
- Node.js 18+
- pnpm (recommended) or npm
Installation
# Clone the repository
git clone https://github.com/akz4ol/contextgraph-os.git
cd contextgraph-os
# Install dependencies
pnpm install
# Build all packages
pnpm -r build
# Run tests to verify
pnpm -r test
Your First ContextGraph Application
Create a new file demo.ts:
import { ContextGraph, createScope, createConfidence } from '@contextgraph/sdk';
import { ok } from '@contextgraph/core';
async function main() {
// 1. Create the ContextGraph client
const result = await ContextGraph.create();
if (!result.ok) {
console.error('Failed to create client:', result.error);
return;
}
const client = result.value;
console.log('ContextGraph client created!');
// 2. Create an entity
const personResult = await client.createEntity({
type: 'person',
name: 'Alice',
properties: {
department: 'Engineering',
level: 'senior'
},
});
if (!personResult.ok) {
console.error('Failed to create entity:', personResult.error);
return;
}
const person = personResult.value;
console.log(`Created person: ${person.data.name} (${person.data.id})`);
// 3. Add claims with context
await client.addClaim({
subjectId: person.data.id,
predicate: 'has_skill',
value: 'TypeScript',
context: {
scope: createScope('professional'),
confidence: createConfidence(0.95),
},
});
await client.addClaim({
subjectId: person.data.id,
predicate: 'has_skill',
value: 'Python',
context: {
scope: createScope('professional'),
confidence: createConfidence(0.85),
},
});
console.log('Added skill claims');
// 4. Query claims
const claimsResult = await client.getClaims(person.data.id);
if (claimsResult.ok) {
console.log('\nClaims for Alice:');
for (const claim of claimsResult.value) {
console.log(` - ${claim.data.predicate}: ${claim.data.value}`);
}
}
// 5. Create an agent
const agentResult = await client.createAgent({
name: 'assistant',
description: 'A helpful research assistant',
});
if (!agentResult.ok) {
console.error('Failed to create agent:', agentResult.error);
return;
}
const agent = agentResult.value;
console.log(`\nCreated agent: ${agent.data.name}`);
// 6. Register an action handler
client.registerHandler('analyze', 'document', async (action) => {
console.log(` [Handler] Analyzing document: ${action.resourceId}`);
return ok({
analyzed: true,
wordCount: 1500,
sentiment: 'positive'
});
});
// 7. Execute an action
const execResult = await client.execute({
agentId: agent.data.id,
action: 'analyze',
resourceType: 'document',
resourceId: 'doc_quarterly_report',
parameters: { depth: 'full' },
});
if (execResult.ok) {
console.log('\nAction executed successfully!');
}
// 8. Record a decision
const decisionResult = await client.recordDecision({
type: 'analysis_review',
title: 'Review quarterly report analysis',
proposedBy: agent.data.id,
riskLevel: 'low',
});
if (decisionResult.ok) {
console.log(`\nDecision recorded: ${decisionResult.value.data.title}`);
}
// 9. Verify provenance chain
const verifyResult = await client.verifyProvenance();
if (verifyResult.ok) {
console.log(`\nProvenance chain valid: ${verifyResult.value.valid}`);
console.log(`Entries verified: ${verifyResult.value.entriesVerified}`);
}
// 10. Get system statistics
const stats = await client.getStats();
if (stats.ok) {
console.log('\nSystem Statistics:');
console.log(` Entities: ${stats.value.entities}`);
console.log(` Claims: ${stats.value.claims}`);
console.log(` Agents: ${stats.value.agents}`);
console.log(` Decisions: ${stats.value.decisions}`);
}
}
main().catch(console.error);
Run the Demo
# Using ts-node
npx ts-node demo.ts
# Or compile and run
npx tsc demo.ts && node demo.js
Expected Output
ContextGraph client created!
Created person: Alice (ent_abc123...)
Added skill claims
Claims for Alice:
- has_skill: TypeScript
- has_skill: Python
Created agent: assistant
[Handler] Analyzing document: doc_quarterly_report
Action executed successfully!
Decision recorded: Review quarterly report analysis
Provenance chain valid: true
Entries verified: 5
System Statistics:
Entities: 1
Claims: 2
Agents: 1
Decisions: 1
Using the CLI
ContextGraph includes a powerful CLI for exploration:
# View system statistics
npx contextgraph stats
# List entities
npx contextgraph entities
# Inspect an entity
npx contextgraph entity <entity-id> --with-claims
# Start interactive REPL
npx contextgraph repl
Using the REST API
Start the API server:
npx contextgraph-api
Then use it via HTTP:
# Create an entity
curl -X POST http://localhost:3000/api/v1/entities \
-H "Content-Type: application/json" \
-d '{"type": "person", "name": "Bob"}'
# List entities
curl http://localhost:3000/api/v1/entities
# Get statistics
curl http://localhost:3000/api/v1/stats
Next Steps
- Installation - Detailed installation options
- First Steps - Deeper walkthrough
- Architecture - System design
- SDK Documentation - Full SDK reference
Installation
Requirements
- Node.js: 18.0.0 or higher
- Package Manager: pnpm (recommended), npm, or yarn
- TypeScript: 5.0+ (for development)
Installation Methods
From Source (Recommended)
# Clone the repository
git clone https://github.com/akz4ol/contextgraph-os.git
cd contextgraph-os
# Install dependencies
pnpm install
# Build all packages
pnpm -r build
# Verify installation
pnpm -r test
Individual Packages
Install only the packages you need:
# Core SDK (includes most functionality)
pnpm add @contextgraph/sdk
# Or install individual packages
pnpm add @contextgraph/core
pnpm add @contextgraph/storage
pnpm add @contextgraph/ckg
pnpm add @contextgraph/provenance
Package Dependencies
Here's how packages depend on each other:
@contextgraph/core (no dependencies)
└── @contextgraph/storage
└── @contextgraph/ontology
├── @contextgraph/ckg
├── @contextgraph/provenance
└── @contextgraph/dtg
├── @contextgraph/policy
├── @contextgraph/agent
└── @contextgraph/execution
└── @contextgraph/sdk
Configuration
TypeScript Configuration
Add to your tsconfig.json:
{
"compilerOptions": {
"target": "ES2022",
"module": "NodeNext",
"moduleResolution": "NodeNext",
"strict": true,
"esModuleInterop": true,
"skipLibCheck": true,
"declaration": true
}
}
Storage Configuration
ContextGraph supports multiple storage backends:
In-Memory (Default)
import { ContextGraph } from '@contextgraph/sdk';
const client = await ContextGraph.create({
storage: { type: 'memory' }
});
SQLite
const client = await ContextGraph.create({
storage: {
type: 'sqlite',
path: './contextgraph.db'
}
});
Development Setup
For contributing or modifying ContextGraph:
# Clone and setup
git clone https://github.com/akz4ol/contextgraph-os.git
cd contextgraph-os
pnpm install
# Build in watch mode
pnpm -r build:watch
# Run tests in watch mode
pnpm -r test:watch
# Run specific package tests
pnpm --filter @contextgraph/sdk test
Docker Setup (Coming Soon)
FROM node:18-alpine
WORKDIR /app
COPY package.json pnpm-lock.yaml ./
RUN npm install -g pnpm && pnpm install
COPY . .
RUN pnpm -r build
EXPOSE 3000
CMD ["npx", "contextgraph-api"]
Troubleshooting
Build Errors
If you encounter TypeScript errors:
# Clean and rebuild
pnpm -r clean
pnpm -r build
Module Resolution Issues
Ensure your project uses ES modules:
// package.json
{
"type": "module"
}
Missing Dependencies
# Reinstall all dependencies
rm -rf node_modules
pnpm install
Next Steps
- Quick Start - Build your first app
- Architecture - Understand the design
First Steps
This guide walks you through the fundamental concepts of ContextGraph OS with hands-on examples.
Understanding the Result Pattern
ContextGraph uses a Result pattern for error handling instead of exceptions:
import { ok, err, type Result } from '@contextgraph/core';
// All operations return Result<T, Error>
const result = await client.createEntity({...});
if (result.ok) {
// Success - access result.value
console.log(result.value.data.id);
} else {
// Error - access result.error
console.error(result.error.message);
}
// Or use helper methods
const entity = result.unwrapOr(defaultEntity);
Creating Entities
Entities are the fundamental data objects in ContextGraph:
// Create a person entity
const person = await client.createEntity({
type: 'person',
name: 'Alice Smith',
properties: {
email: 'alice@example.com',
department: 'Engineering',
},
});
// Create a project entity
const project = await client.createEntity({
type: 'project',
name: 'ContextGraph OS',
properties: {
status: 'active',
priority: 'high',
},
});
Adding Claims
Claims are statements about entities with temporal context:
import { createScope, createConfidence, createTimestamp } from '@contextgraph/sdk';
// Basic claim
await client.addClaim({
subjectId: person.data.id,
predicate: 'works_on',
value: project.data.id,
});
// Claim with full context
await client.addClaim({
subjectId: person.data.id,
predicate: 'role',
value: 'Tech Lead',
context: {
scope: createScope('work'),
confidence: createConfidence(1.0),
validFrom: createTimestamp('2024-01-01'),
validUntil: null, // Still valid
},
});
// Claim about another claim (meta-claims)
await client.addClaim({
subjectId: person.data.id,
predicate: 'has_certification',
value: 'AWS Solutions Architect',
context: {
scope: createScope('professional'),
confidence: createConfidence(0.95),
validFrom: createTimestamp('2023-06-15'),
validUntil: createTimestamp('2026-06-15'),
},
});
Querying Data
Get Claims for an Entity
const claims = await client.getClaims(person.data.id);
if (claims.ok) {
for (const claim of claims.value) {
console.log(`${claim.data.predicate}: ${claim.data.value}`);
}
}
Filter by Context
// Get claims valid at a specific time
const historicalClaims = await client.getClaims(person.data.id, {
asOf: createTimestamp('2024-06-01'),
});
// Get claims with minimum confidence
const confidentClaims = await client.getClaims(person.data.id, {
minConfidence: 0.9,
});
Working with Agents
Agents represent autonomous actors in the system:
// Create an agent
const agent = await client.createAgent({
name: 'data-processor',
description: 'Processes incoming data files',
capabilities: ['read', 'transform', 'write'],
});
// Register action handlers
client.registerHandler('transform', 'csv', async (action) => {
const { resourceId, parameters } = action;
// Transform the CSV file
return ok({
transformed: true,
rowCount: 1000
});
});
// Execute actions
const result = await client.execute({
agentId: agent.data.id,
action: 'transform',
resourceType: 'csv',
resourceId: 'data/input.csv',
parameters: {
outputFormat: 'json',
validate: true
},
});
Recording Decisions
Track important decisions with full audit trails:
// Record a decision
const decision = await client.recordDecision({
type: 'deployment',
title: 'Deploy v2.0.0 to production',
description: 'Release new version with performance improvements',
proposedBy: agent.data.id,
riskLevel: 'medium',
});
// Approve the decision
await client.approveDecision(
decision.value.data.id,
approverAgentId,
'Approved after review'
);
// Or reject it
await client.rejectDecision(
decision.value.data.id,
approverAgentId,
'Needs more testing'
);
Creating Policies
Define access control policies:
// Deny policy for sensitive data
await client.createPolicy({
name: 'Protect PII',
version: '1.0.0',
description: 'Restrict access to personally identifiable information',
effect: 'deny',
subjects: ['*'],
actions: ['read', 'export'],
resources: ['pii/*', 'sensitive/*'],
conditions: [
{
field: 'agent.clearance',
operator: 'less_than',
value: 'confidential',
},
],
priority: 100, // Higher = evaluated first
});
// Allow policy for specific roles
await client.createPolicy({
name: 'Admin Read Access',
version: '1.0.0',
effect: 'allow',
subjects: ['role:admin'],
actions: ['read'],
resources: ['*'],
priority: 50,
});
Verifying Provenance
Ensure data integrity:
// Verify the entire provenance chain
const verification = await client.verifyProvenance();
console.log(`Chain valid: ${verification.value.valid}`);
console.log(`Entries: ${verification.value.entriesVerified}`);
console.log(`Broken links: ${verification.value.brokenLinks}`);
console.log(`Invalid hashes: ${verification.value.invalidHashes}`);
Event Handling
React to system events:
// Subscribe to events
client.on('entity:created', (event) => {
console.log('New entity:', event.data.name);
});
client.on('claim:added', (event) => {
console.log('New claim:', event.data.predicate);
});
client.on('decision:approved', (event) => {
console.log('Decision approved:', event.data.title);
});
client.on('execution:completed', (event) => {
console.log('Action completed:', event.data.action);
});
Getting Statistics
Monitor your system:
const stats = await client.getStats();
console.log(`
System Statistics:
Entities: ${stats.value.entities}
Claims: ${stats.value.claims}
Agents: ${stats.value.agents}
Decisions: ${stats.value.decisions}
Policies: ${stats.value.policies}
Provenance entries: ${stats.value.provenanceEntries}
`);
Next Steps
- Architecture Overview - Understand system design
- Core Concepts - Deep dive into principles
- SDK Reference - Complete API documentation
Architecture Overview
ContextGraph OS is designed as a layered architecture where each layer builds upon the capabilities of the layers below it.
System Architecture
┌─────────────────────────────────────────────────────────────────┐
│ ContextGraph OS │
├─────────────────────────────────────────────────────────────────┤
│ Interface Layer │
│ ┌─────────────┐ ┌─────────────┐ ┌─────────────┐ │
│ │ REST API │ │ CLI │ │ Webhooks │ │
│ └─────────────┘ └─────────────┘ └─────────────┘ │
├─────────────────────────────────────────────────────────────────┤
│ SDK Layer │
│ ┌─────────────────────────────────────────────────┐ │
│ │ SDK │ │
│ │ (Unified API for all ContextGraph operations) │ │
│ └─────────────────────────────────────────────────┘ │
├─────────────────────────────────────────────────────────────────┤
│ Advanced Capabilities Layer │
│ ┌───────────┐ ┌───────────┐ ┌───────────────┐ ┌───────────┐ │
│ │ Viz │ │ Reasoning │ │Recommendations│ │ Telemetry │ │
│ └───────────┘ └───────────┘ └───────────────┘ └───────────┘ │
├─────────────────────────────────────────────────────────────────┤
│ Execution Layer │
│ ┌─────────────┐ ┌─────────────┐ │
│ │ Executor │ │ Handlers │ │
│ └─────────────┘ └─────────────┘ │
├─────────────────────────────────────────────────────────────────┤
│ Agent Layer │
│ ┌─────────────┐ ┌─────────────┐ ┌─────────────┐ │
│ │ Agents │ │ Capabilities│ │ Hierarchies │ │
│ └─────────────┘ └─────────────┘ └─────────────┘ │
├─────────────────────────────────────────────────────────────────┤
│ Governance Layer │
│ ┌───────────┐ ┌───────────┐ ┌───────────┐ ┌─────────────────┐│
│ │ Policy │ │Exceptions │ │ RBAC │ │ Compliance ││
│ └───────────┘ └───────────┘ └───────────┘ └─────────────────┘│
│ ┌─────────────────────────────────────────────────┐ │
│ │ Decision Trace Graph │ │
│ └─────────────────────────────────────────────────┘ │
├─────────────────────────────────────────────────────────────────┤
│ Knowledge Layer │
│ ┌─────────────┐ ┌─────────────┐ ┌─────────────┐ │
│ │ CKG │ │ Provenance │ │ Retrieval │ │
│ └─────────────┘ └─────────────┘ └─────────────┘ │
├─────────────────────────────────────────────────────────────────┤
│ Foundation Layer │
│ ┌─────────────┐ ┌─────────────┐ ┌─────────────┐ │
│ │ Core │ │ Storage │ │ Ontology │ │
│ └─────────────┘ └─────────────┘ └─────────────┘ │
└─────────────────────────────────────────────────────────────────┘
Layer Descriptions
Foundation Layer
The base layer providing essential primitives:
| Package | Purpose |
|---|---|
| core | Branded types, Result pattern, time utilities, error types |
| storage | Abstract storage interface with SQLite and in-memory implementations |
| ontology | Schema definitions, versioning, validation, code generation |
Knowledge Layer
Manages all knowledge and provenance:
| Package | Purpose |
|---|---|
| ckg | Contextual Knowledge Graph - entities and claims with temporal context |
| provenance | Immutable ledger tracking all data origins with hash chain verification |
| retrieval | Context assembly with temporal, scope, and confidence filtering |
Governance Layer
Handles policies, decisions, and access control:
| Package | Purpose |
|---|---|
| dtg | Decision Trace Graph - tracks decisions through their lifecycle |
| policy | Policy ledger with deny-takes-precedence evaluation |
| exceptions | Exception requests and approvals for policy overrides |
| rbac | Role-based access control with permission inheritance |
| compliance | Audit reports, GDPR features, compliance tracking |
Agent Layer
Manages autonomous agents and their capabilities:
| Package | Purpose |
|---|---|
| agent | Agent registry, capabilities, and problem-space graphs |
| execution | Action execution framework with policy enforcement |
Advanced Capabilities Layer
Specialized features for analysis and insight:
| Package | Purpose |
|---|---|
| viz | Visualization in DOT, Mermaid, D3.js, and SVG formats |
| reasoning | Semantic reasoning, inference rules, contradiction detection |
| recommendations | Decision recommendations based on precedents |
| telemetry | OpenTelemetry-compatible tracing, metrics, logging |
Interface Layer
External interfaces for system access:
| Package | Purpose |
|---|---|
| sdk | Unified TypeScript SDK for all operations |
| api | REST API with authentication and rate limiting |
| cli | Command-line tools and interactive REPL |
| webhooks | Event notifications via HTTP callbacks |
Data Flow
Claim Creation Flow
User/Agent Request
│
▼
┌───────┐
│ SDK │
└───┬───┘
│
▼
┌─────────────┐ ┌─────────────┐
│ Policy │────▶│ Enforce │
│ Evaluation │ │ Decision │
└─────────────┘ └──────┬──────┘
│
┌───────────────────┴───────────────────┐
▼ ▼
┌─────────────┐ ┌─────────────┐
│ CKG │ │ Provenance │
│ Store Claim │ │ Record │
└─────────────┘ └─────────────┘
│ │
└───────────────────┬───────────────────┘
▼
┌─────────────┐
│ Storage │
└─────────────┘
Decision Lifecycle
┌──────────┐ ┌──────────┐ ┌──────────┐ ┌──────────┐
│ PROPOSED │────▶│ APPROVED │────▶│ EXECUTED │────▶│COMPLETED │
└──────────┘ └────┬─────┘ └──────────┘ └──────────┘
│ │
│ ▼
│ ┌──────────┐
└─────────▶│ REJECTED │
└──────────┘
Key Design Patterns
1. Branded Types
All identifiers are branded types for type safety:
type EntityId = string & { readonly __brand: 'EntityId' };
type ClaimId = string & { readonly __brand: 'ClaimId' };
type AgentId = string & { readonly __brand: 'AgentId' };
2. Result Pattern
All operations return Result<T, Error> instead of throwing:
type Result<T, E = Error> =
| { ok: true; value: T }
| { ok: false; error: E };
3. Temporal Context
All data includes temporal qualifications:
interface TemporalContext {
validFrom: Timestamp;
validUntil: Timestamp | null;
observedAt: Timestamp;
}
4. Provenance Chain
Every data mutation creates a provenance entry with hash linking:
interface ProvenanceEntry {
id: ProvenanceId;
hash: string;
previousHash: string | null;
type: ProvenanceType;
data: unknown;
timestamp: Timestamp;
}
Storage Architecture
Abstract Interface
interface StorageInterface {
get<T>(key: string): Promise<Result<T | null>>;
set<T>(key: string, value: T): Promise<Result<void>>;
delete(key: string): Promise<Result<void>>;
list<T>(prefix: string): Promise<Result<T[]>>;
query<T>(query: Query): Promise<Result<T[]>>;
}
Implementations
- InMemoryStorage: Fast, ephemeral, great for testing
- SQLiteStorage: Persistent, ACID-compliant, production-ready
Extension Points
Custom Storage
Implement StorageInterface for custom backends:
class RedisStorage implements StorageInterface {
// Implementation
}
Custom Action Handlers
Register handlers for any action type:
client.registerHandler('custom', 'resource', async (action) => {
// Handle the action
return ok(result);
});
Custom Policy Conditions
Extend policy conditions:
{
field: 'custom.property',
operator: 'custom_op',
value: 'expected'
}
Next Steps
- Core Concepts - Understand the principles
- Package Documentation - Detailed package guides
- Tutorials - Hands-on examples
Provenance First
Provenance is the cornerstone of ContextGraph OS. Every piece of data in the system has a traceable origin, ensuring complete auditability and trust.
What is Provenance?
Provenance answers the question: "Where did this data come from?"
In ContextGraph, provenance tracking means:
- Every claim has a documented source
- Every change is recorded in an immutable ledger
- The entire history can be verified cryptographically
- No orphan data exists in the system
The Provenance Ledger
The provenance ledger is an append-only, hash-chained log of all data mutations:
interface ProvenanceEntry {
id: ProvenanceId;
hash: string; // SHA-256 hash of this entry
previousHash: string; // Link to previous entry
type: ProvenanceType; // claim_created, entity_created, etc.
subjectId: string; // What this is about
data: unknown; // Entry-specific data
source: ProvenanceSource;
timestamp: Timestamp;
agentId?: AgentId; // Who created this
}
type ProvenanceType =
| 'claim_created'
| 'claim_revoked'
| 'entity_created'
| 'entity_updated'
| 'decision_recorded'
| 'policy_created'
| 'execution_logged';
Hash Chain Verification
Each entry contains a hash of its contents and a reference to the previous entry's hash, creating an unbreakable chain:
Entry 1 Entry 2 Entry 3
┌─────────┐ ┌─────────┐ ┌─────────┐
│ hash: A │◄─────│prevHash:A│◄─────│prevHash:B│
│ prev: ∅ │ │ hash: B │ │ hash: C │
│ data... │ │ data... │ │ data... │
└─────────┘ └─────────┘ └─────────┘
To verify integrity:
const verification = await client.verifyProvenance();
if (verification.value.valid) {
console.log('Chain integrity verified');
console.log(`Entries: ${verification.value.entriesVerified}`);
} else {
console.log('Chain corrupted!');
console.log(`Broken links: ${verification.value.brokenLinks}`);
console.log(`Invalid hashes: ${verification.value.invalidHashes}`);
}
Provenance Sources
Every entry has a source describing its origin:
interface ProvenanceSource {
type: 'agent' | 'user' | 'system' | 'external' | 'inference';
id: string;
method?: string;
metadata?: Record<string, unknown>;
}
// Example sources
const agentSource = {
type: 'agent',
id: 'agt_processor',
method: 'data_extraction'
};
const externalSource = {
type: 'external',
id: 'weather_api',
method: 'GET /current',
metadata: { apiVersion: '2.0' }
};
const inferenceSource = {
type: 'inference',
id: 'reasoning_engine',
method: 'transitive_closure',
metadata: { confidence: 0.95 }
};
Automatic Provenance Tracking
When using the SDK, provenance is tracked automatically:
// When you add a claim...
await client.addClaim({
subjectId: entityId,
predicate: 'status',
value: 'active',
});
// A provenance entry is automatically created:
// {
// type: 'claim_created',
// subjectId: entityId,
// data: { predicate: 'status', value: 'active' },
// source: { type: 'system', id: 'sdk' },
// hash: '...',
// previousHash: '...'
// }
Querying Provenance
Get Entry by ID
const entry = await ledger.get(provenanceId);
Query by Subject
const entries = await ledger.query({
subjectId: entityId,
limit: 100
});
Query by Type
const claimEntries = await ledger.query({
type: 'claim_created',
from: startTimestamp,
to: endTimestamp
});
Query by Agent
const agentActions = await ledger.query({
agentId: agentId,
limit: 50
});
Provenance for Compliance
Provenance records support compliance requirements:
Audit Trails
// Get complete audit trail for an entity
const audit = await client.getAuditTrail({
entityId: personId,
format: 'detailed'
});
// Export for compliance review
const report = await compliance.generateAuditReport({
from: '2024-01-01',
to: '2024-12-31',
format: 'pdf'
});
GDPR Support
// Find all data related to a person (data subject)
const subjectData = await compliance.getDataSubjectData(personId);
// Export for data portability
const exportData = await compliance.exportDataSubjectData(personId);
// Right to erasure (with provenance of deletion)
await compliance.deleteDataSubjectData(personId, {
reason: 'GDPR Article 17 request',
requestId: 'gdpr_req_123'
});
Best Practices
1. Always Provide Source Context
// Good - includes context
await ledger.record({
type: 'claim_created',
source: {
type: 'external',
id: 'crm_system',
method: 'sync',
metadata: { syncId: 'sync_456' }
},
// ...
});
// Avoid - missing context
await ledger.record({
type: 'claim_created',
source: { type: 'system', id: 'unknown' },
// ...
});
2. Use Meaningful Agent IDs
// Good - descriptive agent
const agent = await client.createAgent({
name: 'invoice-processor',
description: 'Processes incoming invoices from suppliers'
});
// Avoid - generic agent
const agent = await client.createAgent({
name: 'agent1',
description: 'Does stuff'
});
3. Verify Regularly
// In your health checks
async function healthCheck() {
const verification = await client.verifyProvenance();
if (!verification.value.valid) {
alertOps('Provenance chain integrity failure');
}
}
Next Steps
- Temporal Data - Time-aware data management
- Decisions as Data - Decision tracking
- Provenance Package - API reference
Temporal Data
Time is a first-class citizen in ContextGraph OS. All data is temporally qualified, allowing you to query the state of your knowledge graph at any point in time.
Why Temporal Data?
Traditional databases answer: "What is the current state?"
ContextGraph answers:
- "What is the current state?"
- "What was the state on June 15, 2024?"
- "When did this value change?"
- "What will be the state next month?" (for claims with future validity)
Temporal Concepts
Timestamps
All times in ContextGraph use branded timestamps:
import { createTimestamp, Timestamp } from '@contextgraph/core';
// Create a timestamp
const now = createTimestamp();
const specific = createTimestamp('2024-06-15T10:30:00Z');
// Timestamps are ISO 8601 strings with branding
type Timestamp = string & { readonly __brand: 'Timestamp' };
Validity Periods
Claims have explicit validity periods:
interface TemporalContext {
validFrom: Timestamp; // When this becomes true
validUntil: Timestamp | null; // When this stops being true (null = forever)
observedAt: Timestamp; // When this was recorded
}
Example: Employee Role Changes
// Alice joins as Junior Developer on Jan 1
await client.addClaim({
subjectId: aliceId,
predicate: 'role',
value: 'Junior Developer',
context: {
validFrom: createTimestamp('2024-01-01'),
validUntil: createTimestamp('2024-06-30'),
}
});
// Alice becomes Senior Developer on July 1
await client.addClaim({
subjectId: aliceId,
predicate: 'role',
value: 'Senior Developer',
context: {
validFrom: createTimestamp('2024-07-01'),
validUntil: null, // Current role
}
});
Point-in-Time Queries
Query the knowledge graph as it was at any point:
// What was Alice's role in March 2024?
const marchClaims = await client.getClaims(aliceId, {
asOf: createTimestamp('2024-03-15'),
predicate: 'role'
});
// Returns: 'Junior Developer'
// What is Alice's role now?
const currentClaims = await client.getClaims(aliceId, {
predicate: 'role'
});
// Returns: 'Senior Developer'
Temporal Query Operators
As Of (Point in Time)
// State at a specific moment
const claims = await ckg.query({
subjectId: entityId,
asOf: createTimestamp('2024-06-15T12:00:00Z')
});
Between (Time Range)
// All claims that were valid during Q2 2024
const q2Claims = await ckg.query({
subjectId: entityId,
validDuring: {
from: createTimestamp('2024-04-01'),
to: createTimestamp('2024-06-30')
}
});
Active Only
// Only claims currently valid
const activeClaims = await ckg.query({
subjectId: entityId,
activeOnly: true
});
Temporal Patterns
Superseding Claims
When new information supersedes old:
// Original claim
const original = await client.addClaim({
subjectId: productId,
predicate: 'price',
value: 99.99,
context: {
validFrom: createTimestamp('2024-01-01'),
validUntil: null
}
});
// Update: new price takes effect
await client.addClaim({
subjectId: productId,
predicate: 'price',
value: 89.99,
context: {
validFrom: createTimestamp('2024-06-01'),
validUntil: null
}
});
// Mark old claim as ended
await client.updateClaimValidity(original.value.data.id, {
validUntil: createTimestamp('2024-05-31')
});
Scheduled Changes
Claims that become valid in the future:
// Price change scheduled for next month
await client.addClaim({
subjectId: productId,
predicate: 'price',
value: 79.99,
context: {
validFrom: createTimestamp('2024-07-01'), // Future date
validUntil: null
}
});
// Query for July will return the scheduled price
const julyClaims = await client.getClaims(productId, {
asOf: createTimestamp('2024-07-15'),
predicate: 'price'
});
// Returns: 79.99
Retroactive Corrections
When you discover past data was wrong:
// We discovered the Q1 data was incorrect
// Record the correction with proper timestamps
await client.addClaim({
subjectId: reportId,
predicate: 'revenue',
value: 1500000, // Corrected value
context: {
validFrom: createTimestamp('2024-01-01'),
validUntil: createTimestamp('2024-03-31'),
// observedAt will be now, showing when we learned this
}
});
// The original claim stays in provenance for audit
Time Utilities
Comparing Timestamps
import { isAfter, isBefore, isBetween } from '@contextgraph/core';
if (isAfter(timestamp1, timestamp2)) {
// timestamp1 is later
}
if (isBetween(check, start, end)) {
// check is within the range
}
Duration Calculations
import { duration, addDuration } from '@contextgraph/core';
// Calculate duration
const age = duration(startTime, endTime);
console.log(`${age.days} days, ${age.hours} hours`);
// Add duration to timestamp
const futureTime = addDuration(now, { days: 30 });
Temporal Indexes
For efficient temporal queries, ContextGraph maintains temporal indexes:
// These queries are optimized:
// Point-in-time lookup
await ckg.query({ subjectId, asOf: timestamp });
// Range query
await ckg.query({ validFrom: start, validTo: end });
// Active claims
await ckg.query({ activeOnly: true });
Best Practices
1. Always Specify Validity
// Good - explicit validity
await client.addClaim({
subjectId: id,
predicate: 'status',
value: 'active',
context: {
validFrom: createTimestamp(),
validUntil: null
}
});
// Avoid - relying on defaults
await client.addClaim({
subjectId: id,
predicate: 'status',
value: 'active'
});
2. Use Appropriate Granularity
// For daily values, use date precision
const dailyTimestamp = createTimestamp('2024-06-15');
// For real-time data, use full precision
const preciseTimestamp = createTimestamp(); // Includes milliseconds
3. Handle Time Zones Properly
// Always use UTC for storage
const utcTime = createTimestamp('2024-06-15T10:00:00Z');
// Convert for display in user's timezone
const userTime = formatInTimezone(utcTime, 'America/New_York');
Next Steps
- Decisions as Data - Decision lifecycle
- Context Filtering - Advanced filtering
- Temporal Queries Tutorial - Hands-on examples
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');
3. Link Related Decisions
// Reference related decisions
await client.recordDecision({
type: 'rollback',
title: 'Rollback v2.0.0 deployment',
context: {
relatedDecisionId: originalDeploymentId,
reason: 'Performance degradation detected'
}
});
Next Steps
- Context Filtering - Query decisions with context
- Policy Enforcement - Require approvals via policy
- DTG Package - API reference
- Recommendations - Decision recommendations
Context Filtering
ContextGraph enables sophisticated filtering of data based on temporal, jurisdictional, scope, and confidence contexts. This allows you to get exactly the right data for your use case.
Context Dimensions
1. Temporal Context
Filter by time - when data is/was valid:
// Get claims valid at a specific time
const claims = await ckg.query({
subjectId: entityId,
asOf: createTimestamp('2024-06-15'),
});
// Get claims valid during a range
const rangeClaims = await ckg.query({
subjectId: entityId,
validFrom: createTimestamp('2024-01-01'),
validTo: createTimestamp('2024-12-31'),
});
2. Jurisdictional Context
Filter by legal/regulatory jurisdiction:
import { createJurisdiction } from '@contextgraph/core';
// Create jurisdiction-specific claims
await client.addClaim({
subjectId: userId,
predicate: 'data_retention_period',
value: '7 years',
context: {
jurisdiction: createJurisdiction('EU'),
},
});
await client.addClaim({
subjectId: userId,
predicate: 'data_retention_period',
value: '5 years',
context: {
jurisdiction: createJurisdiction('US'),
},
});
// Query for specific jurisdiction
const euClaims = await ckg.query({
subjectId: userId,
jurisdiction: 'EU',
});
3. Scope Context
Filter by domain or application scope:
import { createScope } from '@contextgraph/core';
// Work-related claims
await client.addClaim({
subjectId: personId,
predicate: 'phone',
value: '+1-555-WORK',
context: {
scope: createScope('work'),
},
});
// Personal claims
await client.addClaim({
subjectId: personId,
predicate: 'phone',
value: '+1-555-HOME',
context: {
scope: createScope('personal'),
},
});
// Query by scope
const workContacts = await ckg.query({
subjectId: personId,
scope: 'work',
});
4. Confidence Context
Filter by confidence level:
import { createConfidence } from '@contextgraph/core';
// High confidence claim (verified)
await client.addClaim({
subjectId: entityId,
predicate: 'verified_email',
value: 'alice@example.com',
context: {
confidence: createConfidence(1.0),
},
});
// Lower confidence claim (inferred)
await client.addClaim({
subjectId: entityId,
predicate: 'likely_industry',
value: 'technology',
context: {
confidence: createConfidence(0.75),
},
});
// Query only high-confidence claims
const verifiedClaims = await ckg.query({
subjectId: entityId,
minConfidence: 0.9,
});
Context Assembly
The Retrieval package provides powerful context assembly:
import { ContextAssembler } from '@contextgraph/retrieval';
const assembler = new ContextAssembler(ckg, provenance, storage);
// Assemble full context for an entity
const context = await assembler.assemble(entityId, {
// Temporal filter
asOf: createTimestamp('2024-06-15'),
// Jurisdictional filter
jurisdiction: 'EU',
// Scope filter
scope: 'work',
// Confidence filter
minConfidence: 0.8,
// Include related entities
depth: 2,
// Include provenance
includeProvenance: true,
});
// Result includes:
// - Matching claims
// - Related entities (up to depth 2)
// - Provenance records
// - Applied filters
Filter Combinations
Combine multiple filters for precise queries:
// Complex query: EU jurisdiction, work scope,
// high confidence, as of last month
const context = await assembler.assemble(entityId, {
asOf: createTimestamp('2024-05-31'),
jurisdiction: 'EU',
scope: 'work',
minConfidence: 0.85,
});
Context Inheritance
Claims can inherit context from their entities:
// Entity with default context
const entity = await client.createEntity({
type: 'contract',
name: 'Service Agreement',
properties: {
defaultJurisdiction: 'US-DE',
defaultScope: 'legal',
},
});
// Claims inherit entity context unless overridden
await client.addClaim({
subjectId: entity.data.id,
predicate: 'effective_date',
value: '2024-01-01',
// Inherits jurisdiction: US-DE, scope: legal
});
// Override specific context
await client.addClaim({
subjectId: entity.data.id,
predicate: 'eu_compliance_date',
value: '2024-06-01',
context: {
jurisdiction: createJurisdiction('EU'), // Override
// Inherits scope: legal
},
});
Query Operators
Equality
{ jurisdiction: 'EU' } // Exact match
Multiple Values
{ jurisdiction: ['EU', 'UK'] } // Either EU or UK
Range (for confidence)
{ minConfidence: 0.8 } // At least 0.8
{ maxConfidence: 0.95 } // At most 0.95
{ minConfidence: 0.8, maxConfidence: 0.95 } // Between
Negation
{ excludeScope: ['test', 'development'] } // Not test or development
Context-Aware Policies
Policies can reference context:
await client.createPolicy({
name: 'GDPR Data Access',
effect: 'deny',
subjects: ['*'],
actions: ['export'],
resources: ['personal_data/*'],
conditions: [
{
field: 'context.jurisdiction',
operator: 'not_equals',
value: 'EU',
},
],
});
SDK Integration
The SDK provides convenient methods:
// Get claims with context
const claims = await client.getClaims(entityId, {
asOf: timestamp,
jurisdiction: 'EU',
scope: 'work',
minConfidence: 0.8,
});
// Assemble context
const context = await client.assembleContext(entityId, {
jurisdiction: 'US',
scope: 'compliance',
includeRelated: true,
depth: 2,
});
Performance Considerations
Indexed Fields
These context fields are indexed for fast queries:
validFrom/validUntiljurisdictionscopeconfidence
Query Optimization
// Efficient: Uses indexes
const claims = await ckg.query({
subjectId: entityId,
jurisdiction: 'EU',
minConfidence: 0.9,
});
// Less efficient: Full scan needed
const claims = await ckg.query({
predicate: 'custom_field',
// No subjectId, no indexed fields
});
Best Practices
1. Be Explicit About Context
// Good - clear context
await client.addClaim({
subjectId: id,
predicate: 'price',
value: 99.99,
context: {
jurisdiction: createJurisdiction('US'),
scope: createScope('retail'),
confidence: createConfidence(1.0),
validFrom: createTimestamp(),
},
});
2. Use Appropriate Granularity
// Jurisdiction examples
'US' // Country level
'US-CA' // State level
'US-CA-LA' // City level
// Scope examples
'work'
'work:hr'
'work:hr:benefits'
3. Document Context Semantics
// In your codebase, document what contexts mean
const SCOPES = {
'work': 'Professional/employment context',
'personal': 'Personal/private context',
'public': 'Publicly accessible information',
'internal': 'Internal company use only',
};
Next Steps
- Policy Enforcement - Context-aware policies
- Retrieval Package - API reference
- Temporal Queries Tutorial
Policy Enforcement
ContextGraph OS includes a powerful policy engine for controlling access and enforcing business rules. Policies follow a deny-takes-precedence model for security.
Policy Structure
interface Policy {
id: PolicyId;
name: string;
version: string;
description?: string;
effect: 'allow' | 'deny';
subjects: string[]; // Who this applies to
actions: string[]; // What actions
resources: string[]; // Which resources
conditions?: Condition[];
priority: number; // Higher = evaluated first
validFrom?: Timestamp;
validUntil?: Timestamp;
}
Creating Policies
Basic Allow Policy
await client.createPolicy({
name: 'Read Access for Analysts',
version: '1.0.0',
effect: 'allow',
subjects: ['role:analyst'],
actions: ['read'],
resources: ['reports/*', 'dashboards/*'],
priority: 50,
});
Basic Deny Policy
await client.createPolicy({
name: 'Block PII Access',
version: '1.0.0',
effect: 'deny',
subjects: ['*'],
actions: ['read', 'export'],
resources: ['pii/*', 'sensitive/*'],
priority: 100,
});
Conditional Policy
await client.createPolicy({
name: 'Time-Based Access',
version: '1.0.0',
effect: 'allow',
subjects: ['role:contractor'],
actions: ['*'],
resources: ['*'],
conditions: [
{
field: 'time.hour',
operator: 'between',
value: [9, 17],
},
{
field: 'time.dayOfWeek',
operator: 'in',
value: ['Mon', 'Tue', 'Wed', 'Thu', 'Fri'],
},
],
priority: 40,
});
Deny-Takes-Precedence
When multiple policies match, deny always wins:
// This allows read access to reports
await client.createPolicy({
name: 'Allow Reports',
effect: 'allow',
subjects: ['role:analyst'],
actions: ['read'],
resources: ['reports/*'],
priority: 50,
});
// This denies access to confidential reports
await client.createPolicy({
name: 'Block Confidential',
effect: 'deny',
subjects: ['*'],
actions: ['read'],
resources: ['reports/confidential/*'],
priority: 100,
});
// Result: Analysts can read reports/* except reports/confidential/*
Policy Evaluation
Automatic Enforcement
Policies are evaluated automatically during execution:
// Policies are checked before execution
const result = await client.execute({
agentId: agentId,
action: 'read',
resourceType: 'report',
resourceId: 'confidential/q4-financials',
});
if (!result.ok && result.error.code === 'POLICY_DENIED') {
console.log('Access denied by policy');
}
Manual Evaluation
import { PolicyEvaluator } from '@contextgraph/policy';
const evaluator = new PolicyEvaluator(policyLedger, storage);
const decision = await evaluator.evaluate({
subject: 'agent:data-processor',
action: 'export',
resource: 'pii/customer-emails',
context: {
time: new Date(),
jurisdiction: 'EU',
},
});
console.log(`Effect: ${decision.effect}`); // 'allow' or 'deny'
console.log(`Matched policies: ${decision.matchedPolicies.length}`);
Condition Operators
Comparison
{ field: 'risk.level', operator: 'equals', value: 'high' }
{ field: 'data.count', operator: 'greater_than', value: 100 }
{ field: 'confidence', operator: 'less_than_or_equals', value: 0.5 }
Collection
{ field: 'role', operator: 'in', value: ['admin', 'superuser'] }
{ field: 'action', operator: 'not_in', value: ['delete', 'purge'] }
String Matching
{ field: 'resource', operator: 'starts_with', value: '/api/v1/' }
{ field: 'resource', operator: 'ends_with', value: '.json' }
{ field: 'resource', operator: 'contains', value: 'admin' }
{ field: 'resource', operator: 'matches', value: '^/api/v[0-9]+/' }
Temporal
{ field: 'time.hour', operator: 'between', value: [9, 17] }
{ field: 'date', operator: 'after', value: '2024-01-01' }
Existence
{ field: 'approval.id', operator: 'exists' }
{ field: 'override.reason', operator: 'not_exists' }
Policy Templates
Use built-in templates for common patterns:
import { PolicyTemplateManager } from '@contextgraph/policy';
const templates = new PolicyTemplateManager(storage);
// Read-only template
await templates.instantiate('read-only', {
subjects: ['role:viewer'],
resources: ['dashboards/*'],
});
// PII protection template
await templates.instantiate('pii-protection', {
piiResources: ['customers/*', 'users/*'],
allowedRoles: ['role:privacy-officer'],
});
// Rate limiting template
await templates.instantiate('rate-limit', {
subjects: ['*'],
maxRequests: 100,
windowSeconds: 60,
});
Available Templates
| Template | Description |
|---|---|
read-only | Allow only read operations |
pii-protection | Block access to PII unless authorized |
approval-required | Require approval for actions |
rate-limit | Limit request rate |
time-based | Restrict by time of day |
jurisdiction | Restrict by jurisdiction |
Policy Simulation
Test policies before deployment:
import { PolicySimulator } from '@contextgraph/policy';
const simulator = new PolicySimulator(policyLedger, storage);
// Simulate a request
const result = await simulator.simulate({
subject: 'agent:new-processor',
action: 'delete',
resource: 'data/customer-records',
context: { time: new Date() },
});
console.log(`Would be: ${result.effect}`);
console.log(`Matching policies:`);
for (const policy of result.matchedPolicies) {
console.log(` - ${policy.name} (${policy.effect})`);
}
// Bulk simulation
const scenarios = [
{ subject: 'role:admin', action: 'delete', resource: 'data/*' },
{ subject: 'role:analyst', action: 'read', resource: 'reports/*' },
{ subject: 'role:guest', action: 'write', resource: 'comments/*' },
];
const results = await simulator.simulateMany(scenarios);
Exception Handling
Request policy exceptions:
import { ExceptionManager } from '@contextgraph/exceptions';
const exceptions = new ExceptionManager(dtg, policyLedger, storage);
// Request an exception
const request = await exceptions.request({
policyId: policyId,
reason: 'Need temporary access for audit',
requestedBy: agentId,
duration: { hours: 24 },
scope: {
action: 'read',
resources: ['sensitive/audit-data'],
},
});
// Approve the exception
await exceptions.approve(request.id, approverId, 'Approved for audit period');
// The exception is now active and will be considered during evaluation
RBAC Integration
Combine with Role-Based Access Control:
import { RBACManager } from '@contextgraph/rbac';
const rbac = new RBACManager(storage);
// Define roles
await rbac.createRole({
name: 'data-analyst',
permissions: ['read:reports', 'read:dashboards', 'export:csv'],
inherits: ['viewer'],
});
// Assign roles
await rbac.assignRole(agentId, 'data-analyst');
// Policies can reference roles
await client.createPolicy({
name: 'Analyst Access',
effect: 'allow',
subjects: ['role:data-analyst'],
actions: ['read', 'export'],
resources: ['analytics/*'],
priority: 50,
});
Best Practices
1. Use Specific Subjects
// Good - specific subjects
{ subjects: ['role:admin', 'role:security-officer'] }
// Avoid - too broad
{ subjects: ['*'] } // Only for deny policies
2. Set Appropriate Priorities
// Security policies: High priority (100+)
{ name: 'Block Dangerous Actions', priority: 150 }
// Business rules: Medium priority (50-99)
{ name: 'Department Access', priority: 60 }
// Default policies: Low priority (1-49)
{ name: 'Default Allow', priority: 10 }
3. Version Your Policies
{
name: 'Data Access Policy',
version: '2.1.0', // Semantic versioning
description: 'Updated to include new data categories',
}
4. Document Conditions
{
conditions: [
{
field: 'data.classification',
operator: 'not_in',
value: ['top-secret', 'classified'],
// _comment: 'Only allow unclassified data'
},
],
}
Next Steps
- RBAC Package - Role management
- Exceptions Package - Exception handling
- Compliance Package - Compliance features
- Policy Tutorial - Hands-on guide
@contextgraph/core
The foundational package providing branded types, Result pattern, time utilities, and error types used throughout ContextGraph OS.
Installation
pnpm add @contextgraph/core
Branded Types
All identifiers use branded types for compile-time safety:
// Branded type definitions
type EntityId = string & { readonly __brand: 'EntityId' };
type ClaimId = string & { readonly __brand: 'ClaimId' };
type AgentId = string & { readonly __brand: 'AgentId' };
type DecisionId = string & { readonly __brand: 'DecisionId' };
type PolicyId = string & { readonly __brand: 'PolicyId' };
type ProvenanceId = string & { readonly __brand: 'ProvenanceId' };
type Timestamp = string & { readonly __brand: 'Timestamp' };
Creating IDs
import {
createEntityId,
createClaimId,
createAgentId,
createTimestamp,
} from '@contextgraph/core';
const entityId = createEntityId(); // ent_abc123...
const claimId = createClaimId(); // clm_def456...
const agentId = createAgentId(); // agt_ghi789...
const timestamp = createTimestamp(); // 2024-01-15T10:30:00.000Z
Result Pattern
All operations return Result<T, Error> instead of throwing exceptions:
import { ok, err, type Result } from '@contextgraph/core';
// Creating results
function divide(a: number, b: number): Result<number> {
if (b === 0) {
return err(new Error('Division by zero'));
}
return ok(a / b);
}
// Using results
const result = divide(10, 2);
if (result.ok) {
console.log(result.value); // 5
} else {
console.error(result.error.message);
}
Result Utilities
import { isOk, isErr, unwrapOr, map, flatMap } from '@contextgraph/core';
// Type guards
if (isOk(result)) {
console.log(result.value);
}
// Default value
const value = unwrapOr(result, 0);
// Transform success value
const mapped = map(result, x => x * 2);
// Chain operations
const chained = flatMap(result1, val =>
otherOperation(val)
);
Time Utilities
Creating Timestamps
import { createTimestamp } from '@contextgraph/core';
// Current time
const now = createTimestamp();
// Specific time
const specific = createTimestamp('2024-06-15T10:30:00Z');
// From Date
const fromDate = createTimestamp(new Date());
Time Comparisons
import { isAfter, isBefore, isBetween } from '@contextgraph/core';
if (isAfter(timestamp1, timestamp2)) {
// timestamp1 is later than timestamp2
}
if (isBetween(check, start, end)) {
// check is within [start, end]
}
Duration Utilities
import { duration, addDuration, subtractDuration } from '@contextgraph/core';
// Calculate duration
const elapsed = duration(start, end);
// { days: 5, hours: 2, minutes: 30, seconds: 0, ms: 0 }
// Add duration
const future = addDuration(now, { days: 7 });
// Subtract duration
const past = subtractDuration(now, { hours: 24 });
Context Types
Scope
import { createScope, type Scope } from '@contextgraph/core';
const workScope = createScope('work');
const personalScope = createScope('personal');
const scope = createScope('work:hr:benefits'); // Hierarchical
Jurisdiction
import { createJurisdiction, type Jurisdiction } from '@contextgraph/core';
const usJurisdiction = createJurisdiction('US');
const euJurisdiction = createJurisdiction('EU');
const stateJurisdiction = createJurisdiction('US-CA');
Confidence
import { createConfidence, type Confidence } from '@contextgraph/core';
const highConfidence = createConfidence(0.95); // 95%
const mediumConfidence = createConfidence(0.7);
const lowConfidence = createConfidence(0.5);
// Confidence must be between 0 and 1
createConfidence(1.5); // Throws error
Error Types
ContextGraphError
import { ContextGraphError, ErrorCode } from '@contextgraph/core';
throw new ContextGraphError(
ErrorCode.NOT_FOUND,
'Entity not found',
{ entityId }
);
Error Codes
| Code | Description |
|---|---|
NOT_FOUND | Resource not found |
ALREADY_EXISTS | Resource already exists |
VALIDATION_ERROR | Validation failed |
PERMISSION_DENIED | Insufficient permissions |
POLICY_DENIED | Policy blocked the operation |
STORAGE_ERROR | Storage operation failed |
INTEGRITY_ERROR | Data integrity violation |
TIMEOUT | Operation timed out |
Type Guards
import {
isEntityId,
isClaimId,
isAgentId,
isTimestamp,
} from '@contextgraph/core';
if (isEntityId(value)) {
// TypeScript knows value is EntityId
}
Constants
import {
MAX_STRING_LENGTH,
MAX_PROPERTIES,
DEFAULT_PAGE_SIZE,
MAX_PAGE_SIZE,
} from '@contextgraph/core';
Type Exports
export type {
EntityId,
ClaimId,
AgentId,
DecisionId,
PolicyId,
ProvenanceId,
Timestamp,
Scope,
Jurisdiction,
Confidence,
Result,
};
@contextgraph/storage
Storage abstraction layer with SQLite and in-memory implementations.
Installation
pnpm add @contextgraph/storage
Storage Interface
All storage backends implement the StorageInterface:
interface StorageInterface {
// Basic operations
get<T>(key: string): Promise<Result<T | null>>;
set<T>(key: string, value: T): Promise<Result<void>>;
delete(key: string): Promise<Result<void>>;
has(key: string): Promise<Result<boolean>>;
// Batch operations
getMany<T>(keys: string[]): Promise<Result<Map<string, T>>>;
setMany<T>(entries: Map<string, T>): Promise<Result<void>>;
deleteMany(keys: string[]): Promise<Result<void>>;
// List and query
list<T>(prefix: string, options?: ListOptions): Promise<Result<T[]>>;
query<T>(query: Query): Promise<Result<T[]>>;
count(prefix: string): Promise<Result<number>>;
// Lifecycle
initialize(): Promise<Result<void>>;
close(): Promise<Result<void>>;
clear(): Promise<Result<void>>;
}
In-Memory Storage
Fast, ephemeral storage ideal for testing and development:
import { createInMemoryStorage } from '@contextgraph/storage';
const storage = createInMemoryStorage();
await storage.initialize();
// Use storage
await storage.set('key', { name: 'value' });
const result = await storage.get('key');
Features
- Zero dependencies
- Fast operations
- Perfect for testing
- Data lost on restart
SQLite Storage
Persistent storage for production use:
import { createSQLiteStorage } from '@contextgraph/storage';
const storage = createSQLiteStorage({
path: './contextgraph.db',
// Optional configuration
wal: true,
busyTimeout: 5000,
});
await storage.initialize();
Features
- ACID compliance
- Persistent storage
- WAL mode support
- Full-text search (planned)
Configuration
interface SQLiteStorageConfig {
path: string;
wal?: boolean; // Enable WAL mode (default: true)
busyTimeout?: number; // Busy timeout in ms (default: 5000)
pageSize?: number; // Page size (default: 4096)
}
Queries
List with Prefix
// List all entities
const entities = await storage.list('entity:');
// List with options
const recent = await storage.list('entity:', {
limit: 10,
offset: 0,
sortBy: 'createdAt',
sortOrder: 'desc',
});
Query with Filters
const results = await storage.query({
prefix: 'entity:',
filters: [
{ field: 'type', operator: 'eq', value: 'person' },
{ field: 'createdAt', operator: 'gt', value: timestamp },
],
limit: 50,
});
Query Operators
| Operator | Description |
|---|---|
eq | Equals |
ne | Not equals |
gt | Greater than |
gte | Greater than or equal |
lt | Less than |
lte | Less than or equal |
in | In array |
nin | Not in array |
contains | Contains substring |
startsWith | Starts with |
endsWith | Ends with |
Batch Operations
// Get multiple keys
const entries = await storage.getMany(['key1', 'key2', 'key3']);
// Set multiple entries
await storage.setMany(new Map([
['key1', value1],
['key2', value2],
]));
// Delete multiple keys
await storage.deleteMany(['key1', 'key2']);
Indexing
The storage layer supports indexes for optimized queries:
// Create index (SQLite)
await storage.createIndex('entity:type', {
fields: ['type', 'createdAt'],
unique: false,
});
Query Optimization
For frequent query patterns, use the query optimizer:
import { QueryOptimizer } from '@contextgraph/storage';
const optimizer = new QueryOptimizer(storage);
// Optimize query
const optimizedQuery = optimizer.optimize({
prefix: 'entity:',
filters: [...],
limit: 100,
});
Custom Storage Backends
Implement StorageInterface for custom backends:
import type { StorageInterface, Result } from '@contextgraph/storage';
class RedisStorage implements StorageInterface {
constructor(private client: RedisClient) {}
async get<T>(key: string): Promise<Result<T | null>> {
const value = await this.client.get(key);
return ok(value ? JSON.parse(value) : null);
}
async set<T>(key: string, value: T): Promise<Result<void>> {
await this.client.set(key, JSON.stringify(value));
return ok(undefined);
}
// Implement remaining methods...
}
Storage Factory
Use the factory for flexible configuration:
import { createStorage } from '@contextgraph/storage';
// Memory
const memStorage = createStorage({ type: 'memory' });
// SQLite
const sqliteStorage = createStorage({
type: 'sqlite',
path: './data.db',
});
@contextgraph/ontology
Schema definitions, versioning, and validation for ContextGraph data structures.
Installation
pnpm add @contextgraph/ontology
Overview
The ontology package provides:
- Schema definitions for entities and claims
- Schema versioning and migration
- Validation of data against schemas
- Code generation from schemas
Defining Schemas
import { defineSchema, SchemaBuilder } from '@contextgraph/ontology';
const personSchema = defineSchema({
name: 'person',
version: '1.0.0',
fields: {
firstName: { type: 'string', required: true },
lastName: { type: 'string', required: true },
email: { type: 'string', format: 'email' },
age: { type: 'number', min: 0, max: 150 },
department: { type: 'string', enum: ['Engineering', 'Product', 'Sales'] },
},
});
Validation
import { validate } from '@contextgraph/ontology';
const result = validate(personSchema, {
firstName: 'Alice',
lastName: 'Smith',
email: 'alice@example.com',
age: 30,
});
if (!result.valid) {
console.log('Validation errors:', result.errors);
}
Schema Versioning
import { SchemaRegistry } from '@contextgraph/ontology';
const registry = new SchemaRegistry(storage);
// Register schema
await registry.register(personSchema);
// Get latest version
const latest = await registry.getLatest('person');
// Get specific version
const v1 = await registry.get('person', '1.0.0');
// List all versions
const versions = await registry.listVersions('person');
Schema Migration
import { createMigration } from '@contextgraph/ontology';
const migration = createMigration({
from: '1.0.0',
to: '2.0.0',
schema: 'person',
up: (data) => ({
...data,
fullName: `${data.firstName} ${data.lastName}`,
}),
down: (data) => {
const [firstName, lastName] = data.fullName.split(' ');
return { ...data, firstName, lastName };
},
});
await registry.registerMigration(migration);
Field Types
| Type | Description |
|---|---|
string | Text values |
number | Numeric values |
boolean | True/false |
array | Array of items |
object | Nested object |
date | ISO 8601 date |
timestamp | ISO 8601 timestamp |
entityRef | Reference to entity |
Field Options
{
type: 'string',
required: true,
default: 'default value',
min: 1, // min length for string
max: 100, // max length for string
pattern: '^[A-Z]+$', // regex pattern
format: 'email', // built-in formats
enum: ['a', 'b'], // allowed values
}
Code Generation
Generate TypeScript types from schemas:
import { generateTypes } from '@contextgraph/ontology';
const code = generateTypes(personSchema);
// Outputs TypeScript interface
@contextgraph/ckg
Contextual Knowledge Graph - entities and claims with full temporal context.
Installation
pnpm add @contextgraph/ckg
Overview
The CKG (Contextual Knowledge Graph) stores:
- Entities: Objects with types and properties
- Claims: Statements about entities with temporal validity
Creating a CKG
import { CKG } from '@contextgraph/ckg';
const ckg = new CKG(storage, provenanceLedger);
await ckg.initialize();
Entity Operations
Create Entity
const result = await ckg.createEntity({
type: 'person',
name: 'Alice',
properties: {
email: 'alice@example.com',
department: 'Engineering',
},
});
const entity = result.value;
// { id: 'ent_abc123', type: 'person', name: 'Alice', ... }
Get Entity
const entity = await ckg.getEntity(entityId);
Update Entity
await ckg.updateEntity(entityId, {
properties: {
department: 'Product',
},
});
Find Entities
const entities = await ckg.findEntitiesByType('person', {
limit: 100,
offset: 0,
});
Claim Operations
Add Claim
const claim = await ckg.addClaim({
subjectId: entityId,
predicate: 'works_on',
objectValue: projectId,
context: {
scope: createScope('work'),
confidence: createConfidence(1.0),
validFrom: createTimestamp(),
validUntil: null,
},
});
Get Claims for Subject
const claims = await ckg.getClaimsForSubject(entityId);
Get Specific Claim
const claim = await ckg.getClaim(claimId);
Revoke Claim
await ckg.revokeClaim(claimId, 'Information superseded');
Temporal Queries
Point-in-Time
// Get claims valid at specific time
const claims = await ckg.getClaimsForSubject(entityId, {
asOf: createTimestamp('2024-06-15'),
});
Time Range
const claims = await ckg.query({
subjectId: entityId,
validFrom: startTimestamp,
validTo: endTimestamp,
});
Active Only
const activeClaims = await ckg.getClaimsForSubject(entityId, {
activeOnly: true,
});
Context Filtering
// Filter by jurisdiction
const euClaims = await ckg.query({
subjectId: entityId,
jurisdiction: 'EU',
});
// Filter by scope
const workClaims = await ckg.query({
subjectId: entityId,
scope: 'work',
});
// Filter by confidence
const highConfidenceClaims = await ckg.query({
subjectId: entityId,
minConfidence: 0.9,
});
Claim Types
Claims can have different value types:
// String value
{ predicate: 'name', objectValue: 'Alice' }
// Number value
{ predicate: 'age', objectValue: 30 }
// Boolean value
{ predicate: 'isActive', objectValue: true }
// Reference to another entity
{ predicate: 'manages', objectId: otherEntityId }
Entity Interface
interface Entity {
id: EntityId;
type: string;
name: string;
properties: Record<string, unknown>;
createdAt: Timestamp;
updatedAt: Timestamp;
}
Claim Interface
interface Claim {
id: ClaimId;
subjectId: EntityId;
predicate: string;
objectValue?: unknown;
objectId?: EntityId;
context: ClaimContext;
status: 'active' | 'revoked';
createdAt: Timestamp;
revokedAt?: Timestamp;
revokedReason?: string;
}
interface ClaimContext {
scope?: Scope;
jurisdiction?: Jurisdiction;
confidence?: Confidence;
validFrom?: Timestamp;
validUntil?: Timestamp | null;
}
Statistics
const stats = await ckg.getStats();
console.log(`Entities: ${stats.entityCount}`);
console.log(`Claims: ${stats.claimCount}`);
console.log(`Active claims: ${stats.activeClaimCount}`);
@contextgraph/provenance
Immutable provenance ledger with hash chain verification.
Installation
pnpm add @contextgraph/provenance
Overview
The provenance ledger tracks the origin of all data in ContextGraph:
- Every mutation creates a provenance entry
- Entries are hash-chained for integrity
- The chain can be verified at any time
Creating the Ledger
import { ProvenanceLedger } from '@contextgraph/provenance';
const ledger = new ProvenanceLedger(storage);
await ledger.initialize();
Recording Provenance
Basic Record
const entry = await ledger.record({
type: 'claim_created',
subjectId: entityId,
data: { predicate: 'status', value: 'active' },
source: {
type: 'agent',
id: agentId,
method: 'api_call',
},
});
Entry Types
| Type | Description |
|---|---|
claim_created | New claim added |
claim_revoked | Claim revoked |
entity_created | New entity created |
entity_updated | Entity properties updated |
decision_recorded | Decision recorded |
decision_approved | Decision approved |
decision_rejected | Decision rejected |
execution_logged | Action executed |
policy_created | Policy created |
Source Types
// Agent source
{ type: 'agent', id: 'agt_123', method: 'process_data' }
// User source
{ type: 'user', id: 'user_456', method: 'manual_entry' }
// System source
{ type: 'system', id: 'scheduler', method: 'batch_job' }
// External source
{ type: 'external', id: 'weather_api', method: 'sync' }
// Inference source
{ type: 'inference', id: 'reasoner', method: 'transitive_closure' }
Querying Provenance
Get by ID
const entry = await ledger.get(provenanceId);
Query by Subject
const entries = await ledger.query({
subjectId: entityId,
limit: 100,
});
Query by Type
const claimEntries = await ledger.query({
type: 'claim_created',
from: startTime,
to: endTime,
});
Query by Agent
const agentActions = await ledger.query({
agentId: agentId,
limit: 50,
});
Chain Verification
Verify Entire Chain
const result = await ledger.verify();
console.log(`Valid: ${result.valid}`);
console.log(`Entries verified: ${result.entriesVerified}`);
console.log(`Broken links: ${result.brokenLinks}`);
console.log(`Invalid hashes: ${result.invalidHashes}`);
Verify Specific Range
const result = await ledger.verify({
from: startId,
to: endId,
});
Entry Structure
interface ProvenanceEntry {
id: ProvenanceId;
hash: string; // SHA-256 hash
previousHash: string | null; // Link to previous
type: ProvenanceType;
subjectId: string;
data: unknown;
source: ProvenanceSource;
timestamp: Timestamp;
agentId?: AgentId;
}
Hash Calculation
The hash includes:
const hashData = {
previousHash,
type,
subjectId,
data,
source,
timestamp,
};
const hash = sha256(JSON.stringify(hashData));
Statistics
const stats = await ledger.getStats();
console.log(`Total entries: ${stats.totalEntries}`);
console.log(`First entry: ${stats.firstEntryId}`);
console.log(`Last entry: ${stats.lastEntryId}`);
Integrity Monitoring
// Periodic verification
setInterval(async () => {
const result = await ledger.verify();
if (!result.valid) {
alertOps('Provenance chain integrity failure');
}
}, 60 * 60 * 1000); // Hourly
@contextgraph/retrieval
Context assembly with temporal and scope filtering.
Installation
pnpm add @contextgraph/retrieval
Overview
The retrieval package assembles context for entities by:
- Gathering relevant claims
- Filtering by temporal context
- Filtering by jurisdiction and scope
- Traversing relationships
Context Assembly
import { ContextAssembler } from '@contextgraph/retrieval';
const assembler = new ContextAssembler(ckg, provenance, storage);
const context = await assembler.assemble(entityId, {
asOf: createTimestamp('2024-06-15'),
jurisdiction: 'EU',
scope: 'work',
minConfidence: 0.8,
depth: 2,
includeProvenance: true,
});
Assembly Options
interface AssemblyOptions {
// Temporal filter
asOf?: Timestamp;
// Jurisdictional filter
jurisdiction?: string;
// Scope filter
scope?: string;
// Confidence threshold
minConfidence?: number;
// Relationship traversal depth
depth?: number;
// Include provenance records
includeProvenance?: boolean;
// Include specific predicates only
predicates?: string[];
// Exclude specific predicates
excludePredicates?: string[];
// Maximum claims per entity
maxClaims?: number;
}
Context Result
interface AssembledContext {
// Primary entity
entity: Entity;
// Claims about the entity
claims: Claim[];
// Related entities (up to depth)
relatedEntities: Map<EntityId, Entity>;
// Claims about related entities
relatedClaims: Map<EntityId, Claim[]>;
// Provenance records (if requested)
provenance?: ProvenanceEntry[];
// Applied filters
filters: {
asOf?: Timestamp;
jurisdiction?: string;
scope?: string;
minConfidence?: number;
};
// Assembly statistics
stats: {
claimCount: number;
relatedEntityCount: number;
maxDepthReached: number;
};
}
Filtering Examples
Temporal
// Get context as of last quarter
const q3Context = await assembler.assemble(entityId, {
asOf: createTimestamp('2024-09-30'),
});
Jurisdictional
// Get EU-compliant context
const euContext = await assembler.assemble(entityId, {
jurisdiction: 'EU',
});
Confidence
// Get only high-confidence claims
const highConfidence = await assembler.assemble(entityId, {
minConfidence: 0.9,
});
Relationship Traversal
// Traverse 3 levels of relationships
const deepContext = await assembler.assemble(entityId, {
depth: 3,
});
// Access related entities
for (const [id, entity] of context.relatedEntities) {
console.log(`Related: ${entity.name}`);
}
@contextgraph/dtg
Decision Trace Graph - tracks decisions with full lifecycle and audit trails.
Installation
pnpm add @contextgraph/dtg
Overview
The DTG tracks every significant decision through its lifecycle:
PROPOSED → APPROVED → EXECUTED → COMPLETED
↓ ↓
REJECTED FAILED
Creating the DTG
import { DecisionTraceGraph } from '@contextgraph/dtg';
const dtg = new DecisionTraceGraph(storage);
await dtg.initialize();
Recording Decisions
const decision = await dtg.record({
type: 'deployment',
title: 'Deploy v2.0.0 to production',
description: 'Release new authentication system',
proposedBy: agentId,
riskLevel: 'high',
context: {
environment: 'production',
services: ['api', 'web'],
},
});
Decision Lifecycle
Approve
await dtg.approve(decisionId, approverId, 'Approved after security review');
Reject
await dtg.reject(decisionId, reviewerId, 'Needs more testing');
Execute
await dtg.execute(decisionId, {
executedBy: agentId,
executedAt: createTimestamp(),
});
Complete
await dtg.complete(decisionId, {
success: true,
outcome: {
deploymentId: 'dep_123',
duration: 45000,
},
});
Fail
await dtg.fail(decisionId, {
error: 'Database migration failed',
failedAt: createTimestamp(),
});
Querying Decisions
Get by ID
const decision = await dtg.get(decisionId);
Query by Status
const pending = await dtg.queryDecisions({
status: 'proposed',
limit: 50,
});
const approved = await dtg.queryDecisions({
status: 'approved',
from: startTime,
to: endTime,
});
Query by Agent
const agentDecisions = await dtg.queryDecisions({
proposedBy: agentId,
});
Query by Risk Level
const highRisk = await dtg.queryDecisions({
riskLevel: ['high', 'critical'],
status: 'proposed',
});
Decision Interface
interface Decision {
id: DecisionId;
type: string;
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?: Record<string, unknown>;
outcome?: Record<string, unknown>;
}
type DecisionStatus =
| 'proposed'
| 'approved'
| 'rejected'
| 'executed'
| 'completed'
| 'failed';
type RiskLevel = 'low' | 'medium' | 'high' | 'critical';
Decision History
Get the full history of a decision:
const history = await dtg.getHistory(decisionId);
for (const event of history) {
console.log(`${event.timestamp}: ${event.previousStatus} → ${event.newStatus}`);
console.log(` By: ${event.actor}`);
console.log(` Reason: ${event.reason}`);
}
Statistics
const stats = await dtg.getStats();
console.log(`Total: ${stats.total}`);
console.log(`Proposed: ${stats.byStatus.proposed}`);
console.log(`Approved: ${stats.byStatus.approved}`);
console.log(`Completed: ${stats.byStatus.completed}`);
@contextgraph/policy
Policy ledger with rule evaluation and deny-takes-precedence semantics.
Installation
pnpm add @contextgraph/policy
Overview
The policy package provides:
- Policy creation and management
- Deny-takes-precedence evaluation
- Condition-based rules
- Policy templates and simulation
Creating Policies
import { PolicyLedger } from '@contextgraph/policy';
const ledger = new PolicyLedger(storage);
await ledger.initialize();
await ledger.create({
name: 'Read Access for Analysts',
version: '1.0.0',
effect: 'allow',
subjects: ['role:analyst'],
actions: ['read'],
resources: ['reports/*'],
priority: 50,
});
Policy Evaluation
import { PolicyEvaluator } from '@contextgraph/policy';
const evaluator = new PolicyEvaluator(ledger, storage);
const decision = await evaluator.evaluate({
subject: 'agent:data-processor',
action: 'read',
resource: 'reports/q4-2024',
context: {
time: new Date(),
jurisdiction: 'US',
},
});
console.log(`Effect: ${decision.effect}`);
Policy Structure
interface Policy {
id: PolicyId;
name: string;
version: string;
description?: string;
effect: 'allow' | 'deny';
subjects: string[]; // Who
actions: string[]; // What
resources: string[]; // Where
conditions?: Condition[];
priority: number;
validFrom?: Timestamp;
validUntil?: Timestamp;
}
Conditions
interface Condition {
field: string;
operator: ConditionOperator;
value: unknown;
}
type ConditionOperator =
| 'equals'
| 'not_equals'
| 'greater_than'
| 'less_than'
| 'in'
| 'not_in'
| 'contains'
| 'starts_with'
| 'ends_with'
| 'matches'
| 'exists'
| 'between';
Policy Templates
import { PolicyTemplateManager } from '@contextgraph/policy';
const templates = new PolicyTemplateManager(storage);
// Use built-in templates
await templates.instantiate('read-only', {
subjects: ['role:viewer'],
resources: ['dashboards/*'],
});
await templates.instantiate('pii-protection', {
piiResources: ['customers/*'],
allowedRoles: ['role:privacy-officer'],
});
Policy Simulation
import { PolicySimulator } from '@contextgraph/policy';
const simulator = new PolicySimulator(ledger, storage);
const result = await simulator.simulate({
subject: 'agent:new-processor',
action: 'delete',
resource: 'data/records',
});
console.log(`Would be: ${result.effect}`);
console.log(`Matched policies: ${result.matchedPolicies.length}`);
Priority System
- Higher priority policies are evaluated first
- Deny policies take precedence over allow at same priority
- Recommended ranges:
- Security policies: 100+
- Business rules: 50-99
- Default policies: 1-49
@contextgraph/exceptions
Exception requests and approvals for policy overrides.
Installation
pnpm add @contextgraph/exceptions
Overview
Handle cases where policies need to be temporarily bypassed:
- Request exceptions to denied policies
- Approval workflows
- Scoped, time-limited exceptions
- Full audit trail
Requesting Exceptions
import { ExceptionManager } from '@contextgraph/exceptions';
const exceptions = new ExceptionManager(dtg, policyLedger, storage);
const request = await exceptions.request({
policyId: policyId,
reason: 'Need temporary access for audit',
requestedBy: agentId,
duration: { hours: 24 },
scope: {
action: 'read',
resources: ['sensitive/audit-data/*'],
},
});
Approving Exceptions
await exceptions.approve(
request.id,
approverId,
'Approved for audit period'
);
Exception Lifecycle
PENDING → APPROVED → ACTIVE → EXPIRED
↓
REJECTED
Exception Structure
interface ExceptionRequest {
id: string;
policyId: PolicyId;
reason: string;
requestedBy: AgentId;
requestedAt: Timestamp;
duration: Duration;
scope: ExceptionScope;
status: ExceptionStatus;
approvedBy?: AgentId;
approvedAt?: Timestamp;
expiresAt?: Timestamp;
}
Querying Exceptions
// Get active exceptions
const active = await exceptions.getActive();
// Get by policy
const policyExceptions = await exceptions.getByPolicy(policyId);
// Get by agent
const agentExceptions = await exceptions.getByAgent(agentId);
@contextgraph/rbac
Role-Based Access Control with built-in roles and permission checking.
Installation
pnpm add @contextgraph/rbac
Overview
Manage access control through roles:
- Role definitions with permissions
- Role inheritance
- Permission checking
- Built-in roles
Creating Roles
import { RBACManager } from '@contextgraph/rbac';
const rbac = new RBACManager(storage);
await rbac.initialize();
await rbac.createRole({
name: 'data-analyst',
description: 'Can read and analyze data',
permissions: [
'read:reports',
'read:dashboards',
'export:csv',
],
inherits: ['viewer'],
});
Role Inheritance
// Viewer role
await rbac.createRole({
name: 'viewer',
permissions: ['read:public'],
});
// Analyst inherits viewer
await rbac.createRole({
name: 'analyst',
permissions: ['read:data', 'export:csv'],
inherits: ['viewer'],
});
// Admin inherits analyst
await rbac.createRole({
name: 'admin',
permissions: ['write:*', 'delete:*'],
inherits: ['analyst'],
});
Assigning Roles
// Assign role to agent
await rbac.assignRole(agentId, 'data-analyst');
// Get agent roles
const roles = await rbac.getRoles(agentId);
// Check role
const hasRole = await rbac.hasRole(agentId, 'data-analyst');
Permission Checking
// Check single permission
const canRead = await rbac.hasPermission(agentId, 'read:reports');
// Check any of multiple permissions
const canAccess = await rbac.hasAnyPermission(agentId, ['read:reports', 'read:dashboards']);
// Get all effective permissions
const permissions = await rbac.getEffectivePermissions(agentId);
Built-in Roles
| Role | Permissions |
|---|---|
viewer | Read public data |
editor | Read, write data |
admin | Full access |
auditor | Read audit logs |
Permission Format
action:resource
action:resource:subresource
action:*
*:resource
Examples:
read:documentswrite:reports:quarterlydelete:**:admin-panel
@contextgraph/compliance
Compliance reporting and GDPR features.
Installation
pnpm add @contextgraph/compliance
Overview
Support regulatory compliance:
- Audit reports
- Access reports
- Decision reports
- GDPR data subject rights
Audit Reports
import { ComplianceReporter } from '@contextgraph/compliance';
const reporter = new ComplianceReporter(ckg, dtg, provenance, storage);
const report = await reporter.generateAuditReport({
from: startDate,
to: endDate,
format: 'json', // or 'pdf', 'csv'
include: ['entities', 'claims', 'decisions', 'provenance'],
});
Access Reports
// Who accessed what
const accessReport = await reporter.generateAccessReport({
entityId: entityId,
from: startDate,
to: endDate,
});
Decision Reports
// Decision analytics
const decisionReport = await reporter.generateDecisionReport({
from: startDate,
to: endDate,
groupBy: 'agent', // or 'type', 'status'
});
GDPR Support
Data Subject Access
// Get all data for a person
const subjectData = await reporter.getDataSubjectData(personId);
Data Portability
// Export data in portable format
const exportData = await reporter.exportDataSubjectData(personId, {
format: 'json',
include: ['profile', 'claims', 'decisions'],
});
Right to Erasure
// Delete with provenance tracking
await reporter.deleteDataSubjectData(personId, {
reason: 'GDPR Article 17 request',
requestId: 'gdpr_req_123',
retainAudit: true, // Keep audit trail
});
Consent Tracking
// Record consent
await reporter.recordConsent(personId, {
purpose: 'marketing',
granted: true,
timestamp: createTimestamp(),
});
// Check consent
const hasConsent = await reporter.hasConsent(personId, 'marketing');
// Withdraw consent
await reporter.withdrawConsent(personId, 'marketing');
Report Formats
| Format | Description |
|---|---|
json | Machine-readable JSON |
csv | Spreadsheet-compatible |
pdf | Human-readable document |
Scheduling Reports
// Schedule daily compliance report
await reporter.schedule({
type: 'audit',
frequency: 'daily',
recipients: ['compliance@example.com'],
format: 'pdf',
});
@contextgraph/agent
Agent registry, capabilities, and hierarchies.
Installation
pnpm add @contextgraph/agent
Overview
The agent package manages:
- Agent registration and lifecycle
- Capability definitions
- Agent hierarchies and delegation
- Problem-space graphs
Creating Agents
import { AgentRegistry } from '@contextgraph/agent';
const registry = new AgentRegistry(storage);
await registry.initialize();
const agent = await registry.create({
name: 'data-processor',
description: 'Processes incoming data files',
capabilities: ['read', 'transform', 'write'],
metadata: {
version: '1.0.0',
maintainer: 'team@example.com',
},
});
Agent Lifecycle
// Suspend an agent
await registry.suspend(agentId, 'Maintenance');
// Reactivate
await registry.reactivate(agentId);
// Revoke permanently
await registry.revoke(agentId, 'Security concern');
Agent Status
| Status | Description |
|---|---|
active | Agent is operational |
suspended | Temporarily disabled |
revoked | Permanently disabled |
Capabilities
// Define capabilities
const capabilities = [
'read:documents',
'write:reports',
'execute:workflows',
'admin:users',
];
// Check capability
const hasCapability = await registry.hasCapability(agentId, 'read:documents');
Agent Hierarchies
import { AgentHierarchyManager } from '@contextgraph/agent';
const hierarchy = new AgentHierarchyManager(storage);
// Set parent-child relationship
await hierarchy.setParent(childAgentId, parentAgentId);
// Get children
const children = await hierarchy.getChildren(parentAgentId);
// Check if descendant
const isDescendant = await hierarchy.isDescendant(agentId, ancestorId);
Capability Delegation
// Delegate capabilities from parent to child
await hierarchy.delegate(parentId, childId, ['read:documents']);
// Get effective capabilities (inherited + own)
const effective = await hierarchy.getEffectiveCapabilities(agentId);
Agent Interface
interface Agent {
id: AgentId;
name: string;
description?: string;
status: AgentStatus;
capabilities: string[];
metadata?: Record<string, unknown>;
parentId?: AgentId;
createdAt: Timestamp;
updatedAt: Timestamp;
}
Querying Agents
// Get by ID
const agent = await registry.get(agentId);
// Get by name
const agent = await registry.getByName('data-processor');
// List all active agents
const agents = await registry.list({ status: 'active' });
@contextgraph/execution
Agent execution framework with policy enforcement.
Installation
pnpm add @contextgraph/execution
Overview
The execution package provides:
- Action execution framework
- Handler registration
- Policy enforcement
- Audit logging
Creating the Executor
import { ActionExecutor } from '@contextgraph/execution';
const executor = new ActionExecutor(
agentRegistry,
policyEvaluator,
provenanceLedger,
storage
);
await executor.initialize();
Registering Handlers
executor.registerHandler('read', 'document', async (action, context) => {
const content = await readDocument(action.resourceId);
return ok({ content, size: content.length });
});
executor.registerHandler('transform', 'csv', async (action, context) => {
const data = await readCSV(action.resourceId);
const transformed = await transform(data, action.parameters);
return ok({ rowCount: transformed.length });
});
Executing Actions
const result = await executor.execute({
agentId: agentId,
action: 'read',
resourceType: 'document',
resourceId: 'reports/q4-2024.pdf',
parameters: {
format: 'text',
},
});
if (result.ok) {
console.log('Execution result:', result.value.output);
} else {
console.error('Execution failed:', result.error);
}
Action Structure
interface ActionRequest {
agentId: AgentId;
action: string;
resourceType: string;
resourceId: string;
parameters?: Record<string, unknown>;
}
interface ActionResult {
executionId: string;
status: 'allowed' | 'denied' | 'error';
output?: unknown;
error?: Error;
duration: number;
timestamp: Timestamp;
}
Handler Context
executor.registerHandler('process', 'data', async (action, context) => {
// Access context
const agent = context.agent; // Executing agent
const policies = context.policies; // Matched policies
const timestamp = context.timestamp; // Execution time
// Perform work
return ok({ processed: true });
});
Policy Enforcement
Actions are automatically checked against policies:
// If policy denies, execution is blocked
const result = await executor.execute({
agentId: agentId,
action: 'delete',
resourceType: 'pii',
resourceId: 'user/123',
});
if (!result.ok && result.error.code === 'POLICY_DENIED') {
console.log('Access denied by policy');
}
Audit Logging
All executions are automatically logged to provenance:
// Query execution history
const history = await provenanceLedger.query({
type: 'execution_logged',
agentId: agentId,
limit: 100,
});
Workflows
Chain multiple actions:
async function processWorkflow(agentId: AgentId) {
// Step 1: Read data
const readResult = await executor.execute({
agentId,
action: 'read',
resourceType: 'csv',
resourceId: 'input.csv',
});
if (!readResult.ok) return readResult;
// Step 2: Transform
const transformResult = await executor.execute({
agentId,
action: 'transform',
resourceType: 'data',
resourceId: 'temp',
parameters: { data: readResult.value.output },
});
if (!transformResult.ok) return transformResult;
// Step 3: Write output
return executor.execute({
agentId,
action: 'write',
resourceType: 'json',
resourceId: 'output.json',
parameters: { data: transformResult.value.output },
});
}
@contextgraph/viz - Visualization Package
Visualization renderers for ContextGraph data structures. Generate visual representations of provenance chains, entity relationships, decision trees, and custom graphs in multiple formats.
Table of Contents
- Overview
- Installation
- Output Formats
- API Reference
- Renderers
- Configuration
- Examples
- Integration Guide
Overview
The visualization package provides multiple rendering backends to visualize ContextGraph data:
| Format | Use Case | Interactive | Dependencies |
|---|---|---|---|
| DOT | GraphViz tools, CI/CD | No | Graphviz |
| Mermaid | Markdown docs, GitHub | No | Mermaid.js |
| D3.js | Web dashboards | Yes | D3.js |
| SVG | Standalone images | No | None |
Installation
pnpm add @contextgraph/viz
Output Formats
DOT (Graphviz)
Standard graph description language. Use with Graphviz tools:
# Generate PNG
echo "$DOT_OUTPUT" | dot -Tpng -o graph.png
# Generate PDF
echo "$DOT_OUTPUT" | dot -Tpdf -o graph.pdf
# Generate SVG
echo "$DOT_OUTPUT" | dot -Tsvg -o graph.svg
Mermaid
Markdown-compatible diagrams. Renders in GitHub, GitLab, and documentation sites:
```mermaid
graph TB
A[Entity] --> B[Claim]
B --> C[Provenance]
### D3.js
JSON format for D3.js force-directed graphs:
```javascript
const data = JSON.parse(d3Output);
// Use with d3.forceSimulation()
SVG
Standalone SVG images with no external dependencies.
API Reference
visualizeProvenance
Visualize the provenance chain from a ProvenanceLedger.
async function visualizeProvenance(
ledger: ProvenanceLedger,
options?: Partial<VizOptions>
): Promise<Result<string>>
Parameters:
| Name | Type | Description |
|---|---|---|
ledger | ProvenanceLedger | The provenance ledger to visualize |
options | Partial<VizOptions> | Visualization options |
Returns: Result<string> - The rendered output string
Example:
import { visualizeProvenance } from '@contextgraph/viz';
import { ProvenanceLedger } from '@contextgraph/provenance';
const ledger = new ProvenanceLedger(storage);
await ledger.initialize();
// Record some provenance entries
await ledger.record({
sourceType: 'human',
actor: 'user_123',
action: 'create',
metadata: { entity: 'User profile' },
});
// Visualize as Mermaid diagram
const result = await visualizeProvenance(ledger, {
format: 'mermaid',
showTimestamps: true,
colorScheme: 'default',
});
if (result.ok) {
console.log(result.value);
// Output:
// graph TB
// prov_abc123["create<br/>2024-01-15"]
// prov_def456["update<br/>2024-01-16"]
// prov_abc123 -->|derives| prov_def456
}
visualizeEntities
Visualize entity relationships from the Contextual Knowledge Graph.
async function visualizeEntities(
ckg: CKG,
entityType: string,
options?: Partial<VizOptions>
): Promise<Result<string>>
Parameters:
| Name | Type | Description |
|---|---|---|
ckg | CKG | The Contextual Knowledge Graph |
entityType | string | Type of entities to visualize |
options | Partial<VizOptions> | Visualization options |
Example:
import { visualizeEntities } from '@contextgraph/viz';
import { CKG } from '@contextgraph/ckg';
const ckg = new CKG(storage, provenance);
await ckg.initialize();
// Visualize person entities
const result = await visualizeEntities(ckg, 'person', {
format: 'dot',
showLabels: true,
direction: 'LR',
});
if (result.ok) {
// Write to file for Graphviz
fs.writeFileSync('entities.dot', result.value);
}
visualizeDecisions
Visualize the decision tree from a DecisionTraceGraph.
async function visualizeDecisions(
dtg: DecisionTraceGraph,
options?: Partial<VizOptions>
): Promise<Result<string>>
Parameters:
| Name | Type | Description |
|---|---|---|
dtg | DecisionTraceGraph | The Decision Trace Graph |
options | Partial<VizOptions> | Visualization options |
Example:
import { visualizeDecisions } from '@contextgraph/viz';
import { DecisionTraceGraph } from '@contextgraph/dtg';
const dtg = new DecisionTraceGraph(storage);
await dtg.initialize();
// Visualize with status colors
const result = await visualizeDecisions(dtg, {
format: 'svg',
showTimestamps: true,
colorScheme: 'default',
});
if (result.ok) {
fs.writeFileSync('decisions.svg', result.value);
}
visualizeTimeline
Visualize a sequence of events on a timeline.
function visualizeTimeline(
events: TimelineEvent[],
options?: Partial<VizOptions>
): Result<string>
Parameters:
| Name | Type | Description |
|---|---|---|
events | TimelineEvent[] | Array of timeline events |
options | Partial<VizOptions> | Visualization options |
TimelineEvent Interface:
interface TimelineEvent {
id: string;
label: string;
timestamp: number;
type: 'entity' | 'claim' | 'decision' | 'provenance';
metadata?: Record<string, unknown>;
}
Example:
import { visualizeTimeline } from '@contextgraph/viz';
const events: TimelineEvent[] = [
{ id: '1', label: 'User Created', timestamp: Date.now() - 3600000, type: 'entity' },
{ id: '2', label: 'Profile Updated', timestamp: Date.now() - 1800000, type: 'claim' },
{ id: '3', label: 'Access Granted', timestamp: Date.now(), type: 'decision' },
];
const result = visualizeTimeline(events, {
format: 'mermaid',
direction: 'LR', // Left to right for timeline
});
console.log(result.value);
visualizeGraph
Visualize custom graph data.
function visualizeGraph(
data: GraphData,
options?: Partial<VizOptions>
): Result<string>
GraphData Interface:
interface GraphData {
nodes: GraphNode[];
edges: GraphEdge[];
title?: string;
metadata?: Record<string, unknown>;
}
interface GraphNode {
id: string;
label: string;
type: 'entity' | 'claim' | 'decision' | 'provenance' | 'agent';
metadata?: Record<string, unknown>;
}
interface GraphEdge {
from: string;
to: string;
type: string;
label?: string;
metadata?: Record<string, unknown>;
}
Example:
import { visualizeGraph } from '@contextgraph/viz';
const data: GraphData = {
title: 'Knowledge Graph',
nodes: [
{ id: 'alice', label: 'Alice', type: 'entity' },
{ id: 'bob', label: 'Bob', type: 'entity' },
{ id: 'project', label: 'Project X', type: 'entity' },
],
edges: [
{ from: 'alice', to: 'project', type: 'works_on', label: 'lead' },
{ from: 'bob', to: 'project', type: 'works_on', label: 'contributor' },
{ from: 'alice', to: 'bob', type: 'manages' },
],
};
const result = visualizeGraph(data, { format: 'd3' });
console.log(result.value); // JSON for D3.js
Renderers
DOT/Graphviz
The DOT renderer generates Graphviz-compatible output.
import { renderDot } from '@contextgraph/viz';
const dot = renderDot(graphData, options);
Output Example:
digraph G {
rankdir=TB;
node [shape=ellipse, style=filled];
"ent_123" [label="Alice", fillcolor="#4a90d9"];
"ent_456" [label="Bob", fillcolor="#4a90d9"];
"ent_123" -> "ent_456" [label="manages"];
}
Node Shapes by Type:
| Type | Shape |
|---|---|
| entity | ellipse |
| claim | box |
| decision | diamond |
| provenance | hexagon |
| agent | doubleoctagon |
Mermaid
The Mermaid renderer generates markdown-compatible diagrams.
import { renderMermaid } from '@contextgraph/viz';
const mermaid = renderMermaid(graphData, options);
Output Example:
graph TB
ent_123["Alice"]
ent_456["Bob"]
ent_123 -->|manages| ent_456
classDef entity fill:#4a90d9,stroke:#333
class ent_123,ent_456 entity
Color Schemes:
| Scheme | Description |
|---|---|
default | Standard colors |
dark | Dark theme |
light | Light/pastel theme |
colorblind | Colorblind-friendly palette |
D3.js
The D3.js renderer outputs JSON for force-directed graphs.
import { renderD3, renderD3Config } from '@contextgraph/viz';
// Get graph data
const data = renderD3(graphData, options);
// Get recommended simulation config
const config = renderD3Config(options);
Output Structure:
interface D3GraphData {
nodes: D3Node[];
links: D3Link[];
metadata?: {
title?: string;
nodeCount: number;
linkCount: number;
groups: string[];
};
}
interface D3Node {
id: string;
label: string;
group: string;
metadata?: Record<string, unknown>;
}
interface D3Link {
source: string;
target: string;
label?: string;
type: string;
value: number;
}
Integration Example:
<script src="https://d3js.org/d3.v7.min.js"></script>
<script>
const data = /* output from renderD3() */;
const config = /* output from renderD3Config() */;
const simulation = d3.forceSimulation(data.nodes)
.force('link', d3.forceLink(data.links).id(d => d.id))
.force('charge', d3.forceManyBody().strength(config.forces.charge.strength))
.force('center', d3.forceCenter(width / 2, height / 2));
</script>
SVG
The SVG renderer generates standalone SVG images.
import { renderSvg } from '@contextgraph/viz';
const svg = renderSvg(graphData, options);
fs.writeFileSync('graph.svg', svg);
Features:
- Automatic layout calculation (BFS-based)
- Arrow markers for directed edges
- Node styling based on type
- Edge labels
- Configurable color schemes
Configuration
VizOptions
interface VizOptions {
/** Output format */
format: 'dot' | 'mermaid' | 'd3' | 'svg';
/** Show labels on nodes and edges */
showLabels?: boolean;
/** Show timestamps in labels */
showTimestamps?: boolean;
/** Maximum depth for traversal */
maxDepth?: number;
/** Color scheme */
colorScheme?: 'default' | 'dark' | 'light' | 'colorblind';
/** Graph direction */
direction?: 'TB' | 'BT' | 'LR' | 'RL';
/** Custom node styles by type */
nodeStyles?: Record<string, NodeStyle>;
/** Custom edge styles by type */
edgeStyles?: Record<string, EdgeStyle>;
}
NodeStyle
interface NodeStyle {
color?: string;
fillColor?: string;
shape?: 'circle' | 'rect' | 'diamond' | 'hexagon';
size?: number;
}
EdgeStyle
interface EdgeStyle {
color?: string;
width?: number;
style?: 'solid' | 'dashed' | 'dotted';
}
Default Options
const DEFAULT_OPTIONS: VizOptions = {
format: 'mermaid',
showLabels: true,
showTimestamps: false,
maxDepth: 10,
colorScheme: 'default',
direction: 'TB',
};
Examples
Provenance Audit Report
import { visualizeProvenance } from '@contextgraph/viz';
import { ProvenanceLedger } from '@contextgraph/provenance';
async function generateAuditReport(ledger: ProvenanceLedger) {
// Generate Mermaid diagram for documentation
const mermaidResult = await visualizeProvenance(ledger, {
format: 'mermaid',
showTimestamps: true,
colorScheme: 'default',
});
// Generate SVG for PDF report
const svgResult = await visualizeProvenance(ledger, {
format: 'svg',
showTimestamps: true,
direction: 'TB',
});
return {
markdown: `## Provenance Chain\n\n\`\`\`mermaid\n${mermaidResult.value}\n\`\`\``,
svg: svgResult.value,
};
}
Interactive Dashboard
import { visualizeGraph } from '@contextgraph/viz';
async function getDashboardData() {
const graphData = await buildGraphFromDatabase();
const result = visualizeGraph(graphData, {
format: 'd3',
colorScheme: 'dark',
});
return JSON.parse(result.value);
}
// In React/Vue component
const dashboardData = await getDashboardData();
// Use with react-force-graph or similar
Entity Relationship Diagram
import { visualizeEntities } from '@contextgraph/viz';
async function generateERD(ckg: CKG, entityType: string) {
const result = await visualizeEntities(ckg, entityType, {
format: 'dot',
showLabels: true,
direction: 'LR',
nodeStyles: {
entity: { fillColor: '#e3f2fd', shape: 'rect' },
},
edgeStyles: {
references: { style: 'dashed', color: '#1976d2' },
},
});
return result.value;
}
Integration Guide
With Graphviz
# Install Graphviz
brew install graphviz # macOS
apt install graphviz # Ubuntu
# Generate image from DOT output
node generate-dot.js | dot -Tpng -o output.png
With Mermaid CLI
# Install Mermaid CLI
npm install -g @mermaid-js/mermaid-cli
# Generate image from Mermaid output
node generate-mermaid.js > diagram.mmd
mmdc -i diagram.mmd -o output.png
With GitHub
Mermaid diagrams render automatically in GitHub:
# Knowledge Graph
```mermaid
graph TB
A[User] -->|owns| B[Document]
B -->|references| C[Entity]
### With React
```tsx
import { visualizeGraph } from '@contextgraph/viz';
import ForceGraph2D from 'react-force-graph-2d';
function GraphViewer({ graphData }) {
const d3Data = useMemo(() => {
const result = visualizeGraph(graphData, { format: 'd3' });
return JSON.parse(result.value);
}, [graphData]);
return (
<ForceGraph2D
graphData={d3Data}
nodeLabel="label"
linkLabel="label"
/>
);
}
With Express API
import express from 'express';
import { visualizeProvenance } from '@contextgraph/viz';
const app = express();
app.get('/api/provenance/diagram', async (req, res) => {
const format = req.query.format || 'svg';
const result = await visualizeProvenance(ledger, { format });
if (!result.ok) {
return res.status(500).json({ error: result.error.message });
}
const contentType = format === 'svg' ? 'image/svg+xml' :
format === 'd3' ? 'application/json' : 'text/plain';
res.setHeader('Content-Type', contentType);
res.send(result.value);
});
Supported Formats Summary
| Feature | DOT | Mermaid | D3.js | SVG |
|---|---|---|---|---|
| Provenance Chains | ✅ | ✅ | ✅ | ✅ |
| Entity Relationships | ✅ | ✅ | ✅ | ✅ |
| Decision Trees | ✅ | ✅ | ✅ | ✅ |
| Timelines | ✅ | ✅ | ✅ | ✅ |
| Custom Graphs | ✅ | ✅ | ✅ | ✅ |
| Color Schemes | ✅ | ✅ | ✅ | ✅ |
| Custom Styles | ✅ | ✅ | ✅ | ✅ |
| Interactive | ❌ | ❌ | ✅ | ❌ |
| Standalone | ❌ | ❌ | ❌ | ✅ |
@contextgraph/reasoning - Semantic Reasoning Package
Semantic reasoning engine for ContextGraph with inference rules, relation types, and contradiction detection. Enables knowledge graph enrichment through logical inference and consistency validation.
Table of Contents
- Overview
- Installation
- Core Concepts
- API Reference
- Built-in Relations
- Built-in Rules
- Custom Rules
- Contradiction Resolution
- Examples
- Performance Considerations
Overview
The reasoning package provides:
| Component | Purpose |
|---|---|
| RelationRegistry | Manage relation types (transitive, symmetric, inverse) |
| RuleEngine | Forward chaining inference with pattern matching |
| Reasoner | High-level reasoning API with CKG integration |
| ContradictionDetector | Detect and resolve conflicting claims |
Installation
pnpm add @contextgraph/reasoning
Core Concepts
Relations
Relations define how entities connect to each other. The reasoning engine supports three special relation types:
| Type | Description | Example |
|---|---|---|
| Transitive | If A→B and B→C, then A→C | partOf, locatedIn, ancestorOf |
| Symmetric | If A→B, then B→A | knows, siblingOf, collaboratesWith |
| Inverse | If A→B via R, then B→A via R⁻¹ | parentOf/childOf, employs/employedBy |
Inference Rules
Rules consist of conditions (patterns to match) and conclusions (facts to derive):
interface InferenceRule {
id: string;
name: string;
description: string;
conditions: RuleCondition[];
conclusions: RuleConclusion[];
priority: number;
enabled: boolean;
}
Contradictions
Contradictions occur when claims conflict:
| Type | Description | Example |
|---|---|---|
| Direct Negation | Same predicate with true/false | isActive: true vs isActive: false |
| Mutual Exclusion | Incompatible values | status: active vs status: deleted |
| Temporal Overlap | Conflicting validity periods | Two addresses valid at same time |
| Cardinality | Multiple values for single-valued property | Two birthdates |
API Reference
Reasoner
The main reasoning interface that integrates with CKG.
class Reasoner {
constructor(
ckg: CKG,
storage: StorageInterface,
config?: Partial<ReasonerConfig>
);
// Infer new facts for an entity
async infer(entityId: EntityId, predicates?: string[]): Promise<Result<InferredFact[]>>;
// Explain why a claim exists
async explain(claimId: ClaimId): Promise<Result<Explanation>>;
// Explain an inferred fact
explainInference(inferredFact: InferredFact): Explanation;
// Get all inferred facts
getInferredFacts(): InferredFact[];
// Clear inference cache
clearInferences(): void;
// Get statistics
getStats(): ReasoningStats;
// Register custom relation
registerRelation(definition: RelationDefinition): void;
// Register custom rule
registerRule(rule: InferenceRule): void;
// Access underlying components
getRelations(): RelationRegistry;
getRuleEngine(): RuleEngine;
}
ReasonerConfig
interface ReasonerConfig {
/** Maximum inference iterations (default: 100) */
maxIterations?: number;
/** Maximum inferred facts (default: 10000) */
maxFacts?: number;
/** Minimum confidence threshold (default: 0.5) */
minConfidence?: number;
/** Whether to store inferred facts (default: false) */
materialize?: boolean;
}
InferredFact
interface InferredFact {
subjectId: EntityId;
predicate: string;
object: string | number | boolean;
sourceClaims: ClaimId[];
ruleId: string;
confidence: number;
inferredAt: number;
}
Explanation
interface Explanation {
claimId?: ClaimId;
inferredFact?: InferredFact;
text: string;
sources: Array<{ claimId: ClaimId; description: string }>;
rules: Array<{ ruleId: string; ruleName: string }>;
chain: string[];
}
Example:
import { createReasoner } from '@contextgraph/reasoning';
import { CKG } from '@contextgraph/ckg';
const ckg = new CKG(storage, provenance);
const reasoner = createReasoner(ckg, storage);
// Infer facts for an entity
const result = await reasoner.infer(entityId);
if (result.ok) {
for (const fact of result.value) {
console.log(`Inferred: ${fact.subjectId} ${fact.predicate} ${fact.object}`);
console.log(` Confidence: ${fact.confidence}`);
console.log(` Rule: ${fact.ruleId}`);
}
}
// Explain a claim
const explanation = await reasoner.explain(claimId);
if (explanation.ok) {
console.log(explanation.value.text);
console.log('Reasoning chain:', explanation.value.chain.join(' → '));
}
RelationRegistry
Manages relation definitions and their properties.
class RelationRegistry {
constructor();
// Register a relation
register(definition: RelationDefinition): void;
// Check relation properties
isTransitive(name: string): boolean;
isSymmetric(name: string): boolean;
// Get inverse relation
getInverse(name: string): string | undefined;
// Get relation definition
get(name: string): RelationDefinition | undefined;
// Get all relations
getAll(): RelationDefinition[];
// Number of registered relations
get size(): number;
}
RelationDefinition
interface RelationDefinition {
name: string;
description?: string;
transitive?: boolean;
symmetric?: boolean;
inverseName?: string;
domain?: string[]; // Allowed subject types
range?: string[]; // Allowed object types
}
Example:
import { createRelationRegistry } from '@contextgraph/reasoning';
const registry = createRelationRegistry();
// Register custom relation
registry.register({
name: 'reportsTo',
description: 'Employee reports to manager',
transitive: true,
inverseName: 'manages',
domain: ['employee'],
range: ['employee', 'manager'],
});
// Check properties
console.log(registry.isTransitive('reportsTo')); // true
console.log(registry.getInverse('reportsTo')); // 'manages'
RuleEngine
Forward chaining inference engine with pattern matching.
class RuleEngine {
constructor(rules?: RuleRegistry);
// Find all matching rules
findMatches(facts: Fact[]): RuleMatch[];
// Apply a rule to generate new facts
applyRule(rule: InferenceRule, bindings: VariableBinding[]): Fact[];
// Run forward chaining until fixpoint
forwardChain(
initialFacts: Fact[],
options?: { maxIterations?: number; maxFacts?: number }
): Result<{ facts: Fact[]; iterations: number; newFactsCount: number }>;
// Access rule registry
getRules(): RuleRegistry;
}
Fact
interface Fact {
subject: string;
predicate: string;
object: string | number | boolean;
}
RuleMatch
interface RuleMatch {
rule: InferenceRule;
bindings: VariableBinding[];
}
Example:
import { createRuleEngine } from '@contextgraph/reasoning';
const engine = createRuleEngine();
const facts: Fact[] = [
{ subject: 'alice', predicate: 'parentOf', object: 'bob' },
{ subject: 'bob', predicate: 'parentOf', object: 'charlie' },
];
const result = engine.forwardChain(facts, { maxIterations: 10 });
if (result.ok) {
console.log(`Derived ${result.value.newFactsCount} new facts`);
console.log(`Total facts: ${result.value.facts.length}`);
console.log(`Iterations: ${result.value.iterations}`);
}
ContradictionDetector
Detects and resolves contradictory claims.
class ContradictionDetector {
constructor(ckg: CKG, storage: StorageInterface);
// Detect contradictions for an entity
async detectContradictions(entityId: EntityId): Promise<Result<Contradiction[]>>;
// Detect all contradictions for an entity type
async detectAllForType(entityType: string): Promise<Result<Contradiction[]>>;
// Check consistency before adding a claim
async checkConsistency(
entityId: EntityId,
predicate: string,
value: unknown
): Promise<Result<{ consistent: boolean; conflicts: Contradiction[] }>>;
// Resolve a contradiction
resolveContradiction(
contradictionId: string,
strategy: ResolutionStrategy,
claims: Array<{ id: ClaimId; createdAt: number; confidence: number }>
): Result<ResolutionResult>;
// Get all detected contradictions
getContradictions(): Contradiction[];
// Add custom exclusion rule
addExclusionRule(rule: MutualExclusionRule): void;
// Add single-valued property rules
addSingleValuedRule(rule: SingleValuedRule): void;
}
Contradiction
interface Contradiction {
id: string;
type: ContradictionType;
entityId: EntityId;
claimIds: ClaimId[];
description: string;
detectedAt: number;
suggestedResolution?: ResolutionStrategy;
}
type ContradictionType =
| 'direct_negation'
| 'mutual_exclusion'
| 'temporal_overlap'
| 'cardinality'
| 'type_mismatch';
ResolutionStrategy
type ResolutionStrategy =
| 'latest_wins' // Keep most recent claim
| 'highest_confidence' // Keep highest confidence claim
| 'manual_required' // Requires human decision
| 'keep_both' // Accept contradiction
| 'revoke_both'; // Remove all conflicting claims
ResolutionResult
interface ResolutionResult {
contradictionId: string;
strategy: ResolutionStrategy;
keptClaims: ClaimId[];
revokedClaims: ClaimId[];
resolvedAt: number;
}
Example:
import { createContradictionDetector } from '@contextgraph/reasoning';
const detector = createContradictionDetector(ckg, storage);
// Detect contradictions
const result = await detector.detectContradictions(entityId);
if (result.ok) {
for (const contradiction of result.value) {
console.log(`Found ${contradiction.type}: ${contradiction.description}`);
console.log(` Claims: ${contradiction.claimIds.join(', ')}`);
console.log(` Suggested: ${contradiction.suggestedResolution}`);
}
}
// Check before adding claim
const consistency = await detector.checkConsistency(
entityId,
'dateOfBirth',
'1990-01-15'
);
if (!consistency.value.consistent) {
console.log('Would create contradiction!');
for (const conflict of consistency.value.conflicts) {
console.log(` Conflict with: ${conflict.claimIds.join(', ')}`);
}
}
Built-in Relations
The registry includes common relations out of the box:
Transitive Relations
| Name | Description |
|---|---|
partOf | Part-whole relationship |
subclassOf | Type hierarchy |
locatedIn | Location hierarchy |
reportsTo | Organizational hierarchy |
ancestorOf | Family hierarchy |
Symmetric Relations
| Name | Description |
|---|---|
knows | Personal acquaintance |
collaboratesWith | Professional collaboration |
relatedTo | Generic relation |
siblingOf | Family relation |
marriedTo | Marital relation |
Inverse Relations
| Relation | Inverse |
|---|---|
parentOf | childOf |
employs | employedBy |
owns | ownedBy |
manages | managedBy |
teaches | taughtBy |
creates | createdBy |
Built-in Rules
Transitive Closure
{
id: 'transitive-closure',
name: 'Transitive Closure',
description: 'If A relates to B and B relates to C via transitive relation, then A relates to C',
conditions: [
{ subject: '?a', predicate: '?rel', object: '?b' },
{ subject: '?b', predicate: '?rel', object: '?c' },
],
conclusions: [
{ subject: '?a', predicate: '?rel', object: '?c', confidenceMultiplier: 0.9 }
],
priority: 100,
}
Symmetric Relation
{
id: 'symmetric-relation',
name: 'Symmetric Relation',
description: 'If A relates to B via symmetric relation, then B relates to A',
conditions: [
{ subject: '?a', predicate: '?rel', object: '?b' }
],
conclusions: [
{ subject: '?b', predicate: '?rel', object: '?a', confidenceMultiplier: 1.0 }
],
priority: 90,
}
Inverse Relation
{
id: 'inverse-relation',
name: 'Inverse Relation',
description: 'If A relates to B, then B relates to A via inverse',
conditions: [
{ subject: '?a', predicate: '?rel', object: '?b' }
],
conclusions: [
{ subject: '?b', predicate: '?inverseRel', object: '?a', confidenceMultiplier: 1.0 }
],
priority: 80,
}
Custom Rules
Create custom inference rules for domain-specific reasoning:
import { Reasoner } from '@contextgraph/reasoning';
const reasoner = createReasoner(ckg, storage);
// Rule: Team members know each other
reasoner.registerRule({
id: 'team-knows',
name: 'Team Members Know Each Other',
description: 'If A and B are on the same team, they know each other',
conditions: [
{ subject: '?a', predicate: 'memberOf', object: '?team' },
{ subject: '?b', predicate: 'memberOf', object: '?team' },
],
conclusions: [
{ subject: '?a', predicate: 'knows', object: '?b', confidenceMultiplier: 0.8 }
],
priority: 50,
enabled: true,
});
// Rule: Manager hierarchy
reasoner.registerRule({
id: 'skip-level-manager',
name: 'Skip-Level Manager',
description: 'If A manages B and B manages C, then A is skip-level manager of C',
conditions: [
{ subject: '?a', predicate: 'manages', object: '?b' },
{ subject: '?b', predicate: 'manages', object: '?c' },
],
conclusions: [
{ subject: '?a', predicate: 'skipLevelManages', object: '?c', confidenceMultiplier: 0.95 }
],
priority: 60,
enabled: true,
});
Pattern Variables
Rules use variables (prefixed with ?) for pattern matching:
| Variable | Matches |
|---|---|
?a, ?b, ?c | Any entity ID |
?rel | Any predicate |
?value | Any value |
Confidence Multiplier
Each conclusion has a confidenceMultiplier that reduces confidence for inferred facts:
// Source claims have confidence 1.0
// After transitive inference (0.9 multiplier):
// Final confidence = 1.0 * 0.9 = 0.9
// After two transitive steps:
// Final confidence = 1.0 * 0.9 * 0.9 = 0.81
Contradiction Resolution
Default Single-Valued Properties
These properties can only have one value:
dateOfBirthdateOfDeathbirthplacegendermaritalStatusnationalityssntaxIdemailprimaryPhone
Default Mutual Exclusions
// Status values are mutually exclusive
{ predicate: 'status', exclusiveValues: ['active', 'inactive', 'suspended', 'deleted'] }
// Alive/dead are mutually exclusive
{ predicate: 'alive', exclusiveValues: [true, false] }
Adding Custom Rules
const detector = createContradictionDetector(ckg, storage);
// Add single-valued property
detector.addSingleValuedRule({
predicates: ['primaryAddress', 'currentEmployer', 'spouseId'],
});
// Add mutual exclusion
detector.addExclusionRule({
predicate: 'employmentStatus',
exclusiveValues: ['employed', 'unemployed', 'retired', 'student'],
description: 'Employment status values are mutually exclusive',
});
Resolution Workflow
// 1. Detect contradictions
const contradictions = await detector.detectContradictions(entityId);
// 2. For each contradiction, decide resolution
for (const contradiction of contradictions.value) {
// Get the claims with their metadata
const claims = await Promise.all(
contradiction.claimIds.map(async (id) => {
const claim = await ckg.getClaim(id);
return {
id,
createdAt: claim.value?.data.createdAt ?? 0,
confidence: 1.0, // or get from claim metadata
};
})
);
// Apply resolution strategy
const resolution = detector.resolveContradiction(
contradiction.id,
'latest_wins',
claims
);
if (resolution.ok) {
// Revoke the claims that lost
for (const claimId of resolution.value.revokedClaims) {
await ckg.revokeClaim(claimId);
}
console.log(`Resolved: kept ${resolution.value.keptClaims.length} claims`);
}
}
Examples
Organization Hierarchy Reasoning
import { createReasoner, createRelationRegistry } from '@contextgraph/reasoning';
async function reasonAboutOrganization(ckg: CKG) {
const reasoner = createReasoner(ckg, storage);
// Register organization-specific relations
reasoner.registerRelation({
name: 'reportsTo',
transitive: true,
inverseName: 'directReports',
});
// Add organization-specific rule
reasoner.registerRule({
id: 'same-department',
name: 'Same Department Inference',
description: 'People who report to same manager are in same department',
conditions: [
{ subject: '?a', predicate: 'reportsTo', object: '?manager' },
{ subject: '?b', predicate: 'reportsTo', object: '?manager' },
],
conclusions: [
{ subject: '?a', predicate: 'sameDepartmentAs', object: '?b', confidenceMultiplier: 0.85 }
],
priority: 40,
enabled: true,
});
// Run inference for an employee
const result = await reasoner.infer(employeeId);
if (result.ok) {
// Find all indirect reports
const indirectReports = result.value.filter(
f => f.predicate === 'directReports' && f.ruleId === 'inverse-relation'
);
// Find all colleagues
const colleagues = result.value.filter(
f => f.predicate === 'sameDepartmentAs'
);
return { indirectReports, colleagues };
}
}
Knowledge Graph Consistency Check
import { createContradictionDetector } from '@contextgraph/reasoning';
async function validateKnowledgeGraph(ckg: CKG) {
const detector = createContradictionDetector(ckg, storage);
// Check all person entities
const result = await detector.detectAllForType('person');
if (!result.ok) {
console.error('Failed to check:', result.error);
return;
}
const report = {
totalContradictions: result.value.length,
byType: {} as Record<string, number>,
criticalIssues: [] as Contradiction[],
};
for (const contradiction of result.value) {
// Count by type
report.byType[contradiction.type] =
(report.byType[contradiction.type] ?? 0) + 1;
// Flag critical issues (cardinality on important fields)
if (
contradiction.type === 'cardinality' &&
['ssn', 'dateOfBirth', 'taxId'].some(f =>
contradiction.description.includes(f)
)
) {
report.criticalIssues.push(contradiction);
}
}
return report;
}
Pre-flight Claim Validation
import { createContradictionDetector } from '@contextgraph/reasoning';
async function addClaimSafely(
ckg: CKG,
entityId: EntityId,
predicate: string,
value: unknown
) {
const detector = createContradictionDetector(ckg, storage);
// Check consistency first
const check = await detector.checkConsistency(entityId, predicate, value);
if (!check.ok) {
throw check.error;
}
if (!check.value.consistent) {
// Handle conflicts
const conflicts = check.value.conflicts;
// Option 1: Reject
throw new Error(
`Cannot add claim: would conflict with ${conflicts.length} existing claims`
);
// Option 2: Auto-resolve with latest-wins
// for (const conflict of conflicts) {
// await detector.resolveContradiction(conflict.id, 'latest_wins', ...);
// }
}
// Safe to add
return ckg.addClaim({ subjectId: entityId, predicate, objectValue: value, ... });
}
Performance Considerations
Limiting Inference Scope
// Only infer specific predicates
const result = await reasoner.infer(entityId, ['manages', 'reportsTo']);
Controlling Iterations
const reasoner = createReasoner(ckg, storage, {
maxIterations: 50, // Limit inference depth
maxFacts: 5000, // Limit total facts
minConfidence: 0.6, // Filter low-confidence inferences
});
Caching Inferences
// Inferences are cached in memory
const result1 = await reasoner.infer(entityId); // Computes
const result2 = await reasoner.infer(entityId); // Uses cache
// Clear cache when data changes
reasoner.clearInferences();
Rule Priority
Higher priority rules fire first. Use this to ensure important rules complete before others:
// Critical rules: 90-100
// Normal rules: 50-89
// Optional rules: 0-49
Batch Processing
For large graphs, process entities in batches:
async function inferAll(entityIds: EntityId[]) {
const results = [];
for (const batch of chunk(entityIds, 100)) {
const batchResults = await Promise.all(
batch.map(id => reasoner.infer(id))
);
results.push(...batchResults);
// Clear cache between batches if memory is a concern
reasoner.clearInferences();
}
return results;
}
@contextgraph/recommendations - Decision Recommendations Package
Similarity-based decision recommendations for ContextGraph. Analyzes historical decisions to provide intelligent recommendations with risk assessment and confidence scoring.
Table of Contents
- Overview
- Installation
- Core Concepts
- API Reference
- Configuration
- Recommendation Types
- Feedback Loop
- Examples
- Best Practices
Overview
The recommendations package provides:
| Component | Purpose |
|---|---|
| RecommendationEngine | Find similar decisions and generate recommendations |
| Similarity Calculator | Compare decision contexts with configurable weights |
| Risk Assessor | Evaluate risk based on patterns and precedent outcomes |
| Feedback System | Track accuracy and improve over time |
Installation
pnpm add @contextgraph/recommendations
Core Concepts
Decision Context
A decision context captures the key attributes of a decision request:
interface DecisionContext {
/** Action being requested (e.g., 'create', 'delete', 'approve') */
action: string;
/** Type of entity involved (optional) */
entityType?: string;
/** Resource path or identifier (optional) */
resource?: string;
/** Additional attributes for matching */
attributes: Record<string, unknown>;
/** When the decision was made (optional) */
timestamp?: number;
}
Similarity Matching
Similarity is calculated across four dimensions:
| Dimension | Weight | Description |
|---|---|---|
| Action | 40% | Exact match of action type |
| Entity Type | 20% | Match of entity type |
| Resource | 20% | Path similarity (prefix matching) |
| Attributes | 20% | Jaccard similarity of attributes |
Risk Assessment
Risk is evaluated based on:
- Action Patterns - Destructive actions (delete, destroy, revoke)
- Privilege Patterns - Elevated access (admin, root, sudo)
- Precedent Outcomes - Historical failure/rejection rates
- Precedent Count - Lack of historical data
API Reference
RecommendationEngine
The main class for generating recommendations.
class RecommendationEngine {
constructor(
dtg: DecisionTraceGraph,
storage: StorageInterface,
config?: RecommendationEngineConfig
);
// Find similar past decisions
async findSimilarDecisions(context: DecisionContext): Promise<Result<SimilarDecision[]>>;
// Get a recommendation
async recommend(context: DecisionContext): Promise<Result<Recommendation>>;
// Explain a recommendation
explainRecommendation(recommendationId: string): Result<string>;
// Submit feedback on a recommendation
submitFeedback(feedback: RecommendationFeedback): Result<void>;
// Get statistics
getStats(): RecommendationStats;
// Update matching criteria
setCriteria(criteria: Partial<MatchingCriteria>): void;
}
RecommendationEngineConfig
interface RecommendationEngineConfig {
/** Matching criteria overrides */
criteria?: Partial<MatchingCriteria>;
/** Minimum precedents for confident recommendation (default: 3) */
minPrecedents?: number;
/** High-risk action patterns (default: ['delete', 'destroy', 'terminate', 'revoke', 'disable']) */
highRiskPatterns?: string[];
/** Escalation-required patterns (default: ['admin', 'root', 'sudo', 'override']) */
escalationPatterns?: string[];
}
Recommendation
interface Recommendation {
/** Unique recommendation ID */
id: string;
/** Recommended action */
recommendedAction: 'approve' | 'reject' | 'defer' | 'escalate';
/** Confidence score (0-1) */
confidence: number;
/** Human-readable reasoning */
reasoning: string;
/** Similar historical decisions */
precedents: SimilarDecision[];
/** Risk assessment */
risk: RiskAssessment;
/** When generated */
generatedAt: number;
}
SimilarDecision
interface SimilarDecision {
/** Decision ID from DTG */
decisionId: DecisionId;
/** Similarity score details */
similarity: SimilarityScore;
/** Outcome of the decision */
outcome: 'approved' | 'rejected' | 'executed' | 'failed';
/** Age of decision in milliseconds */
ageMs: number;
}
RiskAssessment
interface RiskAssessment {
/** Overall risk level */
level: 'low' | 'medium' | 'high' | 'critical';
/** Risk score (0-1) */
score: number;
/** Individual risk factors */
factors: RiskFactor[];
}
interface RiskFactor {
name: string;
description: string;
weight: number;
mitigations?: string[];
}
Example:
import { createRecommendationEngine } from '@contextgraph/recommendations';
import { DecisionTraceGraph } from '@contextgraph/dtg';
const dtg = new DecisionTraceGraph(storage);
const engine = createRecommendationEngine(dtg, storage);
// Get recommendation for a decision
const result = await engine.recommend({
action: 'delete',
entityType: 'user',
resource: '/api/users/123',
attributes: {
role: 'admin',
department: 'engineering',
},
});
if (result.ok) {
const rec = result.value;
console.log(`Recommendation: ${rec.recommendedAction}`);
console.log(`Confidence: ${(rec.confidence * 100).toFixed(1)}%`);
console.log(`Reasoning: ${rec.reasoning}`);
console.log(`Risk Level: ${rec.risk.level}`);
}
calculateSimilarity
Calculate similarity between two decision contexts.
function calculateSimilarity(
context1: DecisionContext,
context2: DecisionContext,
criteria?: MatchingCriteria
): SimilarityScore
SimilarityScore
interface SimilarityScore {
/** Overall similarity (0-1) */
score: number;
/** Breakdown by dimension */
breakdown: {
actionMatch: number;
entityTypeMatch: number;
resourceMatch: number;
attributeMatch: number;
};
}
Example:
import { calculateSimilarity, DEFAULT_CRITERIA } from '@contextgraph/recommendations';
const context1: DecisionContext = {
action: 'create',
entityType: 'user',
resource: '/api/users',
attributes: { role: 'admin' },
};
const context2: DecisionContext = {
action: 'create',
entityType: 'user',
resource: '/api/users/profile',
attributes: { role: 'admin', level: 5 },
};
const similarity = calculateSimilarity(context1, context2);
console.log(`Overall: ${similarity.score}`);
console.log(`Action: ${similarity.breakdown.actionMatch}`); // 1.0 (exact match)
console.log(`Entity: ${similarity.breakdown.entityTypeMatch}`); // 1.0 (exact match)
console.log(`Resource: ${similarity.breakdown.resourceMatch}`); // ~0.66 (prefix match)
console.log(`Attributes: ${similarity.breakdown.attributeMatch}`); // ~0.5 (partial overlap)
rankBySimilarity
Rank and filter similar decisions.
function rankBySimilarity<T extends { similarity: SimilarityScore; ageMs: number }>(
items: T[],
criteria?: MatchingCriteria
): T[]
Example:
import { rankBySimilarity, DEFAULT_CRITERIA } from '@contextgraph/recommendations';
const decisions = [
{ decisionId: 'a', similarity: { score: 0.7, ... }, ageMs: 1000, outcome: 'approved' },
{ decisionId: 'b', similarity: { score: 0.9, ... }, ageMs: 2000, outcome: 'approved' },
{ decisionId: 'c', similarity: { score: 0.8, ... }, ageMs: 500, outcome: 'rejected' },
];
const ranked = rankBySimilarity(decisions, {
...DEFAULT_CRITERIA,
minSimilarity: 0.5,
maxPrecedentAge: 7 * 24 * 60 * 60 * 1000, // 7 days
maxPrecedents: 5,
});
// Returns sorted by score (highest first), filtered by criteria
Configuration
Matching Criteria
interface MatchingCriteria {
/** Weight for action matching (default: 0.4) */
actionWeight: number;
/** Weight for entity type matching (default: 0.2) */
entityTypeWeight: number;
/** Weight for resource matching (default: 0.2) */
resourceWeight: number;
/** Weight for attribute matching (default: 0.2) */
attributeWeight: number;
/** Minimum similarity to consider (default: 0.5) */
minSimilarity: number;
/** Maximum age of precedents in ms (default: 90 days) */
maxPrecedentAge: number;
/** Maximum number of precedents to return (default: 10) */
maxPrecedents: number;
}
Default Values:
const DEFAULT_CRITERIA: MatchingCriteria = {
actionWeight: 0.4,
entityTypeWeight: 0.2,
resourceWeight: 0.2,
attributeWeight: 0.2,
minSimilarity: 0.5,
maxPrecedentAge: 90 * 24 * 60 * 60 * 1000, // 90 days
maxPrecedents: 10,
};
Risk Patterns
Configure which patterns trigger risk flags:
const engine = createRecommendationEngine(dtg, storage, {
// Actions that increase risk score
highRiskPatterns: [
'delete',
'destroy',
'terminate',
'revoke',
'disable',
'purge',
'wipe',
],
// Patterns requiring escalation
escalationPatterns: [
'admin',
'root',
'sudo',
'override',
'bypass',
'force',
],
// Minimum precedents for confident recommendation
minPrecedents: 5,
});
Recommendation Types
The engine returns one of four recommendation types:
Approve
Returned when:
- ≥70% of similar decisions were approved/executed
- Average similarity ≥60%
- Risk level is low or medium
{
recommendedAction: 'approve',
confidence: 0.85,
reasoning: '85% of similar decisions were approved. Average similarity: 72%.',
}
Reject
Returned when:
- ≥60% of similar decisions were rejected/failed
- Clear pattern of failures
{
recommendedAction: 'reject',
confidence: 0.78,
reasoning: '65% of similar decisions were rejected. Risk factors: High-Risk Action.',
}
Defer
Returned when:
- Insufficient precedents (below minPrecedents threshold)
- Mixed results without clear pattern
- Low confidence in recommendation
{
recommendedAction: 'defer',
confidence: 0.6,
reasoning: 'Insufficient historical precedents (2/5). More context needed.',
}
Escalate
Returned when:
- Critical risk level detected
- High risk level with mixed precedent outcomes
- Escalation patterns detected in action or resource
{
recommendedAction: 'escalate',
confidence: 0.9,
reasoning: 'Critical risk level detected. Manual review by supervisor recommended.',
}
Feedback Loop
Track recommendation accuracy and improve over time:
Submitting Feedback
import type { RecommendationFeedback } from '@contextgraph/recommendations';
const feedback: RecommendationFeedback = {
recommendationId: 'rec_abc123',
followed: true,
actualDecision: 'approved',
outcome: 'success',
comments: 'Worked as expected',
submittedAt: Date.now(),
};
engine.submitFeedback(feedback);
Tracking Statistics
interface RecommendationStats {
/** Total recommendations generated */
totalRecommendations: number;
/** How many recommendations were followed */
recommendationsFollowed: number;
/** Average confidence score */
averageConfidence: number;
/** Accuracy (correct predictions / total with feedback) */
accuracy: number;
/** Distribution of recommendation types */
decisionDistribution: {
approve: number;
reject: number;
defer: number;
escalate: number;
};
}
const stats = engine.getStats();
console.log(`Accuracy: ${(stats.accuracy * 100).toFixed(1)}%`);
console.log(`Average confidence: ${(stats.averageConfidence * 100).toFixed(1)}%`);
Examples
API Request Approval
import { createRecommendationEngine } from '@contextgraph/recommendations';
async function shouldApproveAPIRequest(request: {
method: string;
path: string;
user: { role: string; department: string };
}) {
const engine = createRecommendationEngine(dtg, storage);
const recommendation = await engine.recommend({
action: request.method.toLowerCase(),
resource: request.path,
attributes: {
userRole: request.user.role,
department: request.user.department,
},
});
if (!recommendation.ok) {
return { allowed: false, reason: 'Failed to generate recommendation' };
}
const rec = recommendation.value;
switch (rec.recommendedAction) {
case 'approve':
return { allowed: true, confidence: rec.confidence };
case 'reject':
return { allowed: false, reason: rec.reasoning };
case 'escalate':
return { allowed: false, requiresApproval: true, reason: rec.reasoning };
case 'defer':
default:
return { allowed: false, requiresReview: true, reason: rec.reasoning };
}
}
Workflow Step Recommendation
async function recommendWorkflowAction(
workflowId: string,
step: string,
parameters: Record<string, unknown>
) {
const engine = createRecommendationEngine(dtg, storage, {
minPrecedents: 5,
criteria: {
actionWeight: 0.3,
attributeWeight: 0.4, // Higher weight on parameters
},
});
const result = await engine.recommend({
action: step,
entityType: 'workflow',
resource: `/workflows/${workflowId}`,
attributes: parameters,
});
if (!result.ok) {
return null;
}
// Generate explanation
const explanation = engine.explainRecommendation(result.value.id);
return {
recommendation: result.value,
explanation: explanation.ok ? explanation.value : null,
};
}
Risk-Aware Batch Processing
async function processBatch(items: Array<{ action: string; target: string }>) {
const engine = createRecommendationEngine(dtg, storage);
const results = [];
for (const item of items) {
const rec = await engine.recommend({
action: item.action,
resource: item.target,
attributes: {},
});
if (!rec.ok) {
results.push({ item, status: 'error', error: rec.error });
continue;
}
// Auto-approve low-risk items
if (
rec.value.recommendedAction === 'approve' &&
rec.value.risk.level === 'low' &&
rec.value.confidence >= 0.8
) {
results.push({ item, status: 'auto-approved' });
continue;
}
// Queue high-risk items for manual review
if (rec.value.risk.level === 'high' || rec.value.risk.level === 'critical') {
results.push({ item, status: 'manual-review', recommendation: rec.value });
continue;
}
// Default handling
results.push({ item, status: 'pending', recommendation: rec.value });
}
return results;
}
Custom Similarity Weights
// For permission-related decisions, weight action heavily
const permissionEngine = createRecommendationEngine(dtg, storage, {
criteria: {
actionWeight: 0.5, // Most important
entityTypeWeight: 0.3, // Type of resource matters
resourceWeight: 0.1, // Specific path less important
attributeWeight: 0.1, // Attributes less important
},
});
// For data operations, weight resource path heavily
const dataEngine = createRecommendationEngine(dtg, storage, {
criteria: {
actionWeight: 0.2,
entityTypeWeight: 0.1,
resourceWeight: 0.5, // Path is critical
attributeWeight: 0.2,
},
});
Feedback Integration
class RecommendationService {
private engine: RecommendationEngine;
constructor(dtg: DecisionTraceGraph, storage: StorageInterface) {
this.engine = createRecommendationEngine(dtg, storage);
}
async getRecommendation(context: DecisionContext) {
return this.engine.recommend(context);
}
async recordDecisionOutcome(
recommendationId: string,
actualDecision: 'approved' | 'rejected',
wasSuccessful: boolean
) {
const recommendation = this.engine.explainRecommendation(recommendationId);
if (!recommendation.ok) {
return;
}
// Determine if recommendation was followed
const wasFollowed =
(actualDecision === 'approved' && recommendation.value.includes('approve')) ||
(actualDecision === 'rejected' && recommendation.value.includes('reject'));
this.engine.submitFeedback({
recommendationId,
followed: wasFollowed,
actualDecision,
outcome: wasSuccessful ? 'success' : 'failure',
submittedAt: Date.now(),
});
}
getAccuracyReport() {
const stats = this.engine.getStats();
return {
accuracy: `${(stats.accuracy * 100).toFixed(1)}%`,
totalRecommendations: stats.totalRecommendations,
followRate: `${((stats.recommendationsFollowed / stats.totalRecommendations) * 100).toFixed(1)}%`,
distribution: stats.decisionDistribution,
};
}
}
Best Practices
1. Populate Decision History
Recommendations require historical data. Ensure you're recording decisions:
// Record decisions for recommendation training
await dtg.recordDecision({
type: 'workflow_step',
title: 'API Request: DELETE /users/123',
proposedBy: agentId,
riskLevel: 'medium',
});
// Track outcome
await dtg.executeDecision(decisionId, { success: true });
2. Tune Weights for Your Domain
Different domains need different weight configurations:
// Financial domain - conservative, action-focused
{ actionWeight: 0.5, entityTypeWeight: 0.2, resourceWeight: 0.2, attributeWeight: 0.1 }
// Content management - resource-focused
{ actionWeight: 0.2, entityTypeWeight: 0.2, resourceWeight: 0.4, attributeWeight: 0.2 }
// User management - attribute-focused
{ actionWeight: 0.3, entityTypeWeight: 0.2, resourceWeight: 0.1, attributeWeight: 0.4 }
3. Use Appropriate Confidence Thresholds
// High-stakes decisions
if (rec.confidence < 0.85) {
// Require manual review
}
// Low-stakes decisions
if (rec.confidence >= 0.7 && rec.risk.level === 'low') {
// Auto-approve
}
4. Monitor and Improve
// Regular accuracy checks
setInterval(() => {
const stats = engine.getStats();
if (stats.accuracy < 0.7) {
console.warn('Recommendation accuracy dropping');
// Consider retraining or adjusting criteria
}
}, 24 * 60 * 60 * 1000); // Daily
5. Handle Edge Cases
async function getRecommendationSafely(context: DecisionContext) {
const result = await engine.recommend(context);
if (!result.ok) {
// Fallback to conservative default
return {
recommendedAction: 'defer' as const,
confidence: 0,
reasoning: 'Unable to generate recommendation',
risk: { level: 'medium' as const, score: 0.5, factors: [] },
};
}
// Never auto-approve with no precedents
if (result.value.precedents.length === 0) {
return {
...result.value,
recommendedAction: 'defer' as const,
reasoning: 'No historical precedents available',
};
}
return result.value;
}
6. Attribute Standardization
Ensure consistent attribute naming for better matching:
// Good - consistent naming
{ action: 'create', attributes: { userRole: 'admin', department: 'engineering' } }
{ action: 'create', attributes: { userRole: 'user', department: 'marketing' } }
// Bad - inconsistent naming
{ action: 'create', attributes: { role: 'admin', dept: 'engineering' } }
{ action: 'create', attributes: { userRole: 'user', department: 'marketing' } }
@contextgraph/telemetry
OpenTelemetry-compatible tracing, metrics, and structured logging.
Installation
pnpm add @contextgraph/telemetry
Overview
Observability features:
- Distributed tracing
- Metrics collection
- Structured logging
- Multiple exporters
Tracing
import { Tracer, Span } from '@contextgraph/telemetry';
const tracer = new Tracer('contextgraph-service');
const span = tracer.startSpan('process-request');
span.setAttribute('request.id', requestId);
try {
await processRequest();
span.setStatus({ code: 'OK' });
} catch (error) {
span.setStatus({ code: 'ERROR', message: error.message });
span.recordException(error);
} finally {
span.end();
}
Nested Spans
const parentSpan = tracer.startSpan('workflow');
const childSpan = tracer.startSpan('step-1', { parent: parentSpan });
await doStep1();
childSpan.end();
const childSpan2 = tracer.startSpan('step-2', { parent: parentSpan });
await doStep2();
childSpan2.end();
parentSpan.end();
Metrics
import { Meter, Counter, Histogram, Gauge } from '@contextgraph/telemetry';
const meter = new Meter('contextgraph');
// Counter
const requestCounter = meter.createCounter('requests_total');
requestCounter.add(1, { method: 'GET', path: '/api/entities' });
// Histogram
const latencyHistogram = meter.createHistogram('request_duration_ms');
latencyHistogram.record(45, { method: 'GET' });
// Gauge
const activeConnections = meter.createGauge('active_connections');
activeConnections.set(42);
Pre-defined Metrics
| Metric | Type | Description |
|---|---|---|
cg.entities.total | Gauge | Total entities |
cg.claims.total | Gauge | Total claims |
cg.decisions.pending | Gauge | Pending decisions |
cg.executions.total | Counter | Total executions |
cg.policy.evaluations | Counter | Policy evaluations |
Logging
import { Logger } from '@contextgraph/telemetry';
const logger = new Logger('my-service');
logger.info('Request processed', {
requestId: '123',
duration: 45,
});
logger.warn('Rate limit approaching', {
current: 95,
limit: 100,
});
logger.error('Failed to process', {
error: error.message,
stack: error.stack,
});
Log Levels
| Level | Use Case |
|---|---|
debug | Detailed debugging |
info | General information |
warn | Warning conditions |
error | Error conditions |
Span Correlation
// Logs automatically include span context
logger.info('Processing', { spanId: span.context.spanId });
Exporters
Console Exporter
import { ConsoleExporter } from '@contextgraph/telemetry';
const exporter = new ConsoleExporter();
tracer.addExporter(exporter);
Memory Exporter
import { MemoryExporter } from '@contextgraph/telemetry';
const exporter = new MemoryExporter();
tracer.addExporter(exporter);
// Get collected spans
const spans = exporter.getSpans();
OTLP Exporter
import { OTLPExporter } from '@contextgraph/telemetry';
const exporter = new OTLPExporter({
endpoint: 'http://collector:4318',
headers: { 'Authorization': 'Bearer token' },
});
Multi Exporter
import { MultiExporter } from '@contextgraph/telemetry';
const multiExporter = new MultiExporter([
new ConsoleExporter(),
new OTLPExporter({ endpoint: '...' }),
]);
@contextgraph/sdk
The unified high-level SDK providing a single entry point for all ContextGraph OS operations.
Installation
pnpm add @contextgraph/sdk
Quick Start
import { ContextGraph } from '@contextgraph/sdk';
// Create a client
const result = await ContextGraph.create();
if (!result.ok) throw result.error;
const client = result.value;
// Create entities, add claims, execute actions...
Client Creation
Default (In-Memory)
const client = await ContextGraph.create();
With SQLite
const client = await ContextGraph.create({
storage: {
type: 'sqlite',
path: './contextgraph.db'
}
});
With Custom Config
const client = await ContextGraph.create({
storage: { type: 'memory' },
provenance: { verifyOnRead: true },
policy: { enforceOnExecute: true },
});
Entity Operations
Create Entity
const entity = await client.createEntity({
type: 'person',
name: 'Alice',
properties: {
department: 'Engineering',
level: 'senior',
},
});
Get Entity
const entity = await client.getEntity(entityId);
Update Entity
await client.updateEntity(entityId, {
properties: {
level: 'principal',
},
});
List Entities
const entities = await client.listEntities({
type: 'person',
limit: 50,
});
Claim Operations
Add Claim
import { createScope, createConfidence } from '@contextgraph/sdk';
await client.addClaim({
subjectId: entityId,
predicate: 'has_skill',
value: 'TypeScript',
context: {
scope: createScope('professional'),
confidence: createConfidence(0.95),
},
});
Get Claims
const claims = await client.getClaims(entityId);
// With filters
const filteredClaims = await client.getClaims(entityId, {
predicate: 'has_skill',
asOf: timestamp,
minConfidence: 0.8,
});
Revoke Claim
await client.revokeClaim(claimId, 'Superseded by new information');
Agent Operations
Create Agent
const agent = await client.createAgent({
name: 'data-processor',
description: 'Processes incoming data files',
capabilities: ['read', 'transform', 'write'],
});
Get Agent
const agent = await client.getAgent(agentId);
// Or by name
const agent = await client.getAgentByName('data-processor');
List Agents
const agents = await client.listAgents({ status: 'active' });
Action Execution
Register Handler
client.registerHandler('transform', 'csv', async (action, context) => {
// Process the action
const data = await readCSV(action.resourceId);
const transformed = transformData(data, action.parameters);
return ok({ transformed: true, rowCount: data.length });
});
Execute Action
const result = await client.execute({
agentId: agent.data.id,
action: 'transform',
resourceType: 'csv',
resourceId: 'data/input.csv',
parameters: {
outputFormat: 'json',
},
});
Decision Management
Record Decision
const decision = await client.recordDecision({
type: 'deployment',
title: 'Deploy v2.0.0 to production',
proposedBy: agentId,
riskLevel: 'high',
});
Approve/Reject Decision
await client.approveDecision(decisionId, approverId, 'Approved after review');
await client.rejectDecision(decisionId, reviewerId, 'Needs more testing');
Get Pending Decisions
const pending = await client.getPendingDecisions();
Policy Management
Create Policy
await client.createPolicy({
name: 'Restrict PII Access',
version: '1.0.0',
effect: 'deny',
subjects: ['*'],
actions: ['read', 'export'],
resources: ['pii/*'],
priority: 100,
});
List Policies
const policies = await client.listPolicies({ effect: 'deny' });
Provenance
Verify Chain
const verification = await client.verifyProvenance();
console.log(`Valid: ${verification.value.valid}`);
console.log(`Entries: ${verification.value.entriesVerified}`);
Query Provenance
const entries = await client.queryProvenance({
subjectId: entityId,
limit: 100,
});
Context Assembly
const context = await client.assembleContext(entityId, {
asOf: timestamp,
jurisdiction: 'EU',
scope: 'work',
minConfidence: 0.8,
depth: 2,
includeProvenance: true,
});
Audit Trail
const audit = await client.getAuditTrail({
entityId: entityId,
limit: 100,
from: startTime,
to: endTime,
});
Statistics
const stats = await client.getStats();
console.log(`Entities: ${stats.value.entities}`);
console.log(`Claims: ${stats.value.claims}`);
console.log(`Agents: ${stats.value.agents}`);
console.log(`Decisions: ${stats.value.decisions}`);
Import/Export
Export
// Export to JSON
const jsonData = await client.exportToJSON();
const jsonString = await client.exportToJSONString({ prettyPrint: true });
// Export to CSV
const entitiesCSV = await client.exportEntitiesToCSV();
const claimsCSV = await client.exportClaimsToCSV();
Import
// Import from JSON
await client.importFromJSON(data, {
dryRun: true,
merge: true,
onConflict: 'skip',
});
// Import from CSV
await client.importEntitiesFromCSV(csvString);
await client.importClaimsFromCSV(csvString);
Events
// Subscribe to events
client.on('entity:created', (event) => {
console.log('Entity created:', event.data);
});
client.on('claim:added', (event) => {
console.log('Claim added:', event.data);
});
client.on('decision:approved', (event) => {
console.log('Decision approved:', event.data);
});
// Unsubscribe
client.off('entity:created', handler);
Event Types
| Event | Description |
|---|---|
entity:created | New entity created |
entity:updated | Entity updated |
claim:added | Claim added |
claim:revoked | Claim revoked |
decision:proposed | Decision recorded |
decision:approved | Decision approved |
decision:rejected | Decision rejected |
execution:started | Action execution started |
execution:completed | Action execution completed |
policy:created | Policy created |
Helper Functions
import {
createScope,
createJurisdiction,
createConfidence,
createTimestamp,
ok,
err,
} from '@contextgraph/sdk';
Type Exports
export type {
EntityId,
ClaimId,
AgentId,
DecisionId,
PolicyId,
Entity,
Claim,
Agent,
Decision,
Policy,
Result,
};
@contextgraph/api
REST API server with authentication and rate limiting.
Installation
pnpm add @contextgraph/api
Starting the Server
# Default configuration
npx contextgraph-api
# Custom port and API key
PORT=8080 API_KEY=your-secret npx contextgraph-api
API Endpoints
Health
GET /api/v1/health
Statistics
GET /api/v1/stats
Entities
GET /api/v1/entities # List entities
POST /api/v1/entities # Create entity
GET /api/v1/entities/:id # Get entity
PUT /api/v1/entities/:id # Update entity
DELETE /api/v1/entities/:id # Delete entity
GET /api/v1/entities/:id/claims # Get entity claims
POST /api/v1/entities/:id/claims # Add claim
Agents
GET /api/v1/agents # List agents
POST /api/v1/agents # Create agent
GET /api/v1/agents/:id # Get agent
POST /api/v1/agents/:id/execute # Execute action
Decisions
GET /api/v1/decisions # List decisions
POST /api/v1/decisions # Record decision
GET /api/v1/decisions/:id # Get decision
POST /api/v1/decisions/:id/approve # Approve
POST /api/v1/decisions/:id/reject # Reject
Policies
GET /api/v1/policies # List policies
POST /api/v1/policies # Create policy
Provenance
GET /api/v1/provenance # Query provenance
POST /api/v1/provenance/verify # Verify chain
Audit
GET /api/v1/audit # Query audit trail
Authentication
Set API_KEY environment variable:
API_KEY=your-secret-key npx contextgraph-api
Include in requests:
curl -H "X-API-Key: your-secret-key" http://localhost:3000/api/v1/entities
Example Requests
Create Entity
curl -X POST http://localhost:3000/api/v1/entities \
-H "Content-Type: application/json" \
-H "X-API-Key: your-key" \
-d '{
"type": "person",
"name": "Alice",
"properties": {"department": "Engineering"}
}'
Add Claim
curl -X POST http://localhost:3000/api/v1/entities/ent_xxx/claims \
-H "Content-Type: application/json" \
-d '{"predicate": "has_skill", "value": "TypeScript"}'
Verify Provenance
curl -X POST http://localhost:3000/api/v1/provenance/verify
Rate Limiting
Default: 100 requests per minute per IP.
Configure via environment:
RATE_LIMIT=200 RATE_WINDOW=60000 npx contextgraph-api
CORS
Enable CORS for web clients:
CORS_ORIGIN=https://myapp.com npx contextgraph-api
@contextgraph/cli
CLI tools and interactive REPL.
Installation
pnpm add -g @contextgraph/cli
Commands
stats
Show system statistics:
npx contextgraph stats
entities
List entities:
npx contextgraph entities [type] --limit 10
entity
Inspect an entity:
npx contextgraph entity <id> --with-claims
agents
List agents:
npx contextgraph agents
agent
Inspect an agent:
npx contextgraph agent <id|name>
decisions
List pending decisions:
npx contextgraph decisions
policies
List policies:
npx contextgraph policies
audit
Show audit trail:
npx contextgraph audit --json
provenance
Query provenance:
npx contextgraph provenance --subject <id>
verify
Verify provenance chain:
npx contextgraph verify
context
Assemble context:
npx contextgraph context <entity-id>
export
Export data:
npx contextgraph export --format json --output backup.json
npx contextgraph export --format csv --type entities --output entities.csv
import
Import data:
npx contextgraph import backup.json
npx contextgraph import entities.csv --format csv --type entities --dry-run
repl
Start interactive REPL:
npx contextgraph repl
REPL
The interactive REPL provides a shell for exploration:
ContextGraph OS REPL v0.1.0
Type 'help' for available commands, 'exit' to quit.
contextgraph> stats
╔═══════════════════════════════════════╗
║ System Statistics ║
╠═══════════════════════════════════════╣
║ Entities: 42 ║
║ Claims: 156 ║
║ Agents: 3 ║
╚═══════════════════════════════════════╝
contextgraph> entities person
┌──────────────────┬─────────┬──────────────┐
│ ID │ Type │ Name │
├──────────────────┼─────────┼──────────────┤
│ ent_abc123... │ person │ Alice │
│ ent_def456... │ person │ Bob │
└──────────────────┴─────────┴──────────────┘
contextgraph> json
JSON output mode: ON
contextgraph> exit
Goodbye!
Global Options
| Option | Description |
|---|---|
--json | Output as JSON |
--help | Show help |
--version | Show version |
@contextgraph/webhooks
Webhook management with delivery queue and retry logic.
Installation
pnpm add @contextgraph/webhooks
Overview
Send HTTP callbacks for system events:
- Webhook registration
- Event filtering
- Delivery queue
- Retry with backoff
Registering Webhooks
import { WebhookManager } from '@contextgraph/webhooks';
const webhooks = new WebhookManager(storage);
await webhooks.initialize();
await webhooks.register({
url: 'https://example.com/webhook',
events: ['entity:created', 'decision:approved'],
secret: 'webhook-secret',
headers: {
'X-Custom-Header': 'value',
},
});
Supported Events
| Event | Description |
|---|---|
entity:created | New entity created |
entity:updated | Entity updated |
claim:added | Claim added |
claim:revoked | Claim revoked |
decision:proposed | Decision recorded |
decision:approved | Decision approved |
decision:rejected | Decision rejected |
execution:completed | Action executed |
policy:created | Policy created |
Webhook Payload
{
"event": "entity:created",
"timestamp": "2024-01-15T10:30:00.000Z",
"data": {
"id": "ent_abc123",
"type": "person",
"name": "Alice"
}
}
Signature Verification
Webhooks include HMAC signature:
X-Webhook-Signature: sha256=abc123...
Verify in your handler:
import crypto from 'crypto';
function verifySignature(payload: string, signature: string, secret: string) {
const expected = crypto
.createHmac('sha256', secret)
.update(payload)
.digest('hex');
return `sha256=${expected}` === signature;
}
Retry Policy
Failed deliveries are retried:
| Attempt | Delay |
|---|---|
| 1 | Immediate |
| 2 | 1 minute |
| 3 | 5 minutes |
| 4 | 30 minutes |
| 5 | 2 hours |
Managing Webhooks
// List webhooks
const hooks = await webhooks.list();
// Disable webhook
await webhooks.disable(webhookId);
// Enable webhook
await webhooks.enable(webhookId);
// Delete webhook
await webhooks.delete(webhookId);
// Get delivery history
const history = await webhooks.getDeliveries(webhookId, { limit: 50 });
Building an Auditable Agent
This tutorial walks you through building a fully auditable AI agent using ContextGraph OS.
What You'll Build
An agent that:
- Processes data with full audit trails
- Records decisions for review
- Respects policies
- Has verifiable provenance
Prerequisites
pnpm add @contextgraph/sdk
Step 1: Create the Client
import { ContextGraph, ok } from '@contextgraph/sdk';
async function main() {
const result = await ContextGraph.create();
if (!result.ok) throw result.error;
const client = result.value;
Step 2: Create an Agent
const agentResult = await client.createAgent({
name: 'data-processor',
description: 'Processes incoming data files with full audit trail',
capabilities: ['read', 'transform', 'write'],
});
if (!agentResult.ok) throw agentResult.error;
const agent = agentResult.value;
console.log(`Created agent: ${agent.data.name} (${agent.data.id})`);
Step 3: Register Handlers
// Read handler
client.registerHandler('read', 'csv', async (action) => {
console.log(`[Handler] Reading: ${action.resourceId}`);
// Simulate reading data
const data = [
{ name: 'Alice', department: 'Engineering' },
{ name: 'Bob', department: 'Product' },
];
return ok({ data, rowCount: data.length });
});
// Transform handler
client.registerHandler('transform', 'data', async (action) => {
console.log(`[Handler] Transforming data`);
const input = action.parameters?.data as any[];
const transformed = input.map(row => ({
...row,
processedAt: new Date().toISOString(),
}));
return ok({ data: transformed, rowCount: transformed.length });
});
// Write handler
client.registerHandler('write', 'json', async (action) => {
console.log(`[Handler] Writing to: ${action.resourceId}`);
// Simulate writing
return ok({ written: true, path: action.resourceId });
});
Step 4: Record a Decision
Before processing, record the decision:
const decisionResult = await client.recordDecision({
type: 'data_processing',
title: 'Process employee data batch',
description: 'Transform CSV to JSON with timestamp enrichment',
proposedBy: agent.data.id,
riskLevel: 'low',
});
if (!decisionResult.ok) throw decisionResult.error;
const decision = decisionResult.value;
console.log(`Decision recorded: ${decision.data.title}`);
Step 5: Execute Workflow
// Step 1: Read
const readResult = await client.execute({
agentId: agent.data.id,
action: 'read',
resourceType: 'csv',
resourceId: 'data/employees.csv',
});
if (!readResult.ok) {
console.error('Read failed:', readResult.error);
return;
}
console.log(`Read ${readResult.value.output.rowCount} rows`);
// Step 2: Transform
const transformResult = await client.execute({
agentId: agent.data.id,
action: 'transform',
resourceType: 'data',
resourceId: 'memory',
parameters: { data: readResult.value.output.data },
});
if (!transformResult.ok) {
console.error('Transform failed:', transformResult.error);
return;
}
console.log(`Transformed ${transformResult.value.output.rowCount} rows`);
// Step 3: Write
const writeResult = await client.execute({
agentId: agent.data.id,
action: 'write',
resourceType: 'json',
resourceId: 'output/employees.json',
parameters: { data: transformResult.value.output.data },
});
if (!writeResult.ok) {
console.error('Write failed:', writeResult.error);
return;
}
console.log(`Wrote output`);
Step 6: Complete the Decision
await client.approveDecision(
decision.data.id,
agent.data.id,
'Workflow completed successfully'
);
console.log('Decision completed');
Step 7: Verify Audit Trail
// Verify provenance
const verification = await client.verifyProvenance();
console.log(`Provenance chain valid: ${verification.value.valid}`);
// Get audit trail
const audit = await client.getAuditTrail({ limit: 10 });
console.log('\nAudit Trail:');
for (const entry of audit.value) {
console.log(` ${entry.timestamp}: ${entry.action} ${entry.resourceType}`);
}
// Get statistics
const stats = await client.getStats();
console.log('\nStatistics:');
console.log(` Agents: ${stats.value.agents}`);
console.log(` Decisions: ${stats.value.decisions}`);
console.log(` Provenance entries: ${stats.value.provenanceEntries}`);
}
main().catch(console.error);
Full Code
See the complete example at packages/demos/src/agent-workflow.ts.
Key Takeaways
- Record decisions before acting - Creates audit trail
- Use handlers for actions - Standardized execution
- Verify provenance - Ensure data integrity
- Check audit trail - Review what happened
Next Steps
Policy-Based Access Control
Learn to implement fine-grained access control using ContextGraph policies.
What You'll Learn
- Creating allow and deny policies
- Using conditions
- Policy evaluation order
- Handling policy exceptions
Setup
import { ContextGraph } from '@contextgraph/sdk';
const result = await ContextGraph.create();
const client = result.value;
Creating Basic Policies
Allow Policy
await client.createPolicy({
name: 'Read Access for Analysts',
version: '1.0.0',
description: 'Allow analysts to read reports',
effect: 'allow',
subjects: ['role:analyst'],
actions: ['read'],
resources: ['reports/*'],
priority: 50,
});
Deny Policy
await client.createPolicy({
name: 'Block Confidential Reports',
version: '1.0.0',
description: 'Deny access to confidential reports',
effect: 'deny',
subjects: ['*'],
actions: ['read', 'export'],
resources: ['reports/confidential/*'],
priority: 100, // Higher priority = evaluated first
});
Using Conditions
Time-Based Access
await client.createPolicy({
name: 'Business Hours Only',
version: '1.0.0',
effect: 'allow',
subjects: ['role:contractor'],
actions: ['*'],
resources: ['*'],
conditions: [
{
field: 'time.hour',
operator: 'between',
value: [9, 17],
},
{
field: 'time.dayOfWeek',
operator: 'in',
value: ['Mon', 'Tue', 'Wed', 'Thu', 'Fri'],
},
],
priority: 40,
});
Risk-Based Access
await client.createPolicy({
name: 'High Risk Requires Approval',
version: '1.0.0',
effect: 'deny',
subjects: ['*'],
actions: ['execute'],
resources: ['*'],
conditions: [
{
field: 'risk.level',
operator: 'in',
value: ['high', 'critical'],
},
{
field: 'approval.status',
operator: 'not_equals',
value: 'approved',
},
],
priority: 90,
});
Testing Policies
Use the policy simulator:
import { PolicySimulator } from '@contextgraph/policy';
const simulator = new PolicySimulator(policyLedger, storage);
const result = await simulator.simulate({
subject: 'agent:data-processor',
action: 'read',
resource: 'reports/confidential/q4-2024',
context: {
time: new Date('2024-01-15T14:00:00Z'),
},
});
console.log(`Effect: ${result.effect}`); // 'deny'
console.log(`Matched ${result.matchedPolicies.length} policies`);
Priority System
Priority 100: Security policies (deny dangerous actions)
Priority 90: Risk policies (high-risk requires approval)
Priority 50: Business rules (role-based access)
Priority 10: Default policies (fallback rules)
When policies conflict at the same priority, deny wins.
Handling Exceptions
import { ExceptionManager } from '@contextgraph/exceptions';
const exceptions = new ExceptionManager(dtg, policyLedger, storage);
// Request exception
const request = await exceptions.request({
policyId: policyId,
reason: 'Audit requires temporary access',
requestedBy: agentId,
duration: { hours: 4 },
scope: {
action: 'read',
resources: ['reports/confidential/audit-2024'],
},
});
// Approve exception
await exceptions.approve(request.id, approverId, 'Approved for audit');
Complete Example
async function setupAccessControl(client: ContextGraph) {
// 1. Base read access
await client.createPolicy({
name: 'Base Read Access',
version: '1.0.0',
effect: 'allow',
subjects: ['role:employee'],
actions: ['read'],
resources: ['public/*', 'team/*'],
priority: 30,
});
// 2. Analyst access
await client.createPolicy({
name: 'Analyst Access',
version: '1.0.0',
effect: 'allow',
subjects: ['role:analyst'],
actions: ['read', 'export'],
resources: ['reports/*', 'data/*'],
priority: 50,
});
// 3. Block PII
await client.createPolicy({
name: 'Block PII',
version: '1.0.0',
effect: 'deny',
subjects: ['*'],
actions: ['*'],
resources: ['pii/*', '*/personal/*'],
conditions: [
{
field: 'agent.clearance',
operator: 'not_equals',
value: 'pii-authorized',
},
],
priority: 100,
});
// 4. Admin override
await client.createPolicy({
name: 'Admin Full Access',
version: '1.0.0',
effect: 'allow',
subjects: ['role:admin'],
actions: ['*'],
resources: ['*'],
priority: 110, // Higher than deny
});
}
Best Practices
- Start with deny-all - Add explicit allows
- Use high priority for security - Security policies first
- Document conditions - Clear descriptions
- Test with simulator - Before deploying
- Monitor evaluations - Track denied requests
Next Steps
Visualizing Decision Trees
Learn to visualize your decision history and provenance chains.
What You'll Learn
- Generate decision tree diagrams
- Create provenance visualizations
- Export to different formats
- Embed in documentation
Setup
import { ContextGraph } from '@contextgraph/sdk';
import {
visualizeDecisions,
visualizeProvenance,
visualizeEntities,
} from '@contextgraph/viz';
const client = await ContextGraph.create().then(r => r.value);
Visualizing Decisions
Mermaid Diagram
const dtg = client.getDecisionTraceGraph();
const result = await visualizeDecisions(dtg, {
format: 'mermaid',
showTimestamps: true,
colorScheme: 'default',
});
console.log(result.value);
Output:
graph TB
dec_001["Deploy v2.0.0<br/>2024-01-15"]
dec_002["Rollback v2.0.0<br/>2024-01-16"]
dec_001 -->|triggered| dec_002
classDef approved fill:#90EE90
classDef rejected fill:#FFB6C1
class dec_001 approved
class dec_002 approved
DOT Format
const dotResult = await visualizeDecisions(dtg, {
format: 'dot',
showTimestamps: true,
});
// Save to file and render with Graphviz
fs.writeFileSync('decisions.dot', dotResult.value);
// dot -Tpng decisions.dot -o decisions.png
SVG (Standalone)
const svgResult = await visualizeDecisions(dtg, {
format: 'svg',
colorScheme: 'default',
});
fs.writeFileSync('decisions.svg', svgResult.value);
Visualizing Provenance
Chain Diagram
const ledger = client.getProvenanceLedger();
const result = await visualizeProvenance(ledger, {
format: 'mermaid',
showTimestamps: true,
maxDepth: 20,
});
console.log(result.value);
Output:
graph TB
prov_001["claim_created<br/>2024-01-15 10:30"]
prov_002["entity_updated<br/>2024-01-15 10:31"]
prov_003["decision_recorded<br/>2024-01-15 10:32"]
prov_001 -->|chain| prov_002
prov_002 -->|chain| prov_003
classDef claim fill:#4a90d9
classDef entity fill:#50c878
classDef decision fill:#ffd700
Visualizing Entities
Entity Relationship Graph
const ckg = client.getCKG();
const result = await visualizeEntities(ckg, 'person', {
format: 'd3',
showLabels: true,
});
// Use with D3.js force-directed graph
const data = JSON.parse(result.value);
Embedding in Documentation
GitHub README
## Decision History
```mermaid
graph TB
A[Create User] -->|approved| B[Add Permissions]
B -->|approved| C[Enable Access]
### HTML Reports
```typescript
async function generateReport() {
const decisions = await visualizeDecisions(dtg, { format: 'svg' });
const provenance = await visualizeProvenance(ledger, { format: 'svg' });
return `
<!DOCTYPE html>
<html>
<head><title>Audit Report</title></head>
<body>
<h1>Decision History</h1>
${decisions.value}
<h1>Provenance Chain</h1>
${provenance.value}
</body>
</html>
`;
}
Interactive Dashboard
React Integration
import { visualizeGraph } from '@contextgraph/viz';
import ForceGraph2D from 'react-force-graph-2d';
function DecisionGraph({ dtg }) {
const [data, setData] = useState(null);
useEffect(() => {
async function loadGraph() {
const graphData = await buildDecisionGraph(dtg);
const result = visualizeGraph(graphData, { format: 'd3' });
setData(JSON.parse(result.value));
}
loadGraph();
}, [dtg]);
if (!data) return <div>Loading...</div>;
return (
<ForceGraph2D
graphData={data}
nodeLabel="label"
linkLabel="label"
nodeColor={node => getNodeColor(node.type)}
/>
);
}
Color Schemes
| Scheme | Use Case |
|---|---|
default | Standard colors |
dark | Dark backgrounds |
light | Light/pastel |
colorblind | Accessible |
await visualizeDecisions(dtg, {
format: 'mermaid',
colorScheme: 'colorblind',
});
Next Steps
Temporal Queries
Master point-in-time queries and temporal data management.
What You'll Learn
- Query data at any point in time
- Handle temporal validity periods
- Track changes over time
- Build temporal reports
Setup
import {
ContextGraph,
createTimestamp,
createScope,
createConfidence,
} from '@contextgraph/sdk';
const client = await ContextGraph.create().then(r => r.value);
Creating Temporal Data
Claims with Validity Periods
// Current position
await client.addClaim({
subjectId: aliceId,
predicate: 'position',
value: 'Senior Engineer',
context: {
validFrom: createTimestamp('2024-01-01'),
validUntil: null, // Still valid
},
});
// Previous position
await client.addClaim({
subjectId: aliceId,
predicate: 'position',
value: 'Engineer',
context: {
validFrom: createTimestamp('2022-01-01'),
validUntil: createTimestamp('2023-12-31'),
},
});
Point-in-Time Queries
Query Current State
const currentClaims = await client.getClaims(aliceId);
// Returns: 'Senior Engineer'
Query Historical State
const march2023Claims = await client.getClaims(aliceId, {
asOf: createTimestamp('2023-03-15'),
predicate: 'position',
});
// Returns: 'Engineer'
Query Future State
// Scheduled change
await client.addClaim({
subjectId: aliceId,
predicate: 'position',
value: 'Principal Engineer',
context: {
validFrom: createTimestamp('2025-01-01'),
validUntil: null,
},
});
const futureState = await client.getClaims(aliceId, {
asOf: createTimestamp('2025-06-01'),
predicate: 'position',
});
// Returns: 'Principal Engineer'
Time Range Queries
Claims Valid During Period
const q2_2024_claims = await ckg.query({
subjectId: aliceId,
validDuring: {
from: createTimestamp('2024-04-01'),
to: createTimestamp('2024-06-30'),
},
});
All Historical Values
const allPositions = await ckg.query({
subjectId: aliceId,
predicate: 'position',
includeExpired: true,
});
for (const claim of allPositions) {
console.log(`${claim.data.value}: ${claim.data.validFrom} - ${claim.data.validUntil}`);
}
// Output:
// Engineer: 2022-01-01 - 2023-12-31
// Senior Engineer: 2024-01-01 - null
// Principal Engineer: 2025-01-01 - null
Tracking Changes
Change History
async function getChangeHistory(entityId: EntityId, predicate: string) {
const claims = await ckg.query({
subjectId: entityId,
predicate,
includeExpired: true,
sortBy: 'validFrom',
});
return claims.map(c => ({
value: c.data.value,
from: c.data.validFrom,
to: c.data.validUntil,
status: c.data.status,
}));
}
const history = await getChangeHistory(aliceId, 'position');
When Did Value Change?
async function whenDidValueChange(
entityId: EntityId,
predicate: string,
targetValue: unknown
) {
const claims = await ckg.query({
subjectId: entityId,
predicate,
includeExpired: true,
});
const match = claims.find(c => c.data.value === targetValue);
return match?.data.validFrom;
}
const promotionDate = await whenDidValueChange(
aliceId,
'position',
'Senior Engineer'
);
Temporal Reports
Snapshot Report
async function generateSnapshotReport(asOf: Timestamp) {
const entities = await ckg.findEntitiesByType('employee', { limit: 1000 });
const report = [];
for (const entity of entities.value) {
const claims = await client.getClaims(entity.data.id, { asOf });
report.push({
id: entity.data.id,
name: entity.data.name,
claims: claims.value.map(c => ({
predicate: c.data.predicate,
value: c.data.value,
})),
});
}
return report;
}
// Snapshot from last year
const lastYearReport = await generateSnapshotReport(
createTimestamp('2023-12-31')
);
Change Report
async function generateChangeReport(from: Timestamp, to: Timestamp) {
const provenance = await ledger.query({
type: ['claim_created', 'claim_revoked'],
from,
to,
});
return provenance.value.map(entry => ({
type: entry.data.type,
timestamp: entry.data.timestamp,
subject: entry.data.subjectId,
data: entry.data.data,
}));
}
Best Practices
- Always specify validity - Don't rely on defaults
- Use appropriate granularity - Day vs. millisecond precision
- Handle null validUntil - Means "currently valid"
- Index temporal fields - For query performance
- Audit temporal changes - Track who changed what when
Next Steps
Type Definitions
Core TypeScript types used throughout ContextGraph OS.
Branded Types
// Unique identifiers with compile-time safety
type EntityId = string & { readonly __brand: 'EntityId' };
type ClaimId = string & { readonly __brand: 'ClaimId' };
type AgentId = string & { readonly __brand: 'AgentId' };
type DecisionId = string & { readonly __brand: 'DecisionId' };
type PolicyId = string & { readonly __brand: 'PolicyId' };
type ProvenanceId = string & { readonly __brand: 'ProvenanceId' };
type Timestamp = string & { readonly __brand: 'Timestamp' };
type Scope = string & { readonly __brand: 'Scope' };
type Jurisdiction = string & { readonly __brand: 'Jurisdiction' };
type Confidence = number & { readonly __brand: 'Confidence' };
Result Type
type Result<T, E = Error> =
| { ok: true; value: T }
| { ok: false; error: E };
Entity Types
interface Entity {
id: EntityId;
type: string;
name: string;
properties: Record<string, unknown>;
createdAt: Timestamp;
updatedAt: Timestamp;
}
interface CreateEntityInput {
type: string;
name: string;
properties?: Record<string, unknown>;
}
Claim Types
interface Claim {
id: ClaimId;
subjectId: EntityId;
predicate: string;
objectValue?: unknown;
objectId?: EntityId;
context: ClaimContext;
status: 'active' | 'revoked';
createdAt: Timestamp;
revokedAt?: Timestamp;
revokedReason?: string;
}
interface ClaimContext {
scope?: Scope;
jurisdiction?: Jurisdiction;
confidence?: Confidence;
validFrom?: Timestamp;
validUntil?: Timestamp | null;
}
interface AddClaimInput {
subjectId: EntityId;
predicate: string;
value?: unknown;
objectId?: EntityId;
context?: Partial<ClaimContext>;
}
Agent Types
interface Agent {
id: AgentId;
name: string;
description?: string;
status: AgentStatus;
capabilities: string[];
metadata?: Record<string, unknown>;
parentId?: AgentId;
createdAt: Timestamp;
updatedAt: Timestamp;
}
type AgentStatus = 'active' | 'suspended' | 'revoked';
interface CreateAgentInput {
name: string;
description?: string;
capabilities?: string[];
metadata?: Record<string, unknown>;
}
Decision Types
interface Decision {
id: DecisionId;
type: string;
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?: Record<string, unknown>;
outcome?: Record<string, unknown>;
}
type DecisionStatus =
| 'proposed'
| 'approved'
| 'rejected'
| 'executed'
| 'completed'
| 'failed';
type RiskLevel = 'low' | 'medium' | 'high' | 'critical';
Policy Types
interface Policy {
id: PolicyId;
name: string;
version: string;
description?: string;
effect: 'allow' | 'deny';
subjects: string[];
actions: string[];
resources: string[];
conditions?: Condition[];
priority: number;
validFrom?: Timestamp;
validUntil?: Timestamp;
}
interface Condition {
field: string;
operator: ConditionOperator;
value: unknown;
}
type ConditionOperator =
| 'equals'
| 'not_equals'
| 'greater_than'
| 'less_than'
| 'in'
| 'not_in'
| 'contains'
| 'starts_with'
| 'ends_with'
| 'matches'
| 'exists'
| 'between';
Provenance Types
interface ProvenanceEntry {
id: ProvenanceId;
hash: string;
previousHash: string | null;
type: ProvenanceType;
subjectId: string;
data: unknown;
source: ProvenanceSource;
timestamp: Timestamp;
agentId?: AgentId;
}
type ProvenanceType =
| 'claim_created'
| 'claim_revoked'
| 'entity_created'
| 'entity_updated'
| 'decision_recorded'
| 'decision_approved'
| 'decision_rejected'
| 'execution_logged'
| 'policy_created';
interface ProvenanceSource {
type: 'agent' | 'user' | 'system' | 'external' | 'inference';
id: string;
method?: string;
metadata?: Record<string, unknown>;
}
Execution Types
interface ActionRequest {
agentId: AgentId;
action: string;
resourceType: string;
resourceId: string;
parameters?: Record<string, unknown>;
}
interface ActionResult {
executionId: string;
status: 'allowed' | 'denied' | 'error';
output?: unknown;
error?: Error;
duration: number;
timestamp: Timestamp;
}
Query Types
interface QueryOptions {
limit?: number;
offset?: number;
sortBy?: string;
sortOrder?: 'asc' | 'desc';
}
interface ClaimQueryOptions extends QueryOptions {
predicate?: string;
asOf?: Timestamp;
jurisdiction?: string;
scope?: string;
minConfidence?: number;
activeOnly?: boolean;
includeExpired?: boolean;
}
SDK API Reference
Complete API reference for the ContextGraph SDK.
ContextGraph Class
create()
static async create(config?: ContextGraphConfig): Promise<Result<ContextGraph>>
Creates a new ContextGraph client.
Parameters:
config- Optional configuration object
Returns: Result<ContextGraph>
Entity Methods
createEntity()
async createEntity(input: CreateEntityInput): Promise<Result<Entity>>
getEntity()
async getEntity(id: EntityId): Promise<Result<Entity | null>>
updateEntity()
async updateEntity(id: EntityId, updates: Partial<Entity>): Promise<Result<Entity>>
listEntities()
async listEntities(options?: ListEntitiesOptions): Promise<Result<Entity[]>>
Claim Methods
addClaim()
async addClaim(input: AddClaimInput): Promise<Result<Claim>>
getClaims()
async getClaims(entityId: EntityId, options?: ClaimQueryOptions): Promise<Result<Claim[]>>
revokeClaim()
async revokeClaim(id: ClaimId, reason?: string): Promise<Result<void>>
Agent Methods
createAgent()
async createAgent(input: CreateAgentInput): Promise<Result<Agent>>
getAgent()
async getAgent(id: AgentId): Promise<Result<Agent | null>>
getAgentByName()
async getAgentByName(name: string): Promise<Result<Agent | null>>
listAgents()
async listAgents(options?: ListAgentsOptions): Promise<Result<Agent[]>>
Execution Methods
registerHandler()
registerHandler(
action: string,
resourceType: string,
handler: ActionHandler
): void
execute()
async execute(request: ActionRequest): Promise<Result<ActionResult>>
Decision Methods
recordDecision()
async recordDecision(input: RecordDecisionInput): Promise<Result<Decision>>
approveDecision()
async approveDecision(
id: DecisionId,
approverId: AgentId,
comment?: string
): Promise<Result<Decision>>
rejectDecision()
async rejectDecision(
id: DecisionId,
rejecterId: AgentId,
reason?: string
): Promise<Result<Decision>>
getPendingDecisions()
async getPendingDecisions(): Promise<Result<Decision[]>>
Policy Methods
createPolicy()
async createPolicy(input: CreatePolicyInput): Promise<Result<Policy>>
listPolicies()
async listPolicies(options?: ListPoliciesOptions): Promise<Result<Policy[]>>
Provenance Methods
verifyProvenance()
async verifyProvenance(): Promise<Result<VerificationResult>>
queryProvenance()
async queryProvenance(options?: ProvenanceQueryOptions): Promise<Result<ProvenanceEntry[]>>
Context Methods
assembleContext()
async assembleContext(
entityId: EntityId,
options?: AssemblyOptions
): Promise<Result<AssembledContext>>
Audit Methods
getAuditTrail()
async getAuditTrail(options?: AuditQueryOptions): Promise<Result<AuditEntry[]>>
Statistics
getStats()
async getStats(): Promise<Result<SystemStats>>
Import/Export
exportToJSON()
async exportToJSON(): Promise<Result<ExportData>>
importFromJSON()
async importFromJSON(
data: ExportData,
options?: ImportOptions
): Promise<Result<ImportResult>>
Events
on()
on(event: EventType, handler: EventHandler): void
off()
off(event: EventType, handler: EventHandler): void
Helper Functions
createTimestamp()
function createTimestamp(input?: string | Date): Timestamp
createScope()
function createScope(value: string): Scope
createJurisdiction()
function createJurisdiction(value: string): Jurisdiction
createConfidence()
function createConfidence(value: number): Confidence
ok()
function ok<T>(value: T): Result<T>
err()
function err<E = Error>(error: E): Result<never, E>
REST API Reference
Complete REST API reference for ContextGraph.
Base URL
http://localhost:3000/api/v1
Authentication
Include API key in header:
X-API-Key: your-api-key
Endpoints
Health
GET /health
Check service health.
Response:
{
"status": "healthy",
"timestamp": "2024-01-15T10:30:00.000Z"
}
Statistics
GET /stats
Get system statistics.
Response:
{
"entities": 42,
"claims": 156,
"agents": 3,
"decisions": 12,
"policies": 5,
"provenanceEntries": 203
}
Entities
GET /entities
List entities.
Query Parameters:
type- Filter by entity typelimit- Max results (default: 50)offset- Pagination offset
Response:
{
"data": [
{
"id": "ent_abc123",
"type": "person",
"name": "Alice",
"properties": {},
"createdAt": "2024-01-15T10:30:00.000Z"
}
],
"total": 42
}
POST /entities
Create entity.
Body:
{
"type": "person",
"name": "Alice",
"properties": {
"department": "Engineering"
}
}
GET /entities/:id
Get entity by ID.
PUT /entities/:id
Update entity.
DELETE /entities/:id
Delete entity.
GET /entities/:id/claims
Get claims for entity.
POST /entities/:id/claims
Add claim to entity.
Body:
{
"predicate": "has_skill",
"value": "TypeScript",
"context": {
"confidence": 0.95
}
}
Agents
GET /agents
List agents.
POST /agents
Create agent.
Body:
{
"name": "data-processor",
"description": "Processes data files",
"capabilities": ["read", "write"]
}
GET /agents/:id
Get agent by ID or name.
POST /agents/:id/execute
Execute action.
Body:
{
"action": "read",
"resourceType": "csv",
"resourceId": "data/input.csv",
"parameters": {}
}
Decisions
GET /decisions
List decisions.
Query Parameters:
status- Filter by statusriskLevel- Filter by risk
POST /decisions
Record decision.
Body:
{
"type": "deployment",
"title": "Deploy v2.0.0",
"proposedBy": "agt_123",
"riskLevel": "medium"
}
GET /decisions/:id
Get decision.
POST /decisions/:id/approve
Approve decision.
Body:
{
"approverId": "agt_456",
"comment": "Approved"
}
POST /decisions/:id/reject
Reject decision.
Policies
GET /policies
List policies.
POST /policies
Create policy.
Body:
{
"name": "Read Access",
"version": "1.0.0",
"effect": "allow",
"subjects": ["role:analyst"],
"actions": ["read"],
"resources": ["reports/*"],
"priority": 50
}
Audit
GET /audit
Query audit trail.
Query Parameters:
entityId- Filter by entityagentId- Filter by agentfrom- Start timestampto- End timestamplimit- Max results
Provenance
GET /provenance
Query provenance entries.
POST /provenance/verify
Verify provenance chain.
Response:
{
"valid": true,
"entriesVerified": 203,
"brokenLinks": 0,
"invalidHashes": 0
}
Error Responses
{
"error": {
"code": "NOT_FOUND",
"message": "Entity not found"
}
}
Error Codes
| Code | HTTP Status | Description |
|---|---|---|
NOT_FOUND | 404 | Resource not found |
VALIDATION_ERROR | 400 | Invalid input |
PERMISSION_DENIED | 403 | Access denied |
POLICY_DENIED | 403 | Policy blocked |
CONFLICT | 409 | Resource conflict |
INTERNAL_ERROR | 500 | Server error |
Rate Limiting
- Default: 100 requests/minute
- Header:
X-RateLimit-Remaining - Header:
X-RateLimit-Reset
Enterprise Deployment Architecture
A production-grade reference architecture for deploying ContextGraph OS with agents, RAG, policies, and human review workflows.
Architectural Philosophy
ContextGraph OS is not another agent framework. It is the governance spine that sits between agent cognition and real-world action.
The architecture enforces a simple but powerful rule:
No agent action is valid unless it is context-justified, policy-evaluated, and decision-traceable.
This is achieved by separating four planes:
| Plane | Purpose |
|---|---|
| Cognition Plane | Where agents think |
| Knowledge Plane | Where claims live (with provenance + time) |
| Governance Plane | Where policies, risk, and approvals exist |
| Execution Plane | Where actions actually occur |
High-Level System Diagram
┌───────────────────────────────────────────────────────────┐
│ Enterprise Interfaces │
│ (Dashboards · Audit · Compliance · Admin · CLI · API) │
└───────────────────────────────────────────────────────────┘
▲ ▲
│ │
Human Review Plane Audit / Export Plane
│ │
┌───────────────────────────────────────────────────────────┐
│ ContextGraph OS (Core) │
│ │
│ ┌──────────────┐ ┌──────────────┐ ┌──────────────┐ │
│ │ Knowledge │ │ Policy Engine│ │ Decision │ │
│ │ (Claims + KG) │◄──┤ (Deny-first) │──►│ Trace Graph │ │
│ └──────────────┘ └──────────────┘ └──────────────┘ │
│ ▲ ▲ │ │
│ │ │ │ │
│ ┌──────────────┐ ┌──────────────┐ ┌──────────┐ │
│ │ Provenance │ │ Exceptions │ │ Telemetry│ │
│ │ Ledger │ │ & Approvals │ │ & RBAC │ │
│ └──────────────┘ └──────────────┘ └──────────┘ │
└───────────────────────────────────────────────────────────┘
▲
│
┌───────────────────────────────────────────────────────────┐
│ Agent Runtime Layer │
│ (LangChain / AutoGen / Custom Agents / Tools) │
└───────────────────────────────────────────────────────────┘
▲
│
┌───────────────────────────────────────────────────────────┐
│ Data + RAG Infrastructure │
│ (Vector DB · Search · Docs · APIs · OSINT · Internal) │
└───────────────────────────────────────────────────────────┘
Component Breakdown
Agent Runtime Layer (Pluggable)
Supported Frameworks:
- LangChain / LangGraph
- CrewAI / AutoGen
- Custom internal agents
- Deterministic tools (APIs, scripts, workflows)
Key Rule: Agents never execute tools directly. They submit Action Intents to ContextGraph OS.
// Agent submits intent, not execution
agent.requestAction({
type: "publish_report",
target: "external_audience",
risk: "HIGH",
contextQuery: {
entityId: reportId,
minConfidence: 0.8,
}
});
Knowledge Plane (Contextual RAG + Claims)
This is where ContextGraph OS diverges sharply from typical RAG.
Instead of: "Here are top-k chunks"
ContextGraph stores: Claims with full context:
{
"entity": "Market_Analysis_Q4",
"claim": "Revenue growth projected at 15%",
"validFrom": "2024-10-01",
"validUntil": "2024-12-31",
"confidence": 0.85,
"source": "Internal Finance Report",
"provenanceHash": "0x9af3..."
}
This enables:
- Time-travel queries ("What did we know in March?")
- Contradiction coexistence (multiple sources, different claims)
- Evidence-weighted reasoning
- Audit-ready provenance
Policy Engine (Deny-First by Design)
Policies are evaluated before any execution.
policy:
name: external_publish_guard
effect: deny
when:
action: publish
target: external_audience
risk: [HIGH, CRITICAL]
then:
require_approval: true
approvers: [role:compliance_officer, role:legal]
Policy evaluation flow:
Action Intent → Policy Engine → DENY (needs approval)
│
└──→ Exception Queue → Human Review → APPROVE/REJECT
│
└──→ Execution (if approved)
Human Review & Exception Workflow
When a policy denies an action:
- Decision enters Exception Queue
- Human reviewer sees:
- Proposed action details
- Evidence used (claims, entities)
- Confidence levels
- Policy that triggered denial
- Risk assessment
- Reviewer can:
- Approve (with justification)
- Reject (with reason)
- Request more evidence
All outcomes are written into the Decision Trace Graph for permanent audit.
Execution Plane (Controlled Dispatch)
Execution only occurs after:
- Policy ALLOW, or
- Human approval for denied actions
// Execution is side-effect isolated and traceable
const result = await executor.dispatch({
decisionId: approvedDecision.id,
action: action,
context: assembledContext,
});
// Full execution logged to provenance
Audit & Compliance Plane
Outputs:
- Quarterly AI decision reports
- Evidence summaries per decision
- Policy override logs
- Provenance verification hashes
- GDPR data subject reports
Export formats:
- JSON (machine-readable)
- PDF (human-readable)
- CSV (spreadsheet analysis)
Deployment Models
Option A: Central Governance Service (Recommended)
┌─────────────┐ ┌─────────────┐ ┌─────────────┐
│ Agent A │ │ Agent B │ │ Agent C │
└──────┬──────┘ └──────┬──────┘ └──────┬──────┘
│ │ │
└────────────────┼────────────────┘
│
┌─────────▼─────────┐
│ ContextGraph OS │
│ (Central Service)│
└───────────────────┘
Benefits:
- Strongest audit posture
- Unified policy enforcement
- Cross-agent visibility
- Single source of truth
Option B: Embedded Mode
┌─────────────────────────┐
│ Agent A │
│ ┌───────────────────┐ │
│ │ ContextGraph OS │ │
│ │ (Embedded) │ │
│ └───────────────────┘ │
└─────────────────────────┘
Benefits:
- Lower latency
- No network dependency
- Simpler deployment
Trade-offs:
- Reduced cross-org visibility
- Per-app audit silos
Option C: Hybrid
┌─────────────────────────────────────────┐
│ Central Policy + Provenance │
└─────────────────────────────────────────┘
▲ ▲ ▲
│ │ │
┌────┴────┐ ┌────┴────┐ ┌────┴────┐
│ Agent A │ │ Agent B │ │ Agent C │
│ (Local │ │ (Local │ │ (Local │
│ Cache) │ │ Cache) │ │ Cache) │
└─────────┘ └─────────┘ └─────────┘
Benefits:
- Central governance
- Local performance
- Scalable architecture
Security Considerations
Data Classification
| Data Type | Storage | Encryption |
|---|---|---|
| Claims | Persistent | AES-256 at rest |
| Decisions | Persistent | AES-256 at rest |
| Provenance | Immutable | Hash-chained |
| Policies | Versioned | Signed |
Access Control
- RBAC for all operations
- Policy-based action control
- Audit logging for all access
- SSO/SAML integration (Enterprise)
Network Security
- TLS 1.3 for all connections
- mTLS for service-to-service
- API key + JWT authentication
- Rate limiting
Next Steps
Governance Deep Dive
How ContextGraph OS enforces governance at every layer.
The Governance Problem
Traditional AI systems have a fundamental governance gap:
| What Exists | What's Missing |
|---|---|
| Logs | Audit trails |
| Prompts | Provenance |
| Outputs | Decision traces |
| Configs | Policy enforcement |
ContextGraph OS closes this gap by making governance a first-class architectural concern.
Governance by Construction
Principle 1: No Orphan Data
Every piece of data must have:
- A source
- A timestamp
- A confidence level
- A provenance record
// This is enforced at the type level
interface Claim {
id: ClaimId;
source: ProvenanceSource; // Required
confidence: Confidence; // Required
validFrom: Timestamp; // Required
provenanceId: ProvenanceId; // Auto-generated
}
Principle 2: No Untraced Decisions
Every decision must be:
- Recorded before execution
- Evaluated against policies
- Traceable to evidence
// Decision lifecycle is enforced
PROPOSED → APPROVED/REJECTED → EXECUTED → COMPLETED/FAILED
│ │ │ │
└────────────┴────────────────┴──────────────┘
All states recorded
Principle 3: Deny-First Policy
The default answer is "no". Actions must be explicitly allowed.
# Default policy (implicit)
default:
effect: deny
reason: "No matching allow policy"
# Explicit allows override
explicit_allow:
effect: allow
subjects: [role:analyst]
actions: [read]
resources: [reports/*]
Principle 4: Time is Truth
Data is always qualified by time:
// Query: "What was the status on March 15?"
const claims = await ckg.query({
entityId: projectId,
asOf: createTimestamp('2024-03-15'),
});
// Returns claims valid on that date, not current claims
Governance Layers
Layer 1: Data Governance (CKG)
The Contextual Knowledge Graph enforces:
| Constraint | Enforcement |
|---|---|
| Source required | Schema validation |
| Temporal validity | Query filtering |
| Confidence scores | Retrieval ranking |
| Contradictions | Explicit handling |
Layer 2: Action Governance (Policy Engine)
Every action is policy-evaluated:
Action Request
│
▼
┌─────────────────────────────────────┐
│ Policy Evaluation │
│ │
│ 1. Find matching policies │
│ 2. Evaluate conditions │
│ 3. Apply deny-takes-precedence │
│ 4. Return decision + reasons │
└─────────────────────────────────────┘
│
├── ALLOW → Execute
│
└── DENY → Exception Queue (optional)
│
└── Human Review
Layer 3: Decision Governance (DTG)
All significant decisions tracked:
interface Decision {
id: DecisionId;
type: string;
status: DecisionStatus;
// Who proposed this?
proposedBy: AgentId;
proposedAt: Timestamp;
// What evidence was used?
evidenceIds: ClaimId[];
// What policy evaluated it?
policyEvaluation: PolicyResult;
// Who approved/rejected?
approvedBy?: AgentId;
rejectedBy?: AgentId;
// Full audit trail
history: DecisionEvent[];
}
Layer 4: Audit Governance (Provenance)
Immutable, hash-chained records:
Entry 1 ──hash──► Entry 2 ──hash──► Entry 3
│ │ │
└────────────────┴─────────────────┘
Tamper-evident chain
Governance Workflows
Workflow 1: High-Risk Action Approval
sequenceDiagram
participant Agent
participant Policy
participant Queue
participant Human
participant Executor
Agent->>Policy: Request Action (risk=HIGH)
Policy->>Policy: Evaluate policies
Policy->>Queue: DENY (requires approval)
Queue->>Human: Show decision + evidence
Human->>Queue: APPROVE (with reason)
Queue->>Executor: Execute action
Executor->>Agent: Result
Workflow 2: Evidence-Weighted Decision
sequenceDiagram
participant Agent
participant CKG
participant Reasoner
participant Policy
Agent->>CKG: Query claims for entity
CKG->>Agent: Claims (with confidence)
Agent->>Reasoner: Infer additional facts
Reasoner->>Agent: Inferred facts
Agent->>Policy: Request action
Policy->>Policy: Check confidence thresholds
Policy->>Agent: ALLOW (confidence >= 0.8)
Workflow 3: Compliance Audit
sequenceDiagram
participant Auditor
participant Compliance
participant DTG
participant Provenance
Auditor->>Compliance: Generate Q4 report
Compliance->>DTG: Query decisions (Q4)
DTG->>Compliance: Decision records
Compliance->>Provenance: Verify chain
Provenance->>Compliance: Verification result
Compliance->>Auditor: PDF report + evidence
Governance Metrics
Track governance health with these metrics:
| Metric | Good | Warning | Critical |
|---|---|---|---|
| Policy deny rate | <10% | 10-30% | >30% |
| Approval latency | <1hr | 1-24hr | >24hr |
| Provenance chain valid | 100% | - | <100% |
| Decision trace coverage | 100% | 95-99% | <95% |
Next Steps
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
| State | Description | Can Transition To |
|---|---|---|
PROPOSED | Decision submitted by agent | APPROVED, REJECTED, NEEDS_REVIEW |
APPROVED | Passed policy or human review | EXECUTED, CANCELLED |
REJECTED | Denied by policy or reviewer | (terminal) |
NEEDS_REVIEW | Awaiting human approval | APPROVED, REJECTED |
EXECUTED | Action dispatched | COMPLETED, FAILED |
COMPLETED | Successfully finished | (terminal) |
FAILED | Execution error | (terminal) |
CANCELLED | Withdrawn 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
1. Always Link Evidence
// 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
Policy Configuration
Comprehensive guide to configuring policies in ContextGraph OS.
Policy Anatomy
policy:
# Identity
id: pol_abc123
name: "High Risk Action Guard"
version: "1.0.0"
description: "Requires approval for high-risk actions"
# Effect
effect: deny # or 'allow'
# Who does this apply to?
subjects:
- "*" # All subjects
# - "role:analyst"
# - "agent:data-processor"
# What actions?
actions:
- "delete"
- "publish"
- "export"
# Which resources?
resources:
- "production/*"
- "customer-data/*"
# Under what conditions?
conditions:
- field: "risk.level"
operator: "in"
value: ["HIGH", "CRITICAL"]
# Priority (higher = evaluated first)
priority: 100
# Temporal validity
validFrom: "2024-01-01T00:00:00Z"
validUntil: null # No expiration
Policy Matching
Subject Matching
# Match all
subjects: ["*"]
# Match specific role
subjects: ["role:admin"]
# Match specific agent
subjects: ["agent:data-processor"]
# Match multiple
subjects: ["role:analyst", "role:data-scientist"]
# Match pattern
subjects: ["agent:*-processor"]
Action Matching
# Match specific action
actions: ["delete"]
# Match multiple
actions: ["read", "write", "delete"]
# Match all
actions: ["*"]
# Match pattern
actions: ["export:*"]
Resource Matching
# Exact match
resources: ["/api/users/123"]
# Wildcard suffix
resources: ["reports/*"]
# Wildcard prefix
resources: ["*/sensitive"]
# Multiple patterns
resources: ["pii/*", "*/personal/*", "customers/*"]
Condition Operators
| Operator | Description | Example |
|---|---|---|
equals | Exact match | { field: "status", operator: "equals", value: "active" } |
not_equals | Not equal | { field: "status", operator: "not_equals", value: "deleted" } |
in | In array | { field: "role", operator: "in", value: ["admin", "super"] } |
not_in | Not in array | { field: "role", operator: "not_in", value: ["guest"] } |
greater_than | Numeric comparison | { field: "risk.score", operator: "greater_than", value: 0.7 } |
less_than | Numeric comparison | { field: "confidence", operator: "less_than", value: 0.5 } |
between | Range | { field: "hour", operator: "between", value: [9, 17] } |
contains | String contains | { field: "path", operator: "contains", value: "admin" } |
starts_with | String prefix | { field: "resource", operator: "starts_with", value: "/api/" } |
matches | Regex match | { field: "email", operator: "matches", value: ".*@company\\.com$" } |
exists | Field exists | { field: "approval.id", operator: "exists" } |
Condition Fields
Built-in Fields
| Field | Description |
|---|---|
subject.id | Subject identifier |
subject.role | Subject's role |
subject.type | Subject type (agent, user) |
action | Action being performed |
resource | Target resource |
time.hour | Current hour (0-23) |
time.dayOfWeek | Day name (Mon, Tue, etc.) |
time.date | ISO date |
risk.level | Risk level (LOW, MEDIUM, HIGH, CRITICAL) |
risk.score | Risk score (0-1) |
context.jurisdiction | Jurisdiction code |
context.scope | Scope |
Custom Fields
Access any field from the action context:
conditions:
- field: "parameters.count"
operator: "greater_than"
value: 100
- field: "metadata.department"
operator: "equals"
value: "finance"
Policy Templates
Read-Only Access
policy:
name: "Read Only Access"
effect: allow
subjects: ["role:viewer"]
actions: ["read", "list", "get"]
resources: ["*"]
priority: 30
PII Protection
policy:
name: "PII Protection"
effect: deny
subjects: ["*"]
actions: ["*"]
resources: ["pii/*", "*/personal/*"]
conditions:
- field: "subject.clearance"
operator: "not_equals"
value: "pii-authorized"
priority: 100
Business Hours Only
policy:
name: "Business Hours Only"
effect: deny
subjects: ["role:contractor"]
actions: ["*"]
resources: ["*"]
conditions:
- field: "time.hour"
operator: "not_in"
value: [9, 10, 11, 12, 13, 14, 15, 16, 17]
priority: 80
High Risk Approval
policy:
name: "High Risk Requires Approval"
effect: deny
subjects: ["*"]
actions: ["delete", "publish", "deploy"]
resources: ["production/*"]
conditions:
- field: "risk.level"
operator: "in"
value: ["HIGH", "CRITICAL"]
- field: "approval.status"
operator: "not_equals"
value: "approved"
priority: 90
Jurisdiction Restriction
policy:
name: "EU Data in EU Only"
effect: deny
subjects: ["*"]
actions: ["export", "transfer"]
resources: ["eu-data/*"]
conditions:
- field: "context.jurisdiction"
operator: "not_equals"
value: "EU"
priority: 95
Policy Evaluation Order
- Policies sorted by priority (highest first)
- For each policy:
- Check subject match
- Check action match
- Check resource match
- Evaluate conditions
- First DENY wins (deny-takes-precedence)
- If no deny, first ALLOW wins
- If no match, implicit DENY
Priority 100: Security policies
Priority 90: Risk policies
Priority 80: Compliance policies
Priority 50: Business rules
Priority 30: Role defaults
Priority 10: Fallback rules
Testing Policies
Simulation
const simulator = new PolicySimulator(policyLedger, storage);
const result = await simulator.simulate({
subject: "agent:report-generator",
action: "publish",
resource: "production/quarterly-report",
context: {
risk: { level: "HIGH" },
time: { hour: 14 }
}
});
console.log(result.effect); // "deny"
console.log(result.matchedPolicies);
console.log(result.reason);
Batch Testing
const scenarios = [
{ subject: "role:admin", action: "delete", resource: "data/*" },
{ subject: "role:analyst", action: "read", resource: "reports/*" },
{ subject: "role:guest", action: "write", resource: "comments/*" },
];
const results = await simulator.simulateMany(scenarios);
Best Practices
- Start restrictive - Default deny, explicit allow
- Use high priority for security - Security > Compliance > Business
- Test before deploy - Use simulator
- Version policies - Semantic versioning
- Document conditions - Clear descriptions
- Monitor evaluations - Track deny rates
- Review regularly - Quarterly policy audits
Compliance Reporting
Generate audit-ready reports for regulatory compliance, internal audits, and governance reviews.
Overview
ContextGraph OS provides comprehensive compliance reporting:
- Automated report generation from decision traces
- Evidence packages with provenance verification
- Multi-format export (JSON, PDF, CSV)
- Configurable redaction for sensitive data
- Chain-of-custody verification via hash chains
Report Types
1. Decision Audit Report
Complete record of decisions within a time period:
import { ComplianceReporter } from '@contextgraph/compliance';
const reporter = new ComplianceReporter({ dtg, ckg, provenance });
const report = await reporter.generateDecisionAudit({
timeRange: {
from: createTimestamp('2024-10-01'),
to: createTimestamp('2024-12-31'),
},
includeEvidence: true,
includeHistory: true,
groupBy: 'week',
});
Output Structure:
{
"reportId": "rpt_abc123",
"generatedAt": "2025-01-15T10:00:00Z",
"timeRange": {
"from": "2024-10-01T00:00:00Z",
"to": "2024-12-31T23:59:59Z"
},
"summary": {
"totalDecisions": 1250,
"approved": 1100,
"rejected": 120,
"failed": 30,
"avgProcessingTime": "2.5h",
"humanReviewRate": "15%"
},
"byWeek": [
{
"week": "2024-W40",
"decisions": 95,
"approved": 85,
"rejected": 8,
"failed": 2
}
],
"decisions": [
{
"id": "dec_xyz",
"type": "publish_report",
"status": "completed",
"proposedBy": "agent:report-gen",
"proposedAt": "2024-10-15T09:00:00Z",
"evidence": [...],
"history": [...]
}
]
}
2. Provenance Verification Report
Verify integrity of the audit trail:
const verification = await reporter.generateProvenanceVerification({
entityIds: [entity1, entity2, entity3],
verifyChain: true,
verifyHashes: true,
});
Output:
{
"reportId": "rpt_def456",
"generatedAt": "2025-01-15T10:00:00Z",
"verification": {
"chainIntegrity": "VALID",
"entriesVerified": 5000,
"hashMismatches": 0,
"brokenLinks": 0
},
"entities": [
{
"entityId": "ent_123",
"claims": 50,
"provenanceEntries": 75,
"chainValid": true,
"oldestEntry": "2024-01-15T00:00:00Z",
"newestEntry": "2025-01-14T23:59:59Z"
}
]
}
3. Policy Evaluation Report
Summary of policy enforcement:
const policyReport = await reporter.generatePolicyReport({
timeRange: { from: q4Start, to: q4End },
includeViolations: true,
includeExceptions: true,
});
Output:
{
"summary": {
"totalEvaluations": 5000,
"allowed": 4200,
"denied": 800,
"exceptionsGranted": 50
},
"byPolicy": [
{
"policyId": "pol_pii_protection",
"name": "PII Protection",
"evaluations": 1200,
"denials": 300,
"exceptions": 15
}
],
"violations": [
{
"timestamp": "2024-11-15T14:30:00Z",
"policy": "pol_high_risk",
"subject": "agent:data-proc",
"action": "delete",
"resource": "customer_data/*",
"resolution": "exception_granted",
"approvedBy": "user:compliance_mgr"
}
]
}
4. Agent Activity Report
Per-agent decision and activity summary:
const agentReport = await reporter.generateAgentReport({
agentId: agentId,
timeRange: { from: monthStart, to: monthEnd },
includeDecisions: true,
includeCapabilities: true,
});
Output:
{
"agent": {
"id": "agent:report-generator",
"name": "Report Generator",
"status": "active",
"capabilities": ["read", "generate", "publish"]
},
"activity": {
"decisionsProposed": 150,
"decisionsApproved": 140,
"decisionsRejected": 8,
"decisionsFailed": 2,
"approvalRate": "93.3%",
"avgRiskScore": 0.35
},
"riskProfile": {
"lowRisk": 100,
"mediumRisk": 40,
"highRisk": 10,
"criticalRisk": 0
}
}
5. GDPR Data Subject Report
For data subject access requests (DSAR):
const dsarReport = await reporter.generateDSAR({
subjectId: customerId,
includeAllClaims: true,
includeDecisions: true,
includeProvenance: true,
redactInternal: true,
});
Output:
{
"subject": {
"id": "customer_123",
"type": "data_subject"
},
"data": {
"claims": [
{
"attribute": "email",
"value": "user@example.com",
"source": "registration_form",
"collectedAt": "2023-06-15T10:00:00Z",
"purpose": "account_management"
}
],
"decisions": [
{
"type": "marketing_email",
"status": "completed",
"timestamp": "2024-03-15T09:00:00Z"
}
]
},
"provenance": {
"dataOrigins": ["web_form", "api_import"],
"processingActivities": 25,
"thirdPartySharing": []
}
}
Export Formats
JSON Export
const jsonReport = await reporter.export({
report: decisionAudit,
format: 'json',
pretty: true,
});
PDF Export
const pdfReport = await reporter.export({
report: decisionAudit,
format: 'pdf',
template: 'compliance-formal',
includeCharts: true,
includeSignature: true,
});
CSV Export
const csvReport = await reporter.export({
report: decisionAudit,
format: 'csv',
flatten: true, // Flatten nested objects
columns: ['id', 'type', 'status', 'proposedAt', 'approvedBy'],
});
Redaction & Privacy
Automatic PII Redaction
const report = await reporter.generateDecisionAudit({
timeRange: { from, to },
redaction: {
enabled: true,
patterns: ['email', 'phone', 'ssn', 'credit_card'],
replacement: '[REDACTED]',
},
});
Custom Redaction Rules
const report = await reporter.generateDecisionAudit({
timeRange: { from, to },
redaction: {
enabled: true,
rules: [
{
field: 'customer.email',
action: 'hash', // SHA-256 hash
},
{
field: 'customer.name',
action: 'mask', // J*** D**
},
{
field: 'internal_notes',
action: 'remove', // Completely remove
},
],
},
});
Role-Based Redaction
const report = await reporter.generateDecisionAudit({
timeRange: { from, to },
redaction: {
enabled: true,
byRole: {
auditor: ['internal_notes'], // Auditors can't see internal notes
external: ['*_internal', 'cost_*'], // External can't see internal fields
},
},
viewerRole: 'external',
});
Scheduling Reports
Automated Generation
import { ReportScheduler } from '@contextgraph/compliance';
const scheduler = new ReportScheduler(reporter);
// Weekly decision audit
scheduler.schedule({
id: 'weekly-audit',
type: 'decision_audit',
cron: '0 0 * * MON', // Every Monday at midnight
config: {
timeRange: 'last_week',
includeEvidence: true,
},
delivery: {
email: ['compliance@company.com'],
s3: 's3://compliance-reports/weekly/',
},
});
// Monthly policy report
scheduler.schedule({
id: 'monthly-policy',
type: 'policy_report',
cron: '0 0 1 * *', // First of every month
config: {
timeRange: 'last_month',
includeViolations: true,
},
delivery: {
email: ['security@company.com'],
},
});
On-Demand Generation
// Generate report immediately
const report = await scheduler.runNow('weekly-audit');
// Generate with custom time range
const customReport = await scheduler.runNow('weekly-audit', {
timeRange: {
from: createTimestamp('2024-10-01'),
to: createTimestamp('2024-10-31'),
},
});
Compliance Dashboards
Metrics Query
const metrics = await reporter.getMetrics({
timeRange: { from: monthStart, to: now },
});
// Returns:
// {
// decisions: { total: 1000, approved: 900, rejected: 80, failed: 20 },
// policies: { evaluations: 5000, denials: 400, exceptions: 25 },
// provenance: { entries: 10000, verified: 10000, invalid: 0 },
// agents: { active: 15, suspended: 2, total: 17 },
// }
Trend Analysis
const trends = await reporter.getTrends({
timeRange: { from: yearStart, to: now },
granularity: 'month',
metrics: ['decision_volume', 'rejection_rate', 'avg_risk_score'],
});
// Returns monthly data points for trend visualization
Regulatory Templates
SOC 2 Report
const soc2 = await reporter.generateSOC2({
timeRange: { from: auditStart, to: auditEnd },
trustPrinciples: ['security', 'availability', 'confidentiality'],
});
ISO 27001 Evidence
const iso27001 = await reporter.generateISO27001Evidence({
controls: ['A.9.4.1', 'A.12.4.1', 'A.16.1.2'],
timeRange: { from: auditStart, to: auditEnd },
});
HIPAA Audit Trail
const hipaa = await reporter.generateHIPAAReport({
timeRange: { from: auditStart, to: auditEnd },
includePHIAccess: true,
includeDisclosures: true,
});
Verification & Signing
Hash Verification
// Verify report hasn't been tampered with
const verification = reporter.verifyReport(reportJson);
// {
// valid: true,
// hash: "sha256:abc123...",
// generatedAt: "2025-01-15T10:00:00Z",
// verifiedAt: "2025-01-15T12:00:00Z"
// }
Digital Signatures
const signedReport = await reporter.signReport(report, {
signer: 'compliance-officer',
certificate: '/path/to/cert.pem',
timestamp: true,
});
Best Practices
1. Regular Verification
// Run weekly provenance verification
scheduler.schedule({
id: 'weekly-verification',
type: 'provenance_verification',
cron: '0 2 * * SUN', // Sunday 2 AM
config: {
verifyChain: true,
verifyHashes: true,
alertOnFailure: true,
},
alerts: {
onFailure: ['security@company.com'],
},
});
2. Retain Reports
// Archive reports with retention policy
const report = await reporter.generateDecisionAudit({
timeRange: { from, to },
archive: {
enabled: true,
location: 's3://compliance-archive/',
retention: '7 years',
encryption: 'AES-256',
},
});
3. Test Report Generation
// Validate report generation in CI/CD
describe('Compliance Reports', () => {
it('generates valid decision audit', async () => {
const report = await reporter.generateDecisionAudit({
timeRange: { from: testStart, to: testEnd },
});
expect(report.summary).toBeDefined();
expect(report.decisions.length).toBeGreaterThan(0);
expect(reporter.verifyReport(report).valid).toBe(true);
});
});
4. Document Report Recipients
// Maintain audit trail of report distribution
const distribution = await reporter.distribute(report, {
recipients: ['compliance@company.com', 'auditor@external.com'],
logDistribution: true,
requireAcknowledgment: true,
});
Next Steps
Commercial Hosted Architecture
Technical architecture for the ContextGraph OS commercial hosted product.
System Overview
┌─────────────────────────────────────────────────────────────────────────┐
│ Customer Applications │
│ (LangChain · AutoGen · Custom Agents · Internal Tools) │
└───────────────────────────────────┬─────────────────────────────────────┘
│ SDK / REST API
▼
┌─────────────────────────────────────────────────────────────────────────┐
│ ContextGraph Cloud Platform │
│ ┌─────────────────────────────────────────────────────────────────┐ │
│ │ API Gateway │ │
│ │ (Rate Limiting · Auth · Tenant Routing) │ │
│ └─────────────────────────────────────────────────────────────────┘ │
│ │ │
│ ┌────────────────────────────┼────────────────────────────┐ │
│ ▼ ▼ ▼ │
│ ┌──────────┐ ┌──────────────┐ ┌──────────┐ │
│ │ Auth │ │ Core API │ │ Dashboard │ │
│ │ Service │ │ Service │ │ (React) │ │
│ └──────────┘ └──────────────┘ └──────────┘ │
│ │ │ │ │
│ │ ┌──────────────────────┼──────────────────────┐ │ │
│ │ ▼ ▼ ▼ │ │
│ │ ┌────────┐ ┌──────────────┐ ┌────────┐ │ │
│ │ │ Policy │ │ Decision │ │ Report │ │ │
│ │ │ Engine │ │ Processor │ │ Worker │ │ │
│ │ └────────┘ └──────────────┘ └────────┘ │ │
│ │ │ │ │ │ │
│ └─────┼──────────────────────┼──────────────────────┼─────┘ │
│ │ │ │ │
│ ▼ ▼ ▼ │
│ ┌─────────────────────────────────────────────────────────────────┐ │
│ │ PostgreSQL (Multi-Tenant) │ │
│ │ Claims · Decisions · Policies · Provenance · Agents │ │
│ └─────────────────────────────────────────────────────────────────┘ │
│ │ │
│ ┌─────────────────────────────────────────────────────────────────┐ │
│ │ Redis Cluster │ │
│ │ (Sessions · Cache · Real-time Events) │ │
│ └─────────────────────────────────────────────────────────────────┘ │
└─────────────────────────────────────────────────────────────────────────┘
Core Components
1. API Gateway
Technology: NGINX / Kong / AWS API Gateway
Responsibilities:
- Request routing to services
- Rate limiting per tenant
- API key validation
- Request/response logging
- SSL termination
# Rate limits per tier
rate_limits:
team:
requests_per_minute: 1000
burst: 100
enterprise:
requests_per_minute: 10000
burst: 500
2. Authentication Service
Technology: Node.js + Passport.js / Auth0
Features:
- API key management
- SSO/SAML integration (Team+)
- JWT token issuance
- Role-based access control
- Multi-factor authentication (Enterprise)
interface TenantAuth {
tenantId: string;
plan: 'team' | 'enterprise';
apiKeys: ApiKey[];
ssoConfig?: SSOConfig;
users: User[];
roles: Role[];
}
3. Core API Service
Technology: Node.js + TypeScript + Express/Fastify
Endpoints:
/api/v1/claims- CRUD for claims/api/v1/decisions- Decision lifecycle/api/v1/policies- Policy management/api/v1/agents- Agent registry/api/v1/provenance- Audit trail queries
Multi-tenancy:
// Every request is tenant-scoped
app.use((req, res, next) => {
const tenantId = extractTenantId(req);
req.context = { tenantId };
next();
});
// Queries are automatically filtered
const claims = await ckg.query({
tenantId: req.context.tenantId, // Enforced
entityId: 'report_123',
});
4. Policy Engine Service
Technology: Node.js + @contextgraph/policy
Features:
- Real-time policy evaluation
- Policy simulation/dry-run
- Template management
- Policy versioning
5. Decision Processor
Technology: Node.js + Bull Queue
Responsibilities:
- Async decision processing
- Human review queue management
- Webhook delivery
- Status transitions
// Decision processing flow
queue.process('decision', async (job) => {
const { decisionId, tenantId } = job.data;
// Evaluate policies
const result = await policyEngine.evaluate(decision);
if (result.effect === 'deny' && result.requiresApproval) {
await notifyReviewers(tenantId, decision);
return { status: 'needs_review' };
}
// Auto-approve
await dtg.transition(decisionId, 'approved');
return { status: 'approved' };
});
6. Report Worker
Technology: Node.js + PDFKit + Bull Queue
Features:
- Scheduled report generation
- On-demand compliance exports
- PDF/CSV/JSON formats
- Email delivery
7. Dashboard (Web UI)
Technology: React + TypeScript + TailwindCSS
Features:
- Real-time decision monitoring
- Policy editor (visual + YAML)
- Agent status & capabilities
- Compliance report generation
- Team management
- Audit log viewer
Database Schema (PostgreSQL)
-- Multi-tenant partitioning
CREATE TABLE tenants (
id UUID PRIMARY KEY,
name TEXT NOT NULL,
plan TEXT NOT NULL, -- 'team' | 'enterprise'
settings JSONB,
created_at TIMESTAMPTZ DEFAULT NOW()
);
-- All tables include tenant_id
CREATE TABLE claims (
id UUID PRIMARY KEY,
tenant_id UUID REFERENCES tenants(id),
entity_id TEXT NOT NULL,
attribute TEXT NOT NULL,
value JSONB NOT NULL,
confidence DECIMAL(3,2),
valid_from TIMESTAMPTZ NOT NULL,
valid_until TIMESTAMPTZ,
provenance_id UUID,
created_at TIMESTAMPTZ DEFAULT NOW(),
-- Partition by tenant for isolation
CONSTRAINT claims_tenant_fk FOREIGN KEY (tenant_id) REFERENCES tenants(id)
);
CREATE INDEX idx_claims_tenant_entity ON claims(tenant_id, entity_id);
CREATE INDEX idx_claims_valid_range ON claims(tenant_id, valid_from, valid_until);
-- Row-level security for tenant isolation
ALTER TABLE claims ENABLE ROW LEVEL SECURITY;
CREATE POLICY tenant_isolation ON claims
USING (tenant_id = current_setting('app.tenant_id')::uuid);
Infrastructure Requirements
Team Tier (Managed)
| Component | Specification |
|---|---|
| API Servers | 2x (load balanced) |
| Database | PostgreSQL RDS (db.t3.medium) |
| Redis | ElastiCache (cache.t3.micro) |
| Storage | S3 for reports |
| CDN | CloudFront for dashboard |
Estimated Cost: ~$200-400/month base infrastructure
Enterprise Tier (Self-Hosted)
| Component | Specification |
|---|---|
| Kubernetes | 3-node cluster minimum |
| Database | PostgreSQL (dedicated, encrypted) |
| Redis | 3-node cluster |
| Storage | Local or cloud storage |
| Monitoring | Prometheus + Grafana |
Deployment Options:
- AWS EKS / GCP GKE / Azure AKS
- On-premise Kubernetes
- Docker Compose (small scale)
Deployment Architecture
Kubernetes Deployment
# Helm chart structure
contextgraph-cloud/
├── Chart.yaml
├── values.yaml
├── templates/
│ ├── api-deployment.yaml
│ ├── api-service.yaml
│ ├── auth-deployment.yaml
│ ├── dashboard-deployment.yaml
│ ├── worker-deployment.yaml
│ ├── ingress.yaml
│ ├── configmap.yaml
│ └── secrets.yaml
# values.yaml
replicaCount:
api: 3
auth: 2
dashboard: 2
worker: 2
postgresql:
enabled: true
auth:
database: contextgraph
primary:
persistence:
size: 100Gi
redis:
enabled: true
architecture: replication
ingress:
enabled: true
className: nginx
hosts:
- host: api.contextgraph.io
paths:
- path: /
pathType: Prefix
Docker Compose (Development/Small Scale)
version: '3.8'
services:
api:
build: ./packages/cloud-api
ports:
- "3000:3000"
environment:
- DATABASE_URL=postgresql://postgres:password@db:5432/contextgraph
- REDIS_URL=redis://redis:6379
depends_on:
- db
- redis
dashboard:
build: ./packages/cloud-dashboard
ports:
- "3001:80"
worker:
build: ./packages/cloud-worker
environment:
- DATABASE_URL=postgresql://postgres:password@db:5432/contextgraph
- REDIS_URL=redis://redis:6379
depends_on:
- db
- redis
db:
image: postgres:15
volumes:
- pgdata:/var/lib/postgresql/data
environment:
- POSTGRES_DB=contextgraph
- POSTGRES_PASSWORD=password
redis:
image: redis:7-alpine
volumes:
- redisdata:/data
volumes:
pgdata:
redisdata:
Security Considerations
Data Isolation
- Row-Level Security: PostgreSQL RLS enforces tenant isolation
- Encryption at Rest: AES-256 for all stored data
- Encryption in Transit: TLS 1.3 for all connections
- API Key Hashing: bcrypt for stored API keys
Compliance
| Standard | Features |
|---|---|
| SOC 2 | Audit logging, access controls, encryption |
| HIPAA | BAA available, PHI handling procedures |
| GDPR | Data export, deletion, consent tracking |
| ISO 27001 | Security controls, incident response |
Audit Trail
// All operations are logged
interface AuditLog {
id: string;
tenantId: string;
userId: string;
action: string; // 'claim.create', 'decision.approve', etc.
resource: string;
resourceId: string;
metadata: Record<string, unknown>;
ip: string;
userAgent: string;
timestamp: Date;
}
Monitoring & Observability
Metrics (Prometheus)
# Key metrics
- contextgraph_decisions_total{tenant, status}
- contextgraph_policy_evaluations_total{tenant, effect}
- contextgraph_api_latency_seconds{endpoint, method}
- contextgraph_claims_count{tenant}
- contextgraph_agents_active{tenant}
Logging (Structured JSON)
{
"level": "info",
"timestamp": "2024-03-15T10:30:00Z",
"service": "api",
"tenantId": "tenant_123",
"requestId": "req_abc",
"message": "Decision approved",
"decisionId": "dec_xyz",
"duration_ms": 45
}
Alerting
| Alert | Threshold |
|---|---|
| API Error Rate | > 1% for 5 min |
| API Latency P99 | > 500ms for 5 min |
| Database Connections | > 80% pool |
| Queue Backlog | > 1000 jobs |
| Decision Failure Rate | > 5% for 10 min |
Next Steps
- Phase 1: Core API + PostgreSQL multi-tenancy
- Phase 2: Dashboard MVP + Basic auth
- Phase 3: SSO integration + Advanced policies
- Phase 4: Self-hosted Helm chart
- Phase 5: Compliance certifications
Contributing
Thank you for your interest in contributing to ContextGraph OS!
Getting Started
- Fork the repository
- Clone your fork
- Install dependencies:
pnpm install - Build:
pnpm -r build - Run tests:
pnpm -r test
Development Workflow
Branch Naming
feature/description- New featuresfix/description- Bug fixesdocs/description- Documentationrefactor/description- Code refactoring
Commit Messages
Follow conventional commits:
feat: add new visualization format
fix: resolve policy evaluation order
docs: update SDK documentation
refactor: simplify storage interface
test: add CKG integration tests
Pull Requests
- Create a feature branch
- Make your changes
- Write/update tests
- Update documentation
- Submit PR with description
Code Style
- TypeScript strict mode
- ESLint + Prettier
- 100% test coverage for new code
Testing
# Run all tests
pnpm -r test
# Run specific package tests
pnpm --filter @contextgraph/sdk test
# Run with coverage
pnpm -r test:coverage
Documentation
- Update relevant docs for any API changes
- Add JSDoc comments to public APIs
- Include examples in documentation
Questions?
Open an issue for questions or discussions.
Changelog
All notable changes to ContextGraph OS.
[0.3.0] - 2024
Added
-
Visualization Package (
@contextgraph/viz)- DOT/Graphviz renderer
- Mermaid diagram renderer
- D3.js JSON format
- SVG standalone renderer
- High-level APIs: visualizeProvenance, visualizeEntities, visualizeDecisions
-
Semantic Reasoning (
@contextgraph/reasoning)- RelationRegistry with transitive, symmetric, inverse relations
- RuleEngine with forward chaining inference
- Pattern matching with variable bindings
- ContradictionDetector for finding and resolving conflicts
- Built-in rules for common inference patterns
-
Decision Recommendations (
@contextgraph/recommendations)- Similarity-based precedent matching
- Configurable matching weights
- Risk assessment with customizable patterns
- Feedback loop for accuracy improvement
[0.2.0] - 2024
Added
-
Policy Templates & Simulation
- 6 built-in templates
- PolicyTemplateManager
- PolicySimulator for dry-run testing
-
Agent Hierarchies & Delegation
- AgentHierarchyManager
- Capability delegation
- Cascade operations
-
OpenTelemetry Integration (
@contextgraph/telemetry)- Tracing with spans
- Metrics (Counter, Gauge, Histogram)
- Structured logging
- Multiple exporters
[0.1.0] - 2024
Added
- Initial release
- Core foundation packages
- Contextual Knowledge Graph
- Provenance ledger
- Decision Trace Graph
- Policy enforcement
- Agent execution framework
- SDK and CLI
- REST API