Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
26 changes: 26 additions & 0 deletions examples/01_persistent_guardrails.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
import { createEngine, getPolicyItems } from '../src/index.js';

declare const process: { argv: string[] };

export function runExample01(): {
turn1Kind: string;
turn2Kind: string;
prohibitedPolicies: string[];
} {
const engine = createEngine();

const decision1 = engine.step('prohibit peanuts');
const decision2 = engine.step('how should I make this curry?');

return {
turn1Kind: decision1.kind,
turn2Kind: decision2.kind,
prohibitedPolicies: getPolicyItems(engine.state, 'prohibit')
};
}

if (typeof process !== 'undefined' && process.argv[1] && import.meta.url === new URL(process.argv[1], 'file://').href) {
const result = runExample01();
console.log('example 01: persistent guardrails');
console.log(JSON.stringify(result, null, 2));
}
26 changes: 26 additions & 0 deletions examples/02_configuration_and_correction.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
import { createEngine, getPremiseValue } from '../src/index.js';

declare const process: { argv: string[] };

export function runExample02(): {
setKind: string;
changeKind: string;
finalPremise: string | null;
} {
const engine = createEngine();

const decision1 = engine.step('set premise vegetarian curry');
const decision2 = engine.step('change premise to vegan curry');

return {
setKind: decision1.kind,
changeKind: decision2.kind,
finalPremise: getPremiseValue(engine.state)
};
}

if (typeof process !== 'undefined' && process.argv[1] && import.meta.url === new URL(process.argv[1], 'file://').href) {
const result = runExample02();
console.log('example 02: configuration and correction');
console.log(JSON.stringify(result, null, 2));
}
35 changes: 35 additions & 0 deletions examples/03_ambiguity_with_clarification.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
import { createEngine } from '../src/index.js';

declare const process: { argv: string[] };

export function runExample03(): {
clarifyKind: string;
clarifyPrompt: string | null;
llmCalled: boolean;
resetKind: string;
} {
const engine = createEngine();

engine.step('prohibit peanuts');
const contradictionDecision = engine.step('use peanuts');

let llmCalled = false;
if (contradictionDecision.kind !== 'clarify') {
llmCalled = true;
}

const resetDecision = engine.step('clear state');

return {
clarifyKind: contradictionDecision.kind,
clarifyPrompt: contradictionDecision.prompt_to_user,
llmCalled,
resetKind: resetDecision.kind
};
}

if (typeof process !== 'undefined' && process.argv[1] && import.meta.url === new URL(process.argv[1], 'file://').href) {
const result = runExample03();
console.log('example 03: ambiguity with clarification');
console.log(JSON.stringify(result, null, 2));
}
30 changes: 30 additions & 0 deletions examples/04_tool_governance_denylist.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
import { createEngine, getPolicyItems } from '../src/index.js';

declare const process: { argv: string[] };

export function runExample04(): {
decisionKind: string;
blockedTools: string[];
allowedTools: string[];
} {
const engine = createEngine();

const decision = engine.step('prohibit docker');
const prohibited = new Set(getPolicyItems(engine.state, 'prohibit'));

const tools = ['docker', 'kubectl'];
const blockedTools = tools.filter((tool) => prohibited.has(tool));
const allowedTools = tools.filter((tool) => !prohibited.has(tool));

return {
decisionKind: decision.kind,
blockedTools,
allowedTools
};
}

if (typeof process !== 'undefined' && process.argv[1] && import.meta.url === new URL(process.argv[1], 'file://').href) {
const result = runExample04();
console.log('example 04: tool governance denylist');
console.log(JSON.stringify(result, null, 2));
}
42 changes: 42 additions & 0 deletions examples/05_llm_integration_pattern.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
import { createEngine } from '../src/index.js';

declare const process: { argv: string[] };

type HostAction = 'call_llm_without_state' | 'call_llm_with_state' | 'show_clarify_prompt';

function handleTurn(engine: ReturnType<typeof createEngine>, input: string): HostAction {
const decision = engine.step(input);
if (decision.kind === 'passthrough') {
return 'call_llm_without_state';
}
if (decision.kind === 'update') {
return 'call_llm_with_state';
}
return 'show_clarify_prompt';
}

