長期履歴を整理する
期間・件数・バイト数で入力を区切り、Agentが既存の記憶を調べながら複数Atomをまとめてwriteします。期間の選び方、意味の分解、モデル、再試行はアプリ側の処理です。
アプリの未処理キュー → 有限の原資料 → Agentが検索・検討
↓
検証した複数Atomのwrite batch
↓
索引更新 → 永続化 → 完了記録一件ずつに限定しない
一つの write({ changes }) で複数の create / revise / retire をまとめられます。{ local: changeId } のbatch-local linkで、同じbatch内の新しいAtomを相互参照できます。途中失敗では全件を取り消します。
import { createHash } from 'node:crypto';
import { MemoryHost, LocalAuthority } from 'atom-memory';
// A host chooses bounded periods and invokes its model. This deterministic
// example demonstrates memory operations, not the quality of an AI Writer.
const authority = new LocalAuthority();
const auth = authority.issue({
subject: 'period-writer',
readPolicies: ['community'],
writePolicies: ['community'],
canIngestSource: true,
});
const host = new MemoryHost({ authority });
const binding = { auth, writePolicy: 'community' };
const input = host.connect({ ...binding, actor: { type: 'input-adapter' } });
/** @type {import('atom-memory').ClientBinding} */
const writerBinding = { ...binding, actor: { type: 'agent' } };
const writer = host.connect(writerBinding);
const periods = ['月曜: 招待期限は24時間という提案。', '木曜: 招待には管理者承認も必要。'];
let topic;
const sources = [];
for (const [index, text] of periods.entries()) {
sources.push(
(
await input.write(
{ changes: [{ id: 'source', op: 'create', content: { text, links: [] }, sources: [] }] },
{ idempotencyKey: `period:${index}` },
)
).changes.source,
);
const presentations = [];
for (const source of sources) {
const detail = await writer.inspect(source.ref, { limit: 0 });
presentations.push({ receipt: detail.receipt });
}
const previous = topic ? await writer.inspect(topic.ref, { limit: 0 }) : null;
if (previous) presentations.push({ receipt: previous.receipt });
const payload = JSON.stringify({
sources: sources.map((source) => source.text),
previous: previous?.atom.text,
});
const token = host.observe(
{ presentations, payloadDigest: createHash('sha256').update(payload).digest('hex') },
writerBinding,
);
// Replace this deterministic organization with the application's model output.
/** @type {import('atom-memory').MemoryContent} */
const content = {
text: sources.map((source) => source.text).join('\n'),
links: { 根拠: sources.map((source) => ({ ref: source.ref, at: 'observed', required: true })) },
};
const result = await writer.write(
{
changes: [
{
id: 'topic',
...(topic ? { op: 'revise', target: topic.ref } : { op: 'create' }),
content,
sources: sources.map((source) => ({ ref: source.ref })),
input: token,
},
],
},
{ idempotencyKey: `organization:${index}` },
);
topic = result.changes.topic;
// Host checkpoints advance only after its storage durability barrier.
}
console.log(`同じAtomを改訂した期間数: ${periods.length}`);
console.log(topic.text);同じAtomを改訂した期間数: 2
月曜: 招待期限は24時間という提案。
木曜: 招待には管理者承認も必要。この例はAPIの動作を示す決定的な処理です。実際の分解・関係付け・説明はAgentがモデルに判断させ、ホストが発行済み参照や出典を検証します。
過去の資料と現在の整理
basis: 'historical' は観測した過去の入力に基づく整理に使います。保存結果の入力を最新版へ自動で読み替えません。現在の状態として保ちたい整理では既定のcurrent基準を使い、stale を受けてAgentが改訂します。
旧版の参照、現在版へのlogicalリンク、別の視点のAtomを使い分けられます。ライブラリは「新しいAtomを唯一の後継として採用する」という運用を決めません。
再開と費用
Atomのコミットと、アプリの入力キューの完了記録は別の責務です。Sakanaは入力世代・モデル実行・Atomコミット・索引更新・flushをチェックポイントで接続します。モデル成功後に保存の応答が失われても、完了済みのモデル呼出しを繰り返さず再開することをテストしています。
入力の削除・訂正は、その入力を見たバッチ全体の再処理をキューへ入れます。細かい出典ラベルだけで、LLMが見た他の入力の影響を無かったことにはしません。費用を減らすには、期間を無制限に大きくせず、既存のまとまりと必要な資料を選んで渡します。
Writerの依存と再開
Writerは公開receiptを実行中プロセスで認可確認に使い、receipt自体はcheckpointへ保存しません。checkpointには採用した既存Atomのentry・sourceと、正確なbatch ID・Atom refを保存します。read が候補探索中に触れたstale候補や拒否された候補を、採用済み入力として無条件に記録するわけではありません。モデル応答後とcommit直前にreceiptの認可・accepted batchのcurrent headを検証し、最終writeでは採用refを version: 'latest' で読み直します。checkpoint復元時も正確な採用refを再束縛します(明示的なrevise targetはrevision CAS)。これにより、モデルリンクが付かない出力でも、モデルに見えた組織化入力の依存と鮮度をdurable lineageとして保持します。
checkpointを復元するときは保存済みの正確なrefを再束縛します。採用したheadが変われば、キャッシュしたモデル出力を再利用せず、新しいreadから再開します。明示的なrevise changeはrevision CASと、確定した置換版のcurrentnessを通して自己stalenessを防ぎます。Writerのagent changeにはhost-issued InputTokenが必要です。
索引は意味構造の保存とは別です。targetのrevisionだけが変わっても、リンク元のown bodyが同じなら、その親を再エンコードしません。変更されたAtomのv3本文をfeedから処理し、関係の影響は構造ランキングで評価します。