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
2 changes: 1 addition & 1 deletion .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ jobs:
runs-on: ubuntu-latest
timeout-minutes: 15
env:
PY_FIXTURE_REF: 0e923284d4c1194a1f390fd75cd361cb48c8efd3
PY_FIXTURE_REF: 6ca2ad74136b46a4da3b67061ecfd835e0ccf0b8
PY_FIXTURE_CHECKOUT: /tmp/context-compiler-source

steps:
Expand Down
8 changes: 4 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -73,16 +73,16 @@ npm install @rlippmann/context-compiler
## Quick Start

```ts
import { createEngine } from '@rlippmann/context-compiler';
import { createEngine, get_clarify_prompt, is_clarify, is_update } from '@rlippmann/context-compiler';

const engine = createEngine();
const decision = engine.step('set premise concise replies');

if (decision.kind === 'update') {
if (is_update(decision)) {
// Use the updated stored rules from the engine.
console.log(engine.state);
} else if (decision.kind === 'clarify') {
console.log(decision.prompt_to_user);
} else if (is_clarify(decision)) {
console.log(get_clarify_prompt(decision));
} else {
// passthrough
}
Expand Down
34 changes: 22 additions & 12 deletions demos/common.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,13 @@
import { createEngine, getPolicyItems, getPremiseValue } from '../src/index.js';
import {
POLICY_PROHIBIT,
POLICY_USE,
createEngine,
getPolicyItems,
getPremiseValue,
get_clarify_prompt,
is_clarify,
is_update
} from '../src/index.js';
import type { Decision, EngineState } from '../src/index.js';
import type { Message } from './llm_client.js';

Expand Down Expand Up @@ -41,8 +50,8 @@ function printStateSummary(state: EngineState): void {
const premiseText = premiseValue ?? '(none)';
console.log('compiled state:');
console.log(`- premise: ${premiseText}`);
console.log(`- use policies: ${policyValuesText(state, 'use')}`);
console.log(`- prohibit policies: ${policyValuesText(state, 'prohibit')}`);
console.log(`- use policies: ${policyValuesText(state, POLICY_USE)}`);
console.log(`- prohibit policies: ${policyValuesText(state, POLICY_PROHIBIT)}`);
}

function printMultilinePrompt(label: string, prompt: string): void {
Expand Down Expand Up @@ -73,13 +82,14 @@ export function printDecision(title: string, decision: Decision, state: EngineSt
return;
}
console.log(`Compiler decision (${title}):`);
if (decision.kind === 'update') {
if (is_update(decision)) {
console.log('result: updated');
printStateSummary(state);
} else if (decision.kind === 'clarify') {
} else if (is_clarify(decision)) {
console.log('result: clarify');
if (decision.prompt_to_user) {
printMultilinePrompt('clarify prompt', decision.prompt_to_user);
const clarifyPrompt = get_clarify_prompt(decision);
if (clarifyPrompt) {
printMultilinePrompt('clarify prompt', clarifyPrompt);
}
printStateSummary(state);
} else {
Expand Down Expand Up @@ -227,12 +237,12 @@ export function compactUserTurns(userTurns: string[]): {

for (const turn of userTurns) {
const decision = engine.step(turn);
if (decision.kind === 'update') {
if (is_update(decision)) {
continue;
}
compactedTurns.push(turn);
if (decision.kind === 'clarify') {
promptToUser = decision.prompt_to_user;
if (is_clarify(decision)) {
promptToUser = get_clarify_prompt(decision);
break;
}
}
Expand All @@ -246,8 +256,8 @@ export function compactUserTurns(userTurns: string[]): {

export function buildCompiledSystemPrompt(state: EngineState): string {
const premise = getPremiseValue(state) ?? '(unset)';
const useItems = getPolicyItems(state, 'use');
const prohibitItems = getPolicyItems(state, 'prohibit');
const useItems = getPolicyItems(state, POLICY_USE);
const prohibitItems = getPolicyItems(state, POLICY_PROHIBIT);
const useText = useItems.length > 0 ? useItems.join(', ') : '(none)';
const prohibitText = prohibitItems.length > 0 ? prohibitItems.join(', ') : '(none)';

Expand Down
6 changes: 3 additions & 3 deletions examples/03_ambiguity_with_clarification.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { createEngine } from '../src/index.js';
import { createEngine, get_clarify_prompt, is_clarify } from '../src/index.js';

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

Expand All @@ -14,15 +14,15 @@ export function runExample03(): {
const contradictionDecision = engine.step('use peanuts');

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

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

return {
clarifyKind: contradictionDecision.kind,
clarifyPrompt: contradictionDecision.prompt_to_user,
clarifyPrompt: get_clarify_prompt(contradictionDecision),
llmCalled,
resetKind: resetDecision.kind
};
Expand Down
4 changes: 2 additions & 2 deletions examples/04_tool_governance_denylist.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { createEngine, getPolicyItems } from '../src/index.js';
import { POLICY_PROHIBIT, createEngine, getPolicyItems } from '../src/index.js';

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

Expand All @@ -10,7 +10,7 @@ export function runExample04(): {
const engine = createEngine();

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

const tools = ['docker', 'kubectl'];
const blockedTools = tools.filter((tool) => prohibited.has(tool));
Expand Down
6 changes: 3 additions & 3 deletions examples/05_llm_integration_pattern.ts
Original file line number Diff line number Diff line change
@@ -1,15 +1,15 @@
import { createEngine } from '../src/index.js';
import { createEngine, is_passthrough, is_update } 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') {
if (is_passthrough(decision)) {
return 'call_llm_without_state';
}
if (decision.kind === 'update') {
if (is_update(decision)) {
return 'call_llm_with_state';
}
return 'show_clarify_prompt';
Expand Down
8 changes: 4 additions & 4 deletions examples/07_single_policy_correction.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { createEngine, getPolicyItems } from '../src/index.js';
import { POLICY_PROHIBIT, POLICY_USE, createEngine, getPolicyItems } from '../src/index.js';

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

Expand All @@ -11,12 +11,12 @@ export function runExample07(): {
const decision1 = engine.step('prohibit peanuts');
const decision2 = engine.step('remove policy peanuts');
const decision3 = engine.step('use peanuts');
const useItems = getPolicyItems(engine.state, 'use');
const prohibitItems = getPolicyItems(engine.state, 'prohibit');
const useItems = getPolicyItems(engine.state, POLICY_USE);
const prohibitItems = getPolicyItems(engine.state, POLICY_PROHIBIT);

return {
stepKinds: [decision1.kind, decision2.kind, decision3.kind],
finalPolicy: useItems.includes('peanuts') ? 'use' : prohibitItems.includes('peanuts') ? 'prohibit' : null
finalPolicy: useItems.includes('peanuts') ? POLICY_USE : prohibitItems.includes('peanuts') ? POLICY_PROHIBIT : null
};
}

Expand Down
20 changes: 14 additions & 6 deletions examples/nextjs-basic/app/api/chat/route.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,12 @@
import { createEngine, getPolicyItems, getPremiseValue, type EngineState } from '@rlippmann/context-compiler';
import {
DECISION_CLARIFY,
POLICY_USE,
createEngine,
getPolicyItems,
getPremiseValue,
is_clarify,
type EngineState
} from '@rlippmann/context-compiler';
import { loadSessionState, saveSessionState } from '../../../lib/context-sessions';

type ChatBody = {
Expand All @@ -8,11 +16,11 @@ type ChatBody = {
};

type ChatResponse =
| { kind: 'clarify'; prompt_to_user: string | null }
| { kind: typeof DECISION_CLARIFY; prompt_to_user: string | null }
| { kind: 'continue'; output: string };

function stateToSystemPrompt(state: EngineState): string {
const useItems = new Set(getPolicyItems(state, 'use'));
const useItems = new Set(getPolicyItems(state, POLICY_USE));
const policies = getPolicyItems(state)
.map((item) => `- ${useItems.has(item) ? 'USE' : 'PROHIBIT'}: ${item}`)
.join('\n');
Expand Down Expand Up @@ -64,7 +72,7 @@ export async function POST(req: Request): Promise<Response> {
if (replay.kind === 'confirm') {
saveSessionState(sessionId, engine.exportCheckpointJson());
const payload: ChatResponse = {
kind: 'clarify',
kind: DECISION_CLARIFY,
prompt_to_user: replay.prompt_to_user
};
return Response.json(payload);
Expand All @@ -75,10 +83,10 @@ export async function POST(req: Request): Promise<Response> {

const decision = engine.step(input);

if (decision.kind === 'clarify') {
if (is_clarify(decision)) {
saveSessionState(sessionId, engine.exportCheckpointJson());
const payload: ChatResponse = {
kind: 'clarify',
kind: DECISION_CLARIFY,
prompt_to_user: decision.prompt_to_user
};
return Response.json(payload);
Expand Down
20 changes: 14 additions & 6 deletions examples/node-basic/server.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,13 @@
import http from 'node:http';
import { createEngine, getPolicyItems, getPremiseValue, type EngineState } from '@rlippmann/context-compiler';
import {
DECISION_CLARIFY,
POLICY_USE,
createEngine,
getPolicyItems,
getPremiseValue,
is_clarify,
type EngineState
} from '@rlippmann/context-compiler';
import {
parse_preprocessor_output,
preprocess_heuristic
Expand All @@ -12,7 +20,7 @@ type ChatBody = {
};

type ChatResponse =
| { kind: 'clarify'; prompt_to_user: string | null }
| { kind: typeof DECISION_CLARIFY; prompt_to_user: string | null }
| { kind: 'continue'; output: string };

const checkpointBySession = new Map<string, string>(); // sessionId -> engine.exportCheckpointJson()
Expand All @@ -26,7 +34,7 @@ function saveCheckpoint(sessionId: string, json: string): void {
}

function stateToSystemPrompt(state: EngineState): string {
const useItems = new Set(getPolicyItems(state, 'use'));
const useItems = new Set(getPolicyItems(state, POLICY_USE));
const policies = getPolicyItems(state)
.map((item: string) => `- ${useItems.has(item) ? 'USE' : 'PROHIBIT'}: ${item}`)
.join('\n');
Expand Down Expand Up @@ -103,7 +111,7 @@ const server = http.createServer(async (req, res) => {
const replay = engine.apply_transcript(replayMessages);
if (replay.kind === 'confirm') {
saveCheckpoint(sessionId, engine.exportCheckpointJson());
const payload: ChatResponse = { kind: 'clarify', prompt_to_user: replay.prompt_to_user };
const payload: ChatResponse = { kind: DECISION_CLARIFY, prompt_to_user: replay.prompt_to_user };
sendJson(res, 200, payload);
return;
}
Expand All @@ -112,9 +120,9 @@ const server = http.createServer(async (req, res) => {

const preprocessedInput = normalizeInputWithPreprocessor(input);
const decision = engine.step(preprocessedInput);
if (decision.kind === 'clarify') {
if (is_clarify(decision)) {
saveCheckpoint(sessionId, engine.exportCheckpointJson());
const payload: ChatResponse = { kind: 'clarify', prompt_to_user: decision.prompt_to_user };
const payload: ChatResponse = { kind: DECISION_CLARIFY, prompt_to_user: decision.prompt_to_user };
sendJson(res, 200, payload);
return;
}
Expand Down
24 changes: 24 additions & 0 deletions src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,33 @@ export { createEngine, compile_transcript, getPremiseValue, getPolicyItems } fro
export { OUTPUT_VERSION, preview, state_diff, step } from './controller.js';
export type { Engine, EngineInit } from './engine.js';
export type { PreviewResult, StepResult, StructuralDiff } from './controller.js';
import type { Decision, EngineState } from './types.js';
export const DECISION_PASSTHROUGH = 'passthrough' as const;
export const DECISION_UPDATE = 'update' as const;
export const DECISION_CLARIFY = 'clarify' as const;
export const POLICY_USE = 'use' as const;
export const POLICY_PROHIBIT = 'prohibit' as const;

export function is_update(decision: Decision): boolean {
return decision.kind === DECISION_UPDATE;
}

export function is_clarify(decision: Decision): boolean {
return decision.kind === DECISION_CLARIFY;
}

export function is_passthrough(decision: Decision): boolean {
return decision.kind === DECISION_PASSTHROUGH;
}

export function get_clarify_prompt(decision: Decision): string | null {
return is_clarify(decision) ? decision.prompt_to_user : null;
}

export function get_decision_state(decision: Decision): EngineState | null {
return decision.state;
}

export type {
ApplyResult,
CheckpointPendingReplacement,
Expand Down
7 changes: 7 additions & 0 deletions tests/api_parity.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,9 +22,16 @@ const PYTHON_TO_TS_EXPORT_MAP: Record<string, string> = {
compile_transcript: 'compile_transcript',
get_premise_value: 'getPremiseValue',
get_policy_items: 'getPolicyItems',
is_update: 'is_update',
is_clarify: 'is_clarify',
is_passthrough: 'is_passthrough',
get_clarify_prompt: 'get_clarify_prompt',
get_decision_state: 'get_decision_state',
DECISION_PASSTHROUGH: 'DECISION_PASSTHROUGH',
DECISION_UPDATE: 'DECISION_UPDATE',
DECISION_CLARIFY: 'DECISION_CLARIFY',
POLICY_USE: 'POLICY_USE',
POLICY_PROHIBIT: 'POLICY_PROHIBIT',
TranscriptMessage: '__type_only__',
Transcript: '__type_only__',
ApplyResult: '__type_only__'
Expand Down
2 changes: 1 addition & 1 deletion tests/fixtures/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ This directory contains multiple fixture suites with different contracts.

## API contract fixture

[`conformance/api/public-api-v1.json`](conformance/api/public-api-v1.json) defines a small public API presence contract for the Python 0.6 surface that ports must expose.
[`conformance/api/public-api-v1.json`](conformance/api/public-api-v1.json) defines a small public API presence contract for the Python 0.7.x surface that ports must expose.

Ports may sync this artifact with conformance fixtures.

Expand Down
7 changes: 7 additions & 0 deletions tests/fixtures/conformance/api/public-api-v1.json
Original file line number Diff line number Diff line change
Expand Up @@ -8,9 +8,16 @@
"compile_transcript",
"get_premise_value",
"get_policy_items",
"is_update",
"is_clarify",
"is_passthrough",
"get_clarify_prompt",
"get_decision_state",
"DECISION_PASSTHROUGH",
"DECISION_UPDATE",
"DECISION_CLARIFY",
"POLICY_USE",
"POLICY_PROHIBIT",
"TranscriptMessage",
"Transcript",
"ApplyResult"
Expand Down
Loading