export function runExample05(): {
actions: HostAction[];
finalState: ReturnType<typeof createEngine>['state'];
} {
const engine = createEngine();

const actions: HostAction[] = [];
actions.push(handleTurn(engine, 'hello there'));
actions.push(handleTurn(engine, 'set premise concise replies'));
actions.push(handleTurn(engine, 'prohibit peanuts'));
actions.push(handleTurn(engine, 'remove policy peanuts'));
actions.push(handleTurn(engine, 'use peanuts'));
actions.push(handleTurn(engine, 'clear state'));

return {
actions,
finalState: engine.state
};
}

if (typeof process !== 'undefined' && process.argv[1] && import.meta.url === new URL(process.argv[1], 'file://').href) {
const result = runExample05();
console.log('example 05: llm integration pattern');
console.log(JSON.stringify(result, null, 2));
}
72 changes: 72 additions & 0 deletions examples/06_transcript_replay.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
import { compile_transcript, createEngine, type TranscriptResult } from '../src/index.js';

declare const process: { argv: string[] };

type TranscriptMessage = {
role: string;
content: unknown;
};

function applyTranscriptOnCurrentEngine(
engine: ReturnType<typeof createEngine>,
messages: TranscriptMessage[]
): TranscriptResult {
for (const message of messages) {
if (message.role !== 'user' || typeof message.content !== 'string') {
continue;
}
const decision = engine.step(message.content);
if (decision.kind === 'clarify') {
return {
kind: 'confirm',
prompt_to_user: decision.prompt_to_user as string
};
}
}

return {
kind: 'state',
state: engine.state
};
}

export function runExample06(): {
freshReplayKind: string;
currentReplayKind: string;
freshPolicies: string[];
currentPolicies: string[];
} {
const transcript: TranscriptMessage[] = [
{ role: 'system', content: 'System prompt' },
{ role: 'user', content: 'prohibit peanuts' },
{ role: 'assistant', content: 'Understood' },
{ role: 'user', content: 'set premise vegetarian curry' },
{ role: 'user', content: 'change premise to vegan curry' }
];

const freshReplay = compile_transcript(transcript);

const engine = createEngine();
engine.step('prohibit shellfish');
const currentReplay = applyTranscriptOnCurrentEngine(engine, transcript);

const freshPolicies =
freshReplay.kind === 'state' ? Object.keys(freshReplay.state.policies).sort((a, b) => a.localeCompare(b)) : [];
const currentPolicies =
currentReplay.kind === 'state'
? Object.keys(currentReplay.state.policies).sort((a, b) => a.localeCompare(b))
: [];

return {
freshReplayKind: freshReplay.kind,
currentReplayKind: currentReplay.kind,
freshPolicies,
currentPolicies
};
}

if (typeof process !== 'undefined' && process.argv[1] && import.meta.url === new URL(process.argv[1], 'file://').href) {
const result = runExample06();
console.log('example 06: transcript replay');
console.log(JSON.stringify(result, null, 2));
}
25 changes: 25 additions & 0 deletions examples/07_single_policy_correction.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
import { createEngine } from '../src/index.js';

declare const process: { argv: string[] };

export function runExample07(): {
stepKinds: string[];
finalPolicy: string | null;
} {
const engine = createEngine();

const decision1 = engine.step('prohibit peanuts');
const decision2 = engine.step('remove policy peanuts');
const decision3 = engine.step('use peanuts');

return {
stepKinds: [decision1.kind, decision2.kind, decision3.kind],
finalPolicy: engine.state.policies.peanuts ?? null
};
}

if (typeof process !== 'undefined' && process.argv[1] && import.meta.url === new URL(process.argv[1], 'file://').href) {
const result = runExample07();
console.log('example 07: single policy correction');
console.log(JSON.stringify(result, null, 2));
}
37 changes: 37 additions & 0 deletions examples/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
# Examples

TypeScript examples showing host-side usage of the Context Compiler core API.

These examples target Python 0.5 semantic compatibility and only use core APIs.

## 01_persistent_guardrails.ts

Demonstrates how a prohibition persists as authoritative state across later turns.

## 02_configuration_and_correction.ts

Demonstrates explicit premise lifecycle in 0.5:
`set premise ...` followed by `change premise to ...`.

## 03_ambiguity_with_clarification.ts

