Application memory
When an application's agents need shared durable knowledge, read this because a profile store gives runs one owned memory location and scopes can allow reading without writing.
Application memory
Use this recipe when several runs need the same application guidance. Run npx tsx examples/guides/app-memory.ts /path/to/repo; it ensures a profile, creates a profile-owned knowledge document if it does not already exist, then starts a run allowed to read but not write memory.
import Crouter, { NotFoundError } from '@north-light/crouter-sdk';
import { resolve } from 'node:path';
import { z } from 'zod';
const client = new Crouter();
const cwd = resolve(process.argv[2] ?? process.cwd());
const profileName = process.env.APP_PROFILE ?? 'release-notes-app';
const profile = await client.profiles.ensure(profileName, {
projects: [{ path: cwd, memory: 'content' }],
});
try {
await client.memory.retrieve('release/voice', { profile: profile.id, store: 'profile' });
} catch (error) {
if (!(error instanceof NotFoundError)) throw error;
await client.memory.create({
name: 'release/voice',
kind: 'knowledge',
when_and_why_to_read: 'When writing release notes, read this because it defines the product voice and terms customers recognize.',
body: 'Use short factual sentences. Name the user-visible result before implementation details.',
frontmatter: { surfaces: ['{"on":"boot","at":"preview"}'] },
profile: profile.id,
store: 'profile',
});
}
const result = await client.nodes.parse({
cwd,
model: process.env.GUIDE_MODEL,
profile: profile.id,
root: true,
root_lifecycle: 'terminal',
deadline: '5m',
scopes: ['memory:read'],
prompt: 'Write one short release note announcing that customers can download invoices as CSV from account settings. Do not write or update memory.',
output_schema: z.object({ release_note: z.string() }),
});
if (result.kind === 'result') {
console.log(result.output_parsed.release_note);
} else if (result.reason === 'declined') {
console.error(`agent declined: ${result.declined?.reason ?? 'no reason supplied'}`);
process.exitCode = 2;
} else {
console.error(`agent failed: ${result.reason}`, result.detail);
process.exitCode = 1;
}
A profile is the application's durable identity: it names the memory store and the projects whose context the profile can reach. The routing line is part of the document, not decoration. State both the moment the agent should read it and the reason it matters; a document with a vague routing line will not surface when the agent needs it.
The boot/preview surface exposes the document's routing line when this profile's agent starts. The task asks for a release note without naming release/voice; in the local run the agent read that document before producing the note. Without a surface entry, a memory document appears only in its directory listing and its routing line cannot select it at boot. The run passes scopes: ['memory:read']. That is an allow-list: node-targeted memory reads are allowed and writes are refused because memory:write is absent. The application itself is not narrowed by those node scopes, so keep application credentials and its memory mutation policy separate from the agent's capabilities.
Observed against the local daemon with APP_PROFILE=release-notes-app-guide-boot-preview GUIDE_MODEL=openai-codex/gpt-6-sol:high:
You can now download your invoices as a CSV from account settings.
Use profile memory for knowledge shared across the application's related projects. Put facts that belong to one repository in project memory instead, and temporary run notes in node memory. See memory for the ownership tiers and profiles, kinds, and modes for what a profile changes.