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
24 changes: 10 additions & 14 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -74,17 +74,17 @@ npm install @rlippmann/context-compiler
## Quick Start

```ts
import { createEngine, get_clarify_prompt, is_clarify, is_passthrough, is_update } from '@rlippmann/context-compiler';
import { createEngine, getClarifyPrompt, isClarify, isPassthrough, isUpdate } from '@rlippmann/context-compiler';

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

if (is_update(decision)) {
if (isUpdate(decision)) {
// Use the updated stored rules from the engine.
console.log(engine.state);
} else if (is_clarify(decision)) {
console.log(get_clarify_prompt(decision));
} else if (is_passthrough(decision)) {
} else if (isClarify(decision)) {
console.log(getClarifyPrompt(decision));
} else if (isPassthrough(decision)) {
// passthrough
}
```
Expand All @@ -94,16 +94,16 @@ if (is_update(decision)) {
- `createEngine(init?)` -> create an engine instance.
- `engine.step(input)` -> apply one user input and return a `Decision`.
- `engine.state` -> current stored premise/policy rules snapshot.
- `engine.has_pending_clarification()` -> check whether confirmation-only input is currently required.
- `engine.hasPendingClarification()` -> check whether confirmation-only input is currently required.
- `engine.exportJson()` / `engine.importJson(payload)` -> state serialization utilities.
- `engine.exportCheckpoint()` / `engine.importCheckpoint(payload)` -> checkpoint persistence (`authoritative_state` + pending confirmation state) that safely resumes pending confirmations.
- `engine.exportCheckpointJson()` / `engine.importCheckpointJson(payload)` -> JSON checkpoint wrapper persistence helpers.
- `compile_transcript(messages)` -> replay user messages and return `state` or `confirm`.
- `engine.apply_transcript(messages)` -> replay user messages onto an existing engine instance.
- `compileTranscript(messages)` -> replay user messages and return `state` or `confirm`.
- `engine.applyTranscript(messages)` -> replay user messages onto an existing engine instance.
- `getPremiseValue(state)` / `getPolicyItems(state, value?)` -> read helpers for state.
- `step(engine, input)` -> controller step envelope (`output_version`, `mode`, `decision`, `state`).
- `preview(engine, input)` -> dry-run step envelope with `state_before`, `state_after`, `diff`, and `would_mutate` (live engine state is restored).
- `state_diff(before, after)` -> structural state diff used by preview.
- `stateDiff(before, after)` -> structural state diff used by preview.
- `DECISION_PASSTHROUGH` / `DECISION_UPDATE` / `DECISION_CLARIFY` -> decision kind constants.

## Experimental Preprocessor
Expand All @@ -120,11 +120,7 @@ Preprocessor output should always be parsed/validated before passing it to the e
Experimental preprocessor APIs are available via package subpath:

```ts
import {
preprocess_heuristic,
parse_preprocessor_output,
validate_preprocessor_output
} from '@rlippmann/context-compiler/experimental/preprocessor';
import { preprocessHeuristic, parsePreprocessorOutput, validatePreprocessorOutput } from '@rlippmann/context-compiler/experimental/preprocessor';
```

This module is intentionally experimental and separate from the deterministic core engine API.
4 changes: 2 additions & 2 deletions demos/06_llm_context_compaction.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { compile_transcript, getPremiseValue } from '../src/index.js';
import { compileTranscript, getPremiseValue } from '../src/index.js';
import { compactUserTurns, isVerbose, printInfoReport } from './common.js';

const DEMO_NAME = '06_context_compaction — superseded directives eliminated';
Expand Down Expand Up @@ -41,7 +41,7 @@ function buildTurns(turnCount: number): string[] {

function compilePremise(turns: string[]): string {
const messages: TranscriptMessage[] = turns.map((turn) => ({ role: 'user', content: turn }));
const result = compile_transcript(messages);
const result = compileTranscript(messages);
if (result.kind !== 'state') {
throw new Error('Unexpected clarification while compiling transcript');
}
Expand Down
18 changes: 9 additions & 9 deletions demos/common.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,9 @@ import {
createEngine,
getPolicyItems,
getPremiseValue,
get_clarify_prompt,
is_clarify,
is_update
getClarifyPrompt,
isClarify,
isUpdate
} from '../src/index.js';
import type { Decision, EngineState } from '../src/index.js';
import type { Message } from './llm_client.js';
Expand Down Expand Up @@ -82,12 +82,12 @@ export function printDecision(title: string, decision: Decision, state: EngineSt
return;
}
console.log(`Compiler decision (${title}):`);
if (is_update(decision)) {
if (isUpdate(decision)) {
console.log('result: updated');
printStateSummary(state);
} else if (is_clarify(decision)) {
} else if (isClarify(decision)) {
console.log('result: clarify');
const clarifyPrompt = get_clarify_prompt(decision);
const clarifyPrompt = getClarifyPrompt(decision);
if (clarifyPrompt) {
printMultilinePrompt('clarify prompt', clarifyPrompt);
}
Expand Down Expand Up @@ -237,12 +237,12 @@ export function compactUserTurns(userTurns: string[]): {

for (const turn of userTurns) {
const decision = engine.step(turn);
if (is_update(decision)) {
if (isUpdate(decision)) {
continue;
}
compactedTurns.push(turn);
if (is_clarify(decision)) {
promptToUser = get_clarify_prompt(decision);
if (isClarify(decision)) {
promptToUser = getClarifyPrompt(decision);
break;
}
}
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, get_clarify_prompt, is_clarify } from '../src/index.js';
import { createEngine, getClarifyPrompt, isClarify } 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 (!is_clarify(contradictionDecision)) {
if (!isClarify(contradictionDecision)) {
llmCalled = true;
}

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

return {
clarifyKind: contradictionDecision.kind,
clarifyPrompt: get_clarify_prompt(contradictionDecision),
clarifyPrompt: getClarifyPrompt(contradictionDecision),
llmCalled,
resetKind: resetDecision.kind
};
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, is_passthrough, is_update } from '../src/index.js';
import { createEngine, isPassthrough, isUpdate } 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 (is_passthrough(decision)) {
if (isPassthrough(decision)) {
return 'call_llm_without_state';
}
if (is_update(decision)) {
if (isUpdate(decision)) {
return 'call_llm_with_state';
}
return 'show_clarify_prompt';
Expand Down
6 changes: 3 additions & 3 deletions examples/06_transcript_replay.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { compile_transcript, createEngine, getPolicyItems, type TranscriptResult } from '../src/index.js';
import { compileTranscript, createEngine, getPolicyItems, type TranscriptResult } from '../src/index.js';

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

Expand All @@ -21,11 +21,11 @@ export function runExample06(): {
{ role: 'user', content: 'change premise to vegan curry' }
];

const freshReplay = compile_transcript(transcript);
const freshReplay = compileTranscript(transcript);

const engine = createEngine();
engine.step('prohibit shellfish');
const currentReplay: TranscriptResult = engine.apply_transcript(transcript);
const currentReplay: TranscriptResult = engine.applyTranscript(transcript);

const freshPolicies =
freshReplay.kind === 'state' ? getPolicyItems(freshReplay.state) : [];
Expand Down
20 changes: 10 additions & 10 deletions examples/08_controller_preview_diff.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,13 +6,13 @@ import {
POLICY_USE,
createEngine,
getPolicyItems,
get_clarify_prompt,
getClarifyPrompt,
getPremiseValue,
get_decision_state,
is_clarify,
is_update,
getDecisionState,
isClarify,
isUpdate,
preview,
state_diff,
stateDiff,
step,
type Decision,
type EngineState
Expand All @@ -37,19 +37,19 @@ function summarizeDecision(decision: Decision): {
promptToUser: string | null;
decisionState: ReturnType<typeof summarizeState> | null;
} {
if (is_update(decision)) {
const decisionState = get_decision_state(decision);
if (isUpdate(decision)) {
const decisionState = getDecisionState(decision);
return {
kind: DECISION_UPDATE,
promptToUser: null,
decisionState: decisionState ? summarizeState(decisionState) : null
};
}

if (is_clarify(decision)) {
if (isClarify(decision)) {
return {
kind: DECISION_CLARIFY,
promptToUser: get_clarify_prompt(decision),
promptToUser: getClarifyPrompt(decision),
decisionState: null
};
}
Expand Down Expand Up @@ -80,7 +80,7 @@ export function runExample08(): {
const previewResult = preview(engine, 'prohibit peanuts');

const stateAfterPreview = engine.state;
const diffAfterPreview = state_diff(stateBeforePreview, stateAfterPreview);
const diffAfterPreview = stateDiff(stateBeforePreview, stateAfterPreview);

const stepResult = step(engine, 'prohibit peanuts');

Expand Down
4 changes: 2 additions & 2 deletions examples/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ Includes single-item correction with `remove policy <item>`.

## 06_transcript_replay.ts

Shows transcript replay with `compile_transcript(messages)` and replay onto current engine state with `engine.apply_transcript(...)`.
Shows transcript replay with `compileTranscript(messages)` and replay onto current engine state with `engine.applyTranscript(...)`.

## 07_single_policy_correction.ts

Expand All @@ -39,7 +39,7 @@ Demonstrates explicit single-policy correction without `reset policies`:

## 08_controller_preview_diff.ts

Shows controller-layer auditability with `preview(engine, input)` and `state_diff(before, after)`.
Shows controller-layer auditability with `preview(engine, input)` and `stateDiff(before, after)`.
Shows that preview does not mutate live engine state, then applies the same input with `step(engine, input)`.

## Integrations
Expand Down
6 changes: 3 additions & 3 deletions examples/integrations/nextjs-basic/app/api/chat/route.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import {
createEngine,
getPolicyItems,
getPremiseValue,
is_clarify,
isClarify,
type EngineState
} from '@rlippmann/context-compiler';
import { loadSessionState, saveSessionState } from '../../../lib/context-sessions';
Expand Down Expand Up @@ -68,7 +68,7 @@ export async function POST(req: Request): Promise<Response> {
const replayMessages = history.filter(
(m): m is { role: 'user'; content: string } => m.role === 'user' && typeof m.content === 'string'
);
const replay = engine.apply_transcript(replayMessages);
const replay = engine.applyTranscript(replayMessages);
if (replay.kind === 'confirm') {
saveSessionState(sessionId, engine.exportCheckpointJson());
const payload: ChatResponse = {
Expand All @@ -83,7 +83,7 @@ export async function POST(req: Request): Promise<Response> {

const decision = engine.step(input);

if (is_clarify(decision)) {
if (isClarify(decision)) {
saveSessionState(sessionId, engine.exportCheckpointJson());
const payload: ChatResponse = {
kind: DECISION_CLARIFY,
Expand Down
2 changes: 1 addition & 1 deletion examples/integrations/node-basic/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -19,5 +19,5 @@ This example uses `exportCheckpointJson()` / `importCheckpointJson()` for per-se
That preserves both saved compiler state and pending clarify/confirm state.

It also demonstrates a minimal experimental preprocessor pass before `engine.step(...)`
using `preprocess_heuristic(...)` plus `parse_preprocessor_output(...)` from
using `preprocessHeuristic(...)` plus `parsePreprocessorOutput(...)` from
`@rlippmann/context-compiler/experimental/preprocessor`.
14 changes: 7 additions & 7 deletions examples/integrations/node-basic/server.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,12 +5,12 @@ import {
createEngine,
getPolicyItems,
getPremiseValue,
is_clarify,
isClarify,
type EngineState
} from '@rlippmann/context-compiler';
import {
parse_preprocessor_output,
preprocess_heuristic
parsePreprocessorOutput,
preprocessHeuristic
} from '@rlippmann/context-compiler/experimental/preprocessor';

type ChatBody = {
Expand Down Expand Up @@ -64,9 +64,9 @@ function minimalRecentContext(history: ChatBody['history']) {
}

function normalizeInputWithPreprocessor(input: string): string {
const heuristic = preprocess_heuristic(input);
const heuristic = preprocessHeuristic(input);
if (heuristic.classification === 'directive' && heuristic.output !== null) {
const parsed = parse_preprocessor_output(heuristic.output, { source_input: input });
const parsed = parsePreprocessorOutput(heuristic.output, { source_input: input });
if (parsed !== null) {
return parsed;
}
Expand Down Expand Up @@ -108,7 +108,7 @@ const server = http.createServer(async (req, res) => {
const replayMessages = history.filter(
(m): m is { role: 'user'; content: string } => m.role === 'user' && typeof m.content === 'string'
);
const replay = engine.apply_transcript(replayMessages);
const replay = engine.applyTranscript(replayMessages);
if (replay.kind === 'confirm') {
saveCheckpoint(sessionId, engine.exportCheckpointJson());
const payload: ChatResponse = { kind: DECISION_CLARIFY, prompt_to_user: replay.prompt_to_user };
Expand All @@ -120,7 +120,7 @@ const server = http.createServer(async (req, res) => {

const preprocessedInput = normalizeInputWithPreprocessor(input);
const decision = engine.step(preprocessedInput);
if (is_clarify(decision)) {
if (isClarify(decision)) {
saveCheckpoint(sessionId, engine.exportCheckpointJson());
const payload: ChatResponse = { kind: DECISION_CLARIFY, prompt_to_user: decision.prompt_to_user };
sendJson(res, 200, payload);
Expand Down
2 changes: 2 additions & 0 deletions src/controller.ts
Original file line number Diff line number Diff line change
Expand Up @@ -88,6 +88,8 @@ export function state_diff(before: EngineState, after: EngineState): StructuralD
};
}

export const stateDiff = state_diff;

export function step(engine: Engine, user_input: string): StepResult {
const decision = engine.step(user_input);
return {
Expand Down
12 changes: 12 additions & 0 deletions src/engine.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,9 @@ export interface Engine {
step(input: string): Decision;
readonly state: EngineState;
has_pending_clarification(): boolean;
hasPendingClarification(): boolean;
apply_transcript(messages: unknown[]): TranscriptResult;
applyTranscript(messages: unknown[]): TranscriptResult;
exportJson(): string;
importJson(payload: string): void;
exportCheckpoint(): EngineCheckpoint;
Expand Down Expand Up @@ -68,6 +70,10 @@ class EngineImpl implements Engine {
return this._pendingReplacement !== null;
}

hasPendingClarification(): boolean {
return this.has_pending_clarification();
}

exportJson(): string {
return stringifyCanonicalJson(sortKeysDeep(this._state));
}
Expand Down Expand Up @@ -191,6 +197,10 @@ class EngineImpl implements Engine {
};
}

applyTranscript(messages: unknown[]): TranscriptResult {
return this.apply_transcript(messages);
}

private _replaceState(state: EngineState): void {
this._state = state;
this._pendingReplacement = null;
Expand Down Expand Up @@ -349,6 +359,8 @@ export function compile_transcript(messages: unknown[]): TranscriptResult {
return engine.apply_transcript(messages);
}

export const compileTranscript = compile_transcript;

export function getPremiseValue(state: EngineState): string | null {
return state.premise;
}
Expand Down
Loading
Loading