Skip to content
Open
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
44 changes: 44 additions & 0 deletions .claude/skills/noetic-agent-builder/references/api-reference.md
Original file line number Diff line number Diff line change
Expand Up @@ -208,6 +208,26 @@ const compiled = compilePlan(plan, agents, undefined, harness.run.bind(harness))

## Memory Layers

### MemoryLayer Interface

```typescript
interface MemoryLayer<TState = unknown> {
id: string;
name?: string;
slot: number;
scope: MemoryScope;
budget?: BudgetConfig;
recallMode?: 'atomic' | 'eventual';
hooks: MemoryHooks<TState>;
timeouts?: Partial<LayerTimeouts>;
provides?: LayerProvides;
}
```

**`recallMode`** controls how the layer's `recall()` participates in View assembly:
- `'atomic'` (default) -- recall blocks `callModel`. The agent harness waits for all atomic layers before assembling the View. Use for layers whose output the model needs immediately (working memory, static content, steering).
- `'eventual'` -- recall uses cached results, never blocks `callModel`. The cache refreshes asynchronously when `store()` produces new state. Use for slow layers where slight staleness is acceptable (observational memory, RAG).

### workingMemory

Thread/resource-scoped structured state, updated via `updateWorkingMemory` tool call.
Expand Down Expand Up @@ -395,6 +415,30 @@ Layer functions in `provides` are automatically exposed as tools to any `step.ll

`AgentHarness` is generic over `TParams`. The `config` property exposes `AgentConfig<TParams>`, and steps/tools access params via `ctx.harness.config.params`.

### ProjectionPolicy

Controls how the runtime projects conversation items into the model's context window. Set at harness level (default for all steps) or per-step via `StepLLM.projection`.

```typescript
interface ProjectionPolicy {
tokenBudget: number; // Total context window budget
responseReserve: number; // Tokens reserved for the response
overflow: 'truncate' | 'summarize' | 'sliding_window';
overflowModel?: string; // Model for summarize overflow
windowSize?: number; // For sliding_window: max history items
}

// Harness-level default
const harness = new AgentHarness({
name: 'agent',
params: {},
projection: { tokenBudget: 128e3, responseReserve: 4096, overflow: 'sliding_window' },
});