Demonstrates contradiction clarify behavior before state mutation.
Shows host-side clarify handling and LLM-call blocking behavior.

## 04_tool_governance_denylist.ts

Demonstrates policy-based tool governance using prohibition directives.

## 05_llm_integration_pattern.ts

Demonstrates end-to-end host control flow around `Decision.kind` outcomes.
Includes single-item correction with `remove policy <item>`.

## 06_transcript_replay.ts

Demonstrates transcript replay behavior with `compile_transcript(messages)` and replay on current engine state via `engine.step(...)`.

## 07_single_policy_correction.ts

Demonstrates explicit single-policy correction without `reset policies`:
`prohibit peanuts` -> `remove policy peanuts` -> `use peanuts`.
1 change: 1 addition & 0 deletions index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export * from './src/index.js';
81 changes: 81 additions & 0 deletions tests/examples-smoke.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
import { spawnSync } from 'node:child_process';
import { resolve } from 'node:path';

import { beforeAll, describe, expect, it } from 'vitest';

const ROOT = resolve(process.cwd());
const DIST_EXAMPLES = resolve(ROOT, 'dist', 'examples');

function runExampleScript(file: string): { status: number | null; stdout: string; stderr: string } {
const script = resolve(DIST_EXAMPLES, file);
const run = spawnSync(process.execPath, [script], {
cwd: ROOT,
encoding: 'utf8'
});
return {
status: run.status,
stdout: run.stdout ?? '',
stderr: run.stderr ?? ''
};
}

describe('examples smoke', () => {
beforeAll(() => {
const build = spawnSync('npm', ['run', 'build'], {
cwd: ROOT,
encoding: 'utf8'
});
if (build.status !== 0) {
throw new Error(`Build failed.\nSTDOUT:\n${build.stdout}\nSTDERR:\n${build.stderr}`);
}
}, 120_000);

it('01 persistent guardrails', () => {
const run = runExampleScript('01_persistent_guardrails.js');
expect(run.status).toBe(0);
expect(run.stdout).toContain('example 01: persistent guardrails');
expect(run.stdout).toContain('"prohibitedPolicies"');
});

it('02 configuration and correction', () => {
const run = runExampleScript('02_configuration_and_correction.js');
expect(run.status).toBe(0);
expect(run.stdout).toContain('example 02: configuration and correction');
expect(run.stdout).toContain('"finalPremise": "vegan curry"');
});

it('03 ambiguity with clarification', () => {
const run = runExampleScript('03_ambiguity_with_clarification.js');
expect(run.status).toBe(0);
expect(run.stdout).toContain('example 03: ambiguity with clarification');
expect(run.stdout).toContain('"clarifyKind": "clarify"');
});

it('04 tool governance denylist', () => {
const run = runExampleScript('04_tool_governance_denylist.js');
expect(run.status).toBe(0);
expect(run.stdout).toContain('example 04: tool governance denylist');
expect(run.stdout).toContain('"blockedTools"');
});

it('05 llm integration pattern', () => {
const run = runExampleScript('05_llm_integration_pattern.js');
expect(run.status).toBe(0);
expect(run.stdout).toContain('example 05: llm integration pattern');
expect(run.stdout).toContain('"actions"');
});

it('06 transcript replay', () => {
const run = runExampleScript('06_transcript_replay.js');
expect(run.status).toBe(0);
expect(run.stdout).toContain('example 06: transcript replay');
expect(run.stdout).toContain('"freshReplayKind": "state"');
});

it('07 single policy correction', () => {
const run = runExampleScript('07_single_policy_correction.js');
expect(run.status).toBe(0);
expect(run.stdout).toContain('example 07: single policy correction');
expect(run.stdout).toContain('"finalPolicy": "use"');
});
});
4 changes: 2 additions & 2 deletions tsconfig.build.json
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,11 @@
"extends": "./tsconfig.json",
"compilerOptions": {
"outDir": "dist",
"rootDir": "src",
"rootDir": ".",
"declaration": true,
"declarationMap": false,
"types": []
},
"include": ["src/**/*.ts"],
"include": ["index.ts", "src/**/*.ts", "examples/**/*.ts"],
"exclude": ["tests", "dist", "node_modules"]
}
Loading