From 760b3f6118c9e5dca4d9526a2af28b4358ec5d7b Mon Sep 17 00:00:00 2001 From: Robert Lippmann Date: Fri, 17 Apr 2026 02:37:27 -0400 Subject: [PATCH 1/4] feat: port core 0.5 examples to typescript --- examples/01_persistent_guardrails.ts | 18 ++++++ examples/02_configuration_and_correction.ts | 18 ++++++ examples/03_ambiguity_with_clarification.ts | 27 +++++++++ examples/04_tool_governance_denylist.ts | 22 +++++++ examples/05_llm_integration_pattern.ts | 34 +++++++++++ examples/06_transcript_replay.ts | 64 +++++++++++++++++++++ examples/07_single_policy_correction.ts | 17 ++++++ examples/README.md | 37 ++++++++++++ 8 files changed, 237 insertions(+) create mode 100644 examples/01_persistent_guardrails.ts create mode 100644 examples/02_configuration_and_correction.ts create mode 100644 examples/03_ambiguity_with_clarification.ts create mode 100644 examples/04_tool_governance_denylist.ts create mode 100644 examples/05_llm_integration_pattern.ts create mode 100644 examples/06_transcript_replay.ts create mode 100644 examples/07_single_policy_correction.ts create mode 100644 examples/README.md diff --git a/examples/01_persistent_guardrails.ts b/examples/01_persistent_guardrails.ts new file mode 100644 index 0000000..cf38f7a --- /dev/null +++ b/examples/01_persistent_guardrails.ts @@ -0,0 +1,18 @@ +import { createEngine, getPolicyItems } from '../src/index.js'; + +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') + }; +} diff --git a/examples/02_configuration_and_correction.ts b/examples/02_configuration_and_correction.ts new file mode 100644 index 0000000..54bf87f --- /dev/null +++ b/examples/02_configuration_and_correction.ts @@ -0,0 +1,18 @@ +import { createEngine, getPremiseValue } from '../src/index.js'; + +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) + }; +} diff --git a/examples/03_ambiguity_with_clarification.ts b/examples/03_ambiguity_with_clarification.ts new file mode 100644 index 0000000..b5366d1 --- /dev/null +++ b/examples/03_ambiguity_with_clarification.ts @@ -0,0 +1,27 @@ +import { createEngine } from '../src/index.js'; + +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 + }; +} diff --git a/examples/04_tool_governance_denylist.ts b/examples/04_tool_governance_denylist.ts new file mode 100644 index 0000000..347fe96 --- /dev/null +++ b/examples/04_tool_governance_denylist.ts @@ -0,0 +1,22 @@ +import { createEngine, getPolicyItems } from '../src/index.js'; + +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 + }; +} diff --git a/examples/05_llm_integration_pattern.ts b/examples/05_llm_integration_pattern.ts new file mode 100644 index 0000000..867433d --- /dev/null +++ b/examples/05_llm_integration_pattern.ts @@ -0,0 +1,34 @@ +import { createEngine } from '../src/index.js'; + +type HostAction = 'call_llm_without_state' | 'call_llm_with_state' | 'show_clarify_prompt'; + +function handleTurn(engine: ReturnType, 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['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 + }; +} diff --git a/examples/06_transcript_replay.ts b/examples/06_transcript_replay.ts new file mode 100644 index 0000000..c8c2bd2 --- /dev/null +++ b/examples/06_transcript_replay.ts @@ -0,0 +1,64 @@ +import { compile_transcript, createEngine, type TranscriptResult } from '../src/index.js'; + +type TranscriptMessage = { + role: string; + content: unknown; +}; + +function applyTranscriptOnCurrentEngine( + engine: ReturnType, + 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 + }; +} diff --git a/examples/07_single_policy_correction.ts b/examples/07_single_policy_correction.ts new file mode 100644 index 0000000..9d12fe7 --- /dev/null +++ b/examples/07_single_policy_correction.ts @@ -0,0 +1,17 @@ +import { createEngine } from '../src/index.js'; + +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 + }; +} diff --git a/examples/README.md b/examples/README.md new file mode 100644 index 0000000..36795b3 --- /dev/null +++ b/examples/README.md @@ -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 `. + +## 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`. From adf96851f93e619dd2158d128ae76f99e5ae44d7 Mon Sep 17 00:00:00 2001 From: Robert Lippmann Date: Fri, 17 Apr 2026 02:37:27 -0400 Subject: [PATCH 2/4] test: add smoke tests for core examples --- tests/examples-smoke.test.ts | 53 ++++++++++++++++++++++++++++++++++++ 1 file changed, 53 insertions(+) create mode 100644 tests/examples-smoke.test.ts diff --git a/tests/examples-smoke.test.ts b/tests/examples-smoke.test.ts new file mode 100644 index 0000000..30c8f23 --- /dev/null +++ b/tests/examples-smoke.test.ts @@ -0,0 +1,53 @@ +import { describe, expect, it } from 'vitest'; + +import { runExample01 } from '../examples/01_persistent_guardrails.js'; +import { runExample02 } from '../examples/02_configuration_and_correction.js'; +import { runExample03 } from '../examples/03_ambiguity_with_clarification.js'; +import { runExample04 } from '../examples/04_tool_governance_denylist.js'; +import { runExample05 } from '../examples/05_llm_integration_pattern.js'; +import { runExample06 } from '../examples/06_transcript_replay.js'; +import { runExample07 } from '../examples/07_single_policy_correction.js'; + +describe('examples smoke', () => { + it('01 persistent guardrails', () => { + const result = runExample01(); + expect(result.turn1Kind).toBe('update'); + expect(result.prohibitedPolicies).toContain('peanuts'); + }); + + it('02 configuration and correction', () => { + const result = runExample02(); + expect(result.setKind).toBe('update'); + expect(result.finalPremise).toBe('vegan curry'); + }); + + it('03 ambiguity with clarification', () => { + const result = runExample03(); + expect(result.clarifyKind).toBe('clarify'); + expect(result.llmCalled).toBe(false); + }); + + it('04 tool governance denylist', () => { + const result = runExample04(); + expect(result.decisionKind).toBe('update'); + expect(result.blockedTools).toContain('docker'); + }); + + it('05 llm integration pattern', () => { + const result = runExample05(); + expect(result.actions[0]).toBe('call_llm_without_state'); + expect(result.finalState.premise).toBeNull(); + }); + + it('06 transcript replay', () => { + const result = runExample06(); + expect(result.freshReplayKind).toBe('state'); + expect(result.currentPolicies).toContain('shellfish'); + }); + + it('07 single policy correction', () => { + const result = runExample07(); + expect(result.stepKinds).toEqual(['update', 'update', 'update']); + expect(result.finalPolicy).toBe('use'); + }); +}); From 808124b5386f4553c9d2b3c28b1725a4e62d187e Mon Sep 17 00:00:00 2001 From: Robert Lippmann Date: Fri, 17 Apr 2026 02:51:22 -0400 Subject: [PATCH 3/4] chore: make examples directly runnable from dist --- examples/01_persistent_guardrails.ts | 8 ++++++++ examples/02_configuration_and_correction.ts | 8 ++++++++ examples/03_ambiguity_with_clarification.ts | 8 ++++++++ examples/04_tool_governance_denylist.ts | 8 ++++++++ examples/05_llm_integration_pattern.ts | 8 ++++++++ examples/06_transcript_replay.ts | 8 ++++++++ examples/07_single_policy_correction.ts | 8 ++++++++ index.ts | 1 + tsconfig.build.json | 4 ++-- 9 files changed, 59 insertions(+), 2 deletions(-) create mode 100644 index.ts diff --git a/examples/01_persistent_guardrails.ts b/examples/01_persistent_guardrails.ts index cf38f7a..bf67f43 100644 --- a/examples/01_persistent_guardrails.ts +++ b/examples/01_persistent_guardrails.ts @@ -1,5 +1,7 @@ import { createEngine, getPolicyItems } from '../src/index.js'; +declare const process: { argv: string[] }; + export function runExample01(): { turn1Kind: string; turn2Kind: string; @@ -16,3 +18,9 @@ export function runExample01(): { 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)); +} diff --git a/examples/02_configuration_and_correction.ts b/examples/02_configuration_and_correction.ts index 54bf87f..f3d7981 100644 --- a/examples/02_configuration_and_correction.ts +++ b/examples/02_configuration_and_correction.ts @@ -1,5 +1,7 @@ import { createEngine, getPremiseValue } from '../src/index.js'; +declare const process: { argv: string[] }; + export function runExample02(): { setKind: string; changeKind: string; @@ -16,3 +18,9 @@ export function runExample02(): { 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)); +} diff --git a/examples/03_ambiguity_with_clarification.ts b/examples/03_ambiguity_with_clarification.ts index b5366d1..0c504fe 100644 --- a/examples/03_ambiguity_with_clarification.ts +++ b/examples/03_ambiguity_with_clarification.ts @@ -1,5 +1,7 @@ import { createEngine } from '../src/index.js'; +declare const process: { argv: string[] }; + export function runExample03(): { clarifyKind: string; clarifyPrompt: string | null; @@ -25,3 +27,9 @@ export function runExample03(): { 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)); +} diff --git a/examples/04_tool_governance_denylist.ts b/examples/04_tool_governance_denylist.ts index 347fe96..a02b8cb 100644 --- a/examples/04_tool_governance_denylist.ts +++ b/examples/04_tool_governance_denylist.ts @@ -1,5 +1,7 @@ import { createEngine, getPolicyItems } from '../src/index.js'; +declare const process: { argv: string[] }; + export function runExample04(): { decisionKind: string; blockedTools: string[]; @@ -20,3 +22,9 @@ export function runExample04(): { 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)); +} diff --git a/examples/05_llm_integration_pattern.ts b/examples/05_llm_integration_pattern.ts index 867433d..2418e45 100644 --- a/examples/05_llm_integration_pattern.ts +++ b/examples/05_llm_integration_pattern.ts @@ -1,5 +1,7 @@ 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, input: string): HostAction { @@ -32,3 +34,9 @@ export function runExample05(): { 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)); +} diff --git a/examples/06_transcript_replay.ts b/examples/06_transcript_replay.ts index c8c2bd2..f2418db 100644 --- a/examples/06_transcript_replay.ts +++ b/examples/06_transcript_replay.ts @@ -1,5 +1,7 @@ import { compile_transcript, createEngine, type TranscriptResult } from '../src/index.js'; +declare const process: { argv: string[] }; + type TranscriptMessage = { role: string; content: unknown; @@ -62,3 +64,9 @@ export function runExample06(): { 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)); +} diff --git a/examples/07_single_policy_correction.ts b/examples/07_single_policy_correction.ts index 9d12fe7..1fc5d64 100644 --- a/examples/07_single_policy_correction.ts +++ b/examples/07_single_policy_correction.ts @@ -1,5 +1,7 @@ import { createEngine } from '../src/index.js'; +declare const process: { argv: string[] }; + export function runExample07(): { stepKinds: string[]; finalPolicy: string | null; @@ -15,3 +17,9 @@ export function runExample07(): { 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)); +} diff --git a/index.ts b/index.ts new file mode 100644 index 0000000..4df73b3 --- /dev/null +++ b/index.ts @@ -0,0 +1 @@ +export * from './src/index.js'; diff --git a/tsconfig.build.json b/tsconfig.build.json index 0527ad6..59a87a6 100644 --- a/tsconfig.build.json +++ b/tsconfig.build.json @@ -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"] } From 094f8908d804480793b1f03967ef6e906f794d2f Mon Sep 17 00:00:00 2001 From: Robert Lippmann Date: Fri, 17 Apr 2026 02:57:55 -0400 Subject: [PATCH 4/4] test: run examples as built-script smoke checks --- tests/examples-smoke.test.ts | 86 ++++++++++++++++++++++++------------ 1 file changed, 57 insertions(+), 29 deletions(-) diff --git a/tests/examples-smoke.test.ts b/tests/examples-smoke.test.ts index 30c8f23..29bc711 100644 --- a/tests/examples-smoke.test.ts +++ b/tests/examples-smoke.test.ts @@ -1,53 +1,81 @@ -import { describe, expect, it } from 'vitest'; +import { spawnSync } from 'node:child_process'; +import { resolve } from 'node:path'; -import { runExample01 } from '../examples/01_persistent_guardrails.js'; -import { runExample02 } from '../examples/02_configuration_and_correction.js'; -import { runExample03 } from '../examples/03_ambiguity_with_clarification.js'; -import { runExample04 } from '../examples/04_tool_governance_denylist.js'; -import { runExample05 } from '../examples/05_llm_integration_pattern.js'; -import { runExample06 } from '../examples/06_transcript_replay.js'; -import { runExample07 } from '../examples/07_single_policy_correction.js'; +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 result = runExample01(); - expect(result.turn1Kind).toBe('update'); - expect(result.prohibitedPolicies).toContain('peanuts'); + 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 result = runExample02(); - expect(result.setKind).toBe('update'); - expect(result.finalPremise).toBe('vegan curry'); + 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 result = runExample03(); - expect(result.clarifyKind).toBe('clarify'); - expect(result.llmCalled).toBe(false); + 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 result = runExample04(); - expect(result.decisionKind).toBe('update'); - expect(result.blockedTools).toContain('docker'); + 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 result = runExample05(); - expect(result.actions[0]).toBe('call_llm_without_state'); - expect(result.finalState.premise).toBeNull(); + 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 result = runExample06(); - expect(result.freshReplayKind).toBe('state'); - expect(result.currentPolicies).toContain('shellfish'); + 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 result = runExample07(); - expect(result.stepKinds).toEqual(['update', 'update', 'update']); - expect(result.finalPolicy).toBe('use'); + 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"'); }); });