// Per-step override
step.llm({ id: 'summarize', model: 'gpt-4', projection: { ... } })
```

```typescript
// High-level API: execute() returns HarnessResult with streaming accessors
const harness = new AgentHarness({
Expand Down
136 changes: 128 additions & 8 deletions packages/core/src/interpreter/execute-llm.ts
Original file line number Diff line number Diff line change
@@ -1,17 +1,28 @@
import { ZodError } from 'zod';
import { NoeticErrorImpl } from '../errors/noetic-error';
import { allocateBudgets } from '../memory/budget';
import { resolveLayerTools } from '../memory/layer-api';
import { assembleView } from '../memory/projector';
import type { StepMeta, Tool } from '../types/common';
import type { Context } from '../types/context';
import type { FunctionCallItem } from '../types/items';
import type { ContextMemory, MemoryLayer } from '../types/memory';
import type { FunctionCallItem, Item } from '../types/items';
import type { ContextMemory, MemoryLayer, ProjectionPolicy } from '../types/memory';
import { SteeringAction } from '../types/steering';
import type { StepLLM } from '../types/step';
import { frameworkCast } from './framework-cast';
import { createMessage, extractAssistantText, trackUsage } from './message-helpers';
import { createMessage, estimateTokens, extractAssistantText, trackUsage } from './message-helpers';
import { isMutableContext } from './typeguards';

const MAX_STEERING_RETRIES = 3;
const LAYERS_INIT_SENTINEL = '__layers_initialized';

const DEFAULT_PROJECTION: ProjectionPolicy = {
tokenBudget: 128e3,
responseReserve: 4e3,
overflow: 'sliding_window',
};

//#region Helper Functions

function mergeTools(
stepTools: Tool[] | undefined,
Expand All @@ -28,6 +39,31 @@ function mergeTools(
];
}

function partitionItems(items: ReadonlyArray<Item>): {
systemItems: Item[];
historyItems: Item[];
} {
const systemItems: Item[] = [];
const historyItems: Item[] = [];

for (const item of items) {
if (item.type === 'message' && item.role === 'system') {
systemItems.push(item);
continue;
}
historyItems.push(item);
}

return {
systemItems,
historyItems,
};
}

//#endregion

//#region Public API

export async function executeLLM<TMemory, I, O>(
step: StepLLM<TMemory, I, O>,
input: I,
Expand All @@ -41,13 +77,92 @@ export async function executeLLM<TMemory, I, O>(
}

const allTools = mergeTools(step.tools, layers, baseCtx);
const hasLayers = layers !== undefined && layers.length > 0;

// Memory pipeline setup (once, before retry loop)
let budgetMap = new Map<string, number>();

// Resolve projection policy once: step > harness > default
const policy: ProjectionPolicy =
step.projection ?? baseCtx.harness.config.projection ?? DEFAULT_PROJECTION;

if (hasLayers) {
// Init layers on first LLM call in this execution
if (!baseCtx.harness.getLayerState(baseCtx.id, LAYERS_INIT_SENTINEL)) {
const storage = baseCtx.harness.config.storage;
if (storage) {
await baseCtx.harness.initLayers(layers, baseCtx, storage);
baseCtx.harness.setLayerState(baseCtx.id, LAYERS_INIT_SENTINEL, true);
}
}

let systemTokenEstimate = step.instructions ? estimateTokens(step.instructions) : 0;
for (const item of baseCtx.itemLog.items) {
if (item.type === 'message' && item.role === 'system') {
for (const part of item.content) {
if (part.type === 'input_text') {
systemTokenEstimate += estimateTokens(part.text);
}
}
}
}
const { allocations } = allocateBudgets({
layers,
totalBudget: policy.tokenBudget,
systemPromptTokens: systemTokenEstimate,
responseReserve: policy.responseReserve,
});
budgetMap = new Map(
allocations.map((a) => [
a.layerId,
a.allocated,
]),
);
}

let retries = 0;

while (retries <= MAX_STEERING_RETRIES) {
// Recall + Assemble
let requestItems: ReadonlyArray<Item>;

if (hasLayers) {
const query = typeof input === 'string' ? input : '';

const atomicResults = await baseCtx.harness.recallLayersAtomic(
layers,
query,
baseCtx,
budgetMap,
);
const eventualResults = await baseCtx.harness.recallLayersEventual(
layers,
query,
baseCtx,
budgetMap,
);

const layerOutputItems = [
...atomicResults,
...eventualResults,
].flatMap((r) => r.items);

const { systemItems, historyItems } = partitionItems(baseCtx.itemLog.items);

requestItems = assembleView({
systemPromptItems: systemItems,
layerOutputItems,
historyItems,
policy,
});
} else {
requestItems = baseCtx.itemLog.items;
}

const request = allTools
? {
model: step.model,
items: baseCtx.itemLog.items,
items: requestItems,
instructions: step.instructions,
tools: allTools,
params: step.params,
Expand All @@ -58,7 +173,7 @@ export async function executeLLM<TMemory, I, O>(
}
: {
model: step.model,
items: baseCtx.itemLog.items,
items: requestItems,
instructions: step.instructions,
params: step.params,
outputSchema: step.output,
Expand All @@ -67,7 +182,7 @@ export async function executeLLM<TMemory, I, O>(
};
const response = await baseCtx.harness.callModel(request);

if (layers && layers.length > 0) {
if (hasLayers) {
const decision = await baseCtx.harness.afterModelCall(layers, response, baseCtx);

if (decision.action === SteeringAction.Deny) {
Expand All @@ -94,6 +209,11 @@ export async function executeLLM<TMemory, I, O>(
}
}

// Store layers after response
if (hasLayers) {
await baseCtx.harness.storeLayers(layers, response, baseCtx);
}

const meta: StepMeta = {
toolCalls: toolCalls.length > 0 ? toolCalls : undefined,
usage: response.usage,
Expand Down Expand Up @@ -139,12 +259,12 @@ export async function executeLLM<TMemory, I, O>(
}

// Safety net: the loop above always returns or throws within the body.
// This throw is unreachable but protects against future refactors that
// might break the loop invariant.
throw new NoeticErrorImpl({
kind: 'step_failed',
stepId: step.id,
cause: new Error('Steering retries exhausted'),
retriesExhausted: true,
});
}

//#endregion
Loading
Loading