Skip to content

ホストから生成入力を記録する ​

モデル呼出しとpayload構成はアプリが所有します。以下は一要求の例で、モデルloopやAgent frameworkは実装しません。意味の正本は規範仕様です。

ts
import { createHash } from 'node:crypto';
import { LocalAuthority, MemoryHost } from 'atom-memory';
const authority = new LocalAuthority();
const auth = authority.issue({
  subject: 'example',
  readPolicies: ['p'],
  writePolicies: ['p'],
  canIngestSource: true,
});
const host = new MemoryHost({ authority });
const raw = { auth, writePolicy: 'p', actor: { type: 'input-adapter' as const } };
const binding = { ...raw, actor: { type: 'agent' as const } };
(
  await host.connect(raw).write({
    changes: [
      {
        id: 'atom',
        op: 'create',
        content: {
          text: '招待には管理者の承認が必要。',
          links: [],
        },
        sources: [],
      },
    ],
  })
).changes.atom;
const memory = host.connect(binding);
const recall = await memory.read({ query: '招待' }, { tokens: 4000 });
const payload = JSON.stringify({ question: '招待の条件は?', memory: recall.text });
const input = host.observe(
  {
    presentations: [{ receipt: recall.receipt, refs: recall.refs }],
    payloadDigest: createHash('sha256').update(payload).digest('hex'),
  },
  binding,
);
// Replace this deterministic stub with the application's request function.
const request = async (_payload: string) => '管理者の承認が必要です。';
const result = await request(payload);
// Use the same eventId when retrying this acknowledgement.
host.recordUse(recall.refs, binding, { eventId: 'example-request-1', input });
(
  await memory.write({
    changes: [
      {
        id: 'atom',
        op: 'create',
        content: {
          text: result,
          links: [],
        },
        sources: [],
        input,
      },
    ],
  })
).changes.atom; // The dispatcher injects input; it is not a model tool argument.

送信前に本文を除いた場合はpresentations.refsも実送信に合わせます。ただし使用したreadのacquisitionは選択依存として保持します。citation参照だけの資料全文を送ったとは記録しません。digestはホストの申告と実payloadを監査するための対応で、モデル内部を物理的に検知するものではありません。

前段から作業状態を持ち越す要求では inherit: [previousInput] を指定します。basisをhistoricalにしても継承したwatchは消えません。独立した要求は別tokenで観測し、同じwrite batchの各agent changeへ対応する input を付けます。同じ要求がA/Bを見た場合、引用をa→A、b→Bと分けても生成依存は両入力のままです。humanとinput-adapterのchangeはtokenを省略できます。

writeへ idempotencyKey を付けると、同じsubject・policy・actorで同じsemantic planを再送できます。planを変えた再送はconflictです。commit前の期限切れtokenは拒否されますが、commit済みplanの一致replayは、後からtokenが期限切れになっても同じ operationId とchangesを回収できます。現在の認可・purge境界はreplayでも検証されます。

入力tokenはbinding・認可世代・有効期限・purgeで検証します。入力未確定のtoken、任意文字列、他scopeのtokenは受け付けません。信頼したホストが全入力、外部tool結果、持越し状態を申告することが境界です。

小さなWriter例 ​

mjs
import assert from 'node:assert/strict';
import { pathToFileURL } from 'node:url';
import { createHash, randomUUID } from 'node:crypto';
import { MemoryHost, LocalAuthority } from 'atom-memory';

// This is application code. Inject a real model here; Atom does not define its
// protocol, choose it or decide when another generation should run.
export async function writerScenario(generate = async (source) => `認証の整理: ${source}`) {
  const authority = new LocalAuthority();
  const auth = authority.issue({
    subject: 'example',
    readPolicies: ['p'],
    writePolicies: ['p'],
    canIngestSource: true,
  });
  const host = new MemoryHost({ authority });
  const binding = { auth, writePolicy: 'p' };
  const memory = host.connect({ ...binding, actor: { type: 'input-adapter' } });
  /** @type {import('atom-memory').ClientBinding} */
  const agentBinding = { ...binding, actor: { type: 'agent', generatedOrigin: 'organization' } };
  const writer = host.connect(agentBinding);
  const source = (
    await memory.write({
      changes: [
        {
          id: 'source',
          op: 'create',
          content: { text: '旧クライアントで認証を利用できる。', links: [] },
          sources: [],
        },
      ],
    })
  ).changes.source;
  const organize = async (ref, previous) => {
    const inspection = await writer.inspect(ref, { version: 'latest', limit: 0 });
    const observed = inspection.atom;
    const input = host.observe(
      {
        presentations: [{ receipt: inspection.receipt, refs: [observed.ref] }],
        payloadDigest: createHash('sha256').update(observed.text).digest('hex'),
      },
      agentBinding,
    );
    const text = await generate(observed.text);
    if (typeof text !== 'string' || !text.trim()) throw new Error('Invalid model output');
    const eventId = randomUUID(); // Persist this ID with a request if its ack may be retried.
    host.recordUse([observed.ref], agentBinding, { eventId, input });
    /** @type {import('atom-memory').MemoryContent} */
    const content = {
      text,
      links: { 根拠: { ref: observed.ref, at: 'observed', required: true } },
    };
    return writer.write({
      changes: [
        {
          id: 'organization',
          ...(previous ? { op: 'revise', target: previous } : { op: 'create' }),
          content,
          input,
          sources: [{ ref: observed.ref }],
        },
      ],
    });
  };
  const created = await organize(source.ref);
  const options = { tokens: 10000, depth: 2 };
  const first = await memory.read({ query: '認証' }, options);
  const correction = await memory.write({
    changes: [
      {
        id: 'source',
        op: 'revise',
        target: source.ref,
        content: { text: '旧クライアントで認証を利用できない。', links: [] },
        sources: [],
      },
    ],
  });
  const pending = await memory.read({ query: '認証' }, options);
  assert.ok(pending.stale.includes(created.changes.organization.ref));
  // The host chooses to refresh now. Production agents can enqueue this instead.
  await organize(correction.changes.source.ref, created.changes.organization.ref);
  const reread = await memory.read({ query: '認証' }, options);
  assert.match(reread.text, /利用できない/);
  assert.deepEqual(reread.stale, []);
  const observed = await memory.inspect(source.ref, { limit: 0 });
  return { source, first, pending, reread, observed };
}

if (process.argv[1] && import.meta.url === pathToFileURL(process.argv[1]).href) {
  const result = await writerScenario();
  console.log(`古い整理を通知: ${result.pending.stale.length}`);
  console.log(`Writer改訂後の未更新: ${result.reread.stale.length}`);
  console.log(`観測済み原文: ${result.observed.atom.text}`);
}
text
古い整理を通知: 1
Writer改訂後の未更新: 0
観測済み原文: 旧クライアントで認証を利用できる。

モデル呼出し、再試行、費用、ジョブ、後継採用はホストへ残します。この例の決定的stubは実LLMの品質評価ではありません。

MIT License · TypeScript / Node.js