auggy 0.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (121) hide show
  1. package/CHANGELOG.md +96 -0
  2. package/LICENSE +201 -0
  3. package/README.md +161 -0
  4. package/package.json +76 -0
  5. package/src/agent-card.ts +39 -0
  6. package/src/agent.ts +283 -0
  7. package/src/agentmail-client.ts +138 -0
  8. package/src/augments/bash/index.ts +463 -0
  9. package/src/augments/bash/skill/SKILL.md +156 -0
  10. package/src/augments/budgets/budget-store.ts +513 -0
  11. package/src/augments/budgets/index.ts +134 -0
  12. package/src/augments/budgets/preamble.ts +93 -0
  13. package/src/augments/budgets/types.ts +89 -0
  14. package/src/augments/file-memory/index.ts +71 -0
  15. package/src/augments/filesystem/index.ts +533 -0
  16. package/src/augments/filesystem/skill/SKILL.md +142 -0
  17. package/src/augments/filesystem/skill/references/mount-permissions.md +81 -0
  18. package/src/augments/layered-memory/extractor/buffer.ts +56 -0
  19. package/src/augments/layered-memory/extractor/frequency.ts +79 -0
  20. package/src/augments/layered-memory/extractor/inject-handler.ts +103 -0
  21. package/src/augments/layered-memory/extractor/parse.ts +75 -0
  22. package/src/augments/layered-memory/extractor/prompt.md +26 -0
  23. package/src/augments/layered-memory/index.ts +757 -0
  24. package/src/augments/layered-memory/skill/SKILL.md +153 -0
  25. package/src/augments/layered-memory/storage/migrations/README.md +16 -0
  26. package/src/augments/layered-memory/storage/migrations/supabase-add-fact-fields.sql +9 -0
  27. package/src/augments/layered-memory/storage/sqlite-store.ts +352 -0
  28. package/src/augments/layered-memory/storage/supabase-store.ts +263 -0
  29. package/src/augments/layered-memory/storage/types.ts +98 -0
  30. package/src/augments/link/index.ts +489 -0
  31. package/src/augments/link/translate.ts +261 -0
  32. package/src/augments/notify/adapters/agentmail.ts +70 -0
  33. package/src/augments/notify/adapters/telegram.ts +60 -0
  34. package/src/augments/notify/adapters/webhook.ts +55 -0
  35. package/src/augments/notify/index.ts +284 -0
  36. package/src/augments/notify/skill/SKILL.md +150 -0
  37. package/src/augments/org-context/index.ts +721 -0
  38. package/src/augments/org-context/skill/SKILL.md +96 -0
  39. package/src/augments/skills/index.ts +103 -0
  40. package/src/augments/supabase-memory/index.ts +151 -0
  41. package/src/augments/telegram-transport/index.ts +312 -0
  42. package/src/augments/telegram-transport/polling.ts +55 -0
  43. package/src/augments/telegram-transport/webhook.ts +56 -0
  44. package/src/augments/turn-control/index.ts +61 -0
  45. package/src/augments/turn-control/skill/SKILL.md +155 -0
  46. package/src/augments/visitor-auth/email-validation.ts +66 -0
  47. package/src/augments/visitor-auth/index.ts +779 -0
  48. package/src/augments/visitor-auth/rate-limiter.ts +90 -0
  49. package/src/augments/visitor-auth/skill/SKILL.md +55 -0
  50. package/src/augments/visitor-auth/storage/sqlite-store.ts +398 -0
  51. package/src/augments/visitor-auth/storage/types.ts +164 -0
  52. package/src/augments/visitor-auth/types.ts +123 -0
  53. package/src/augments/visitor-auth/verify-page.ts +179 -0
  54. package/src/augments/web-fetch/index.ts +331 -0
  55. package/src/augments/web-fetch/skill/SKILL.md +100 -0
  56. package/src/cli/agent-index.ts +289 -0
  57. package/src/cli/augment-catalog.ts +320 -0
  58. package/src/cli/augment-resolver.ts +597 -0
  59. package/src/cli/commands/add-skill.ts +194 -0
  60. package/src/cli/commands/add.ts +87 -0
  61. package/src/cli/commands/chat.ts +207 -0
  62. package/src/cli/commands/create.ts +462 -0
  63. package/src/cli/commands/dev.ts +139 -0
  64. package/src/cli/commands/eval.ts +180 -0
  65. package/src/cli/commands/ls.ts +66 -0
  66. package/src/cli/commands/remove.ts +95 -0
  67. package/src/cli/commands/restart.ts +40 -0
  68. package/src/cli/commands/start.ts +123 -0
  69. package/src/cli/commands/status.ts +104 -0
  70. package/src/cli/commands/stop.ts +84 -0
  71. package/src/cli/commands/visitors-revoke.ts +155 -0
  72. package/src/cli/commands/visitors.ts +101 -0
  73. package/src/cli/config-parser.ts +1034 -0
  74. package/src/cli/engine-resolver.ts +68 -0
  75. package/src/cli/index.ts +178 -0
  76. package/src/cli/model-picker.ts +89 -0
  77. package/src/cli/pid-registry.ts +146 -0
  78. package/src/cli/plist-generator.ts +117 -0
  79. package/src/cli/resolve-config.ts +56 -0
  80. package/src/cli/scaffold-skills.ts +158 -0
  81. package/src/cli/scaffold.ts +291 -0
  82. package/src/cli/skill-frontmatter.ts +51 -0
  83. package/src/cli/skill-validator.ts +151 -0
  84. package/src/cli/types.ts +228 -0
  85. package/src/cli/yaml-helpers.ts +66 -0
  86. package/src/engines/_shared/cost.ts +55 -0
  87. package/src/engines/_shared/schema-normalize.ts +75 -0
  88. package/src/engines/anthropic/pricing.ts +117 -0
  89. package/src/engines/anthropic.ts +483 -0
  90. package/src/engines/openai/pricing.ts +67 -0
  91. package/src/engines/openai.ts +446 -0
  92. package/src/engines/openrouter/pricing.ts +83 -0
  93. package/src/engines/openrouter.ts +185 -0
  94. package/src/helpers.ts +24 -0
  95. package/src/http.ts +387 -0
  96. package/src/index.ts +165 -0
  97. package/src/kernel/capability-table.ts +172 -0
  98. package/src/kernel/context-allocator.ts +161 -0
  99. package/src/kernel/history-manager.ts +198 -0
  100. package/src/kernel/lifecycle-manager.ts +106 -0
  101. package/src/kernel/output-validator.ts +35 -0
  102. package/src/kernel/preamble.ts +23 -0
  103. package/src/kernel/route-collector.ts +97 -0
  104. package/src/kernel/timeout.ts +21 -0
  105. package/src/kernel/tool-selector.ts +47 -0
  106. package/src/kernel/trace-emitter.ts +66 -0
  107. package/src/kernel/transport-queue.ts +147 -0
  108. package/src/kernel/turn-loop.ts +1148 -0
  109. package/src/memory/context-synthesis.ts +83 -0
  110. package/src/memory/memory-bus.ts +61 -0
  111. package/src/memory/registry.ts +80 -0
  112. package/src/memory/tools.ts +320 -0
  113. package/src/memory/types.ts +8 -0
  114. package/src/parts.ts +30 -0
  115. package/src/scaffold-templates/identity.md +31 -0
  116. package/src/telegram-client.ts +145 -0
  117. package/src/tokenizer.ts +14 -0
  118. package/src/transports/ag-ui-events.ts +253 -0
  119. package/src/transports/visitor-token.ts +82 -0
  120. package/src/transports/web-transport.ts +948 -0
  121. package/src/types.ts +1009 -0
@@ -0,0 +1,81 @@
1
+ # Mount Permissions Reference
2
+
3
+ ## Permission matrix
4
+
5
+ | Operation | Read-only | Writable | Writable + Deletable |
6
+ |-----------|-----------|----------|---------------------|
7
+ | `fs_read` | ✓ | ✓ | ✓ |
8
+ | `fs_list` | ✓ | ✓ | ✓ |
9
+ | `fs_search` | ✓ | ✓ | ✓ |
10
+ | `fs_write` | ✗ | ✓ | ✓ |
11
+ | `fs_mkdir` | ✗ | ✓ | ✓ |
12
+ | `fs_remove` | ✗ | ✗ | ✓ |
13
+
14
+ ## Mount configuration shape
15
+
16
+ ```typescript
17
+ {
18
+ name: string; // logical name — first path segment
19
+ path: string; // physical path on disk
20
+ writable?: boolean; // default false
21
+ deletable?: boolean; // default false (requires writable: true)
22
+ maxReadSize?: number; // default 256KB (262144 bytes)
23
+ maxWriteSize?: number; // default 1MB (1048576 bytes)
24
+ searchExcludes?: string[]; // default [".git", "node_modules", ".next", "__pycache__", ".DS_Store"]
25
+ }
26
+ ```
27
+
28
+ ## Typical mount configurations
29
+
30
+ ### Skills (read-only)
31
+ ```typescript
32
+ { name: "skills", path: "./augments", writable: false }
33
+ ```
34
+ For reading SKILL.md files, references, and examples. NEVER writable — prevents the agent from modifying its own behavioral teaching.
35
+
36
+ ### Workspace (writable + deletable)
37
+ ```typescript
38
+ { name: "workspace", path: "./workspace", writable: true, deletable: true }
39
+ ```
40
+ Agent's personal working directory. Full read/write/delete access. Used for notes, drafts, intermediate work products, scratch files.
41
+
42
+ ### Repository (read-only)
43
+ ```typescript
44
+ { name: "repo", path: "/repos/platform", writable: false }
45
+ ```
46
+ External code repository mounted for review or analysis. Read-only to prevent accidental modification.
47
+
48
+ ### Output (writable, not deletable)
49
+ ```typescript
50
+ { name: "output", path: "/shared/reports", writable: true, deletable: false }
51
+ ```
52
+ Shared directory for publishing reports. The agent can create and update files but cannot delete published output.
53
+
54
+ ## Size limits
55
+
56
+ | Limit | Default | What it protects |
57
+ |-------|---------|-----------------|
58
+ | `maxReadSize` | 256KB | Prevents large files from consuming the context window. Files over this limit are truncated with a `[truncated]` marker. |
59
+ | `maxWriteSize` | 1MB | Prevents the agent from writing arbitrarily large files to disk. |
60
+ | `fs_search` max results | 100 (configurable up to 1000) | Prevents glob expansion on huge directories from returning overwhelming results. |
61
+
62
+ ## Binary file handling
63
+
64
+ The following extensions are detected as binary and rejected by `fs_read`:
65
+
66
+ **Images:** .png, .jpg, .jpeg, .gif, .bmp, .ico, .webp, .svg
67
+ **Documents:** .pdf
68
+ **Archives:** .zip, .gz, .tar, .bz2, .7z, .rar
69
+ **Media:** .mp3, .mp4, .avi, .mov, .wav, .flac
70
+ **Fonts:** .woff, .woff2, .ttf, .otf, .eot
71
+ **Compiled:** .exe, .dll, .so, .dylib, .o, .a, .wasm, .pyc, .class
72
+
73
+ Binary files return: `Error: Binary file (.ext, size). Use fs_list to see metadata.`
74
+
75
+ ## Security boundaries
76
+
77
+ 1. **Path traversal**: All paths are resolved via `fs.realpath()` (follows symlinks) and checked against the mount root using a `path.relative()`-based containment helper. Paths that escape the mount (relative result of `..` or absolute path for cross-drive) are rejected. The `relative()`-based check handles both prefix-collision siblings (`/var/data/work` does not accept `/var/data/workspace/...`) and root-level mounts (`/` on POSIX accepts nested children correctly).
78
+ 2. **Symlink escape**: Symlinks that point outside the mount boundary are detected and rejected before the file is read, using the same containment check.
79
+ 3. **Mount isolation**: Each mount is an independent security boundary. No cross-mount path references are possible.
80
+ 4. **No absolute paths**: The agent always uses logical paths (`mount-name/...`). Physical paths are never exposed.
81
+ 5. **Trust-level tool gating**: Mutation tools (`fs_write`, `fs_mkdir`, `fs_remove`) are structurally hidden from untrusted peers by the kernel's capability table before the model sees them. The authenticated level loses `fs_remove`. Operator and facility peers see all tools. This runs at tool-selection time; mount-level `writable` / `deletable` flags are a complementary check that runs inside the tool.
@@ -0,0 +1,56 @@
1
+ import type { Transcript } from "../../../types";
2
+
3
+ /**
4
+ * Per-peer in-memory buffer used by the `session-end-only` extraction
5
+ * mode (Decision 3 of the memorist design). When the frequency
6
+ * dispatcher returns "buffer" for a turn, the auto-save handler
7
+ * appends that turn's `Transcript` here. At a configured boundary —
8
+ * session end, idle threshold, or operator-triggered flush — the
9
+ * augment calls `flush(peerId)` and runs extraction on the accumulated
10
+ * snapshot.
11
+ *
12
+ * Process-local: a restart drops all buffered transcripts. That's the
13
+ * intended trade-off — buffered visitor traffic is operationally
14
+ * low-stakes (anonymous public peers), and persistence would require
15
+ * its own retention/compaction story disproportionate to the value.
16
+ */
17
+ export interface ExtractionBuffer {
18
+ /** Append a completed turn's transcript to the peer's buffer. */
19
+ append(peerId: string, transcript: Transcript): void;
20
+ /**
21
+ * Drain and return the peer's buffered transcripts. Subsequent
22
+ * `peek` returns an empty array until the next `append`.
23
+ */
24
+ flush(peerId: string): Transcript[];
25
+ /** Read-only view of currently buffered transcripts for a peer. */
26
+ peek(peerId: string): readonly Transcript[];
27
+ /** Drop the peer's buffer without returning it (e.g. on `forget`). */
28
+ clear(peerId: string): void;
29
+ /** Number of distinct peers with at least one buffered transcript. */
30
+ size(): number;
31
+ }
32
+
33
+ export function createBuffer(): ExtractionBuffer {
34
+ const store = new Map<string, Transcript[]>();
35
+ return {
36
+ append(peerId, transcript) {
37
+ const list = store.get(peerId) ?? [];
38
+ list.push(transcript);
39
+ store.set(peerId, list);
40
+ },
41
+ flush(peerId) {
42
+ const list = store.get(peerId) ?? [];
43
+ store.delete(peerId);
44
+ return list;
45
+ },
46
+ peek(peerId) {
47
+ return store.get(peerId) ?? [];
48
+ },
49
+ clear(peerId) {
50
+ store.delete(peerId);
51
+ },
52
+ size() {
53
+ return store.size;
54
+ },
55
+ };
56
+ }
@@ -0,0 +1,79 @@
1
+ import type { TrustLevel } from "../../../types";
2
+
3
+ /**
4
+ * Per-trust-level extraction frequency knobs (Decision 3 of the memorist
5
+ * design). Operators choose how aggressively auto-save extracts per cohort:
6
+ *
7
+ * - "every-turn": extract after every completed turn
8
+ * - "every-N-turns": extract after turns where turnIndex % N === 0
9
+ * - "session-end-only": buffer transcripts; flush at session boundary
10
+ * - "never": skip extraction entirely for this cohort
11
+ */
12
+ export type ExtractionFrequency = "every-turn" | "every-N-turns" | "session-end-only" | "never";
13
+
14
+ /**
15
+ * Outcome of the frequency check for a single completed turn:
16
+ *
17
+ * - "extract": run extraction now (immediate write path)
18
+ * - "buffer": append the transcript to the per-peer buffer (deferred)
19
+ * - "skip": do nothing for this turn
20
+ */
21
+ export type ExtractionDecision = "extract" | "buffer" | "skip";
22
+
23
+ /**
24
+ * Nested configuration shape consumed by the dispatcher. `public` splits
25
+ * into `recognized` (visitor-token holders) and `anonymous` (no token /
26
+ * fresh visitor) so operators can be more aggressive with returning
27
+ * recognized visitors than with first-touch traffic.
28
+ */
29
+ export interface ExtractionFrequencyConfig {
30
+ creator?: ExtractionFrequency;
31
+ agent?: ExtractionFrequency;
32
+ public?: {
33
+ recognized?: ExtractionFrequency;
34
+ anonymous?: ExtractionFrequency;
35
+ };
36
+ }
37
+
38
+ /**
39
+ * Minimal peer descriptor used by the dispatcher. The full PeerIdentity
40
+ * carries more fields, but only trust + public substate matter for
41
+ * frequency selection.
42
+ */
43
+ export interface PeerInput {
44
+ trustLevel: TrustLevel;
45
+ publicSubstate?: "recognized" | "anonymous";
46
+ }
47
+
48
+ /**
49
+ * Pure dispatch: given a peer, the current turn index for that peer, and
50
+ * the operator's frequency config, return whether to extract immediately,
51
+ * buffer, or skip. No side effects, no I/O.
52
+ */
53
+ export function shouldExtract(
54
+ peer: PeerInput,
55
+ turnIndex: number,
56
+ config: ExtractionFrequencyConfig,
57
+ everyNTurns: number,
58
+ ): ExtractionDecision {
59
+ const freq = resolveFrequency(peer, config);
60
+ if (freq === "never") return "skip";
61
+ if (freq === "every-turn") return "extract";
62
+ if (freq === "session-end-only") return "buffer";
63
+ if (freq === "every-N-turns") return turnIndex % everyNTurns === 0 ? "extract" : "skip";
64
+ return "skip";
65
+ }
66
+
67
+ function resolveFrequency(peer: PeerInput, config: ExtractionFrequencyConfig): ExtractionFrequency {
68
+ if (peer.trustLevel === "creator") return config.creator ?? "every-turn";
69
+ if (peer.trustLevel === "agent") return config.agent ?? "every-N-turns";
70
+ if (peer.trustLevel === "public") {
71
+ const sub = peer.publicSubstate ?? "anonymous";
72
+ return config.public?.[sub] ?? (sub === "recognized" ? "every-turn" : "session-end-only");
73
+ }
74
+ // Defensive — unknown trust levels (shouldn't happen given the union)
75
+ // skip extraction rather than fall through to a default that might
76
+ // accidentally write under an unintended peer cohort.
77
+ console.warn(`[layered-memory] unknown trust level: ${peer.trustLevel}; skipping extraction`);
78
+ return "never";
79
+ }
@@ -0,0 +1,103 @@
1
+ import type { Transcript } from "../../../types";
2
+ import { type ExtractedFact, parseExtractionResponse } from "./parse";
3
+
4
+ /**
5
+ * Minimal extraction-engine surface. The auto-save handler does NOT need
6
+ * the full `ModelClient` shape (assembled prompts, tool definitions, token
7
+ * counters). It needs a single string-in / string-out completion paired
8
+ * with a USD cost so callers can roll the spend into budgets.
9
+ *
10
+ * Decoupling here lets the augment swap in either:
11
+ * - a thin adapter wrapping the agent's primary engine (Phase 2c+), or
12
+ * - a dedicated cheaper extraction model (per Decision 6 of the
13
+ * memorist design — extraction can ride a Haiku-priced engine while
14
+ * the user-facing agent runs Sonnet).
15
+ *
16
+ * The shape is intentionally narrow: no streaming, no tool calls. The
17
+ * extraction LLM emits a JSON array; the handler parses it via parse.ts.
18
+ */
19
+ export interface ExtractionEngine {
20
+ complete(prompt: string): Promise<{ text: string; costUsd: number }>;
21
+ }
22
+
23
+ export interface ExtractionInput {
24
+ transcript: Transcript;
25
+ engine: ExtractionEngine;
26
+ /**
27
+ * Prompt template containing the literal token `{{TRANSCRIPT}}` which
28
+ * the handler replaces with a rendered transcript string. Operators
29
+ * may override the bundled `prompt.md` via
30
+ * `layeredMemory.options.autoSave.promptTemplate`.
31
+ */
32
+ promptTemplate: string;
33
+ }
34
+
35
+ /**
36
+ * Outcome of one extraction call. `costUsd` always carries the engine's
37
+ * reported spend even on failure, so the augment can attribute it to the
38
+ * originating turn's budget when integration ships in Phase 2c.
39
+ *
40
+ * Engine-call failures (network, rate limit) report `costUsd: 0` because
41
+ * no completion happened; parse failures keep the engine's costUsd
42
+ * because the model already billed for the (malformed) response.
43
+ */
44
+ export type ExtractionResult =
45
+ | { success: true; facts: ExtractedFact[]; costUsd: number }
46
+ | { success: false; error: string; costUsd: number };
47
+
48
+ /**
49
+ * Run a single extraction turn: render prompt → call engine → parse JSON.
50
+ *
51
+ * Never throws. Engine errors and parse errors map to
52
+ * `{ success: false, error, costUsd }` so the calling
53
+ * `scheduleAfterTurn` hook can log and skip without poisoning the
54
+ * background-work path. Per ADR-027, extraction failures are explicitly
55
+ * best-effort — they never affect the user-facing turn.
56
+ */
57
+ export async function handleExtractionTurn(input: ExtractionInput): Promise<ExtractionResult> {
58
+ const transcriptText = renderTranscript(input.transcript);
59
+ const prompt = input.promptTemplate.replace("{{TRANSCRIPT}}", transcriptText);
60
+
61
+ let response: { text: string; costUsd: number };
62
+ try {
63
+ response = await input.engine.complete(prompt);
64
+ } catch (err) {
65
+ return {
66
+ success: false,
67
+ error: (err as Error).message,
68
+ costUsd: 0,
69
+ };
70
+ }
71
+
72
+ const parsed = parseExtractionResponse(response.text);
73
+ if (!parsed.success) {
74
+ return {
75
+ success: false,
76
+ error: parsed.error,
77
+ costUsd: response.costUsd,
78
+ };
79
+ }
80
+
81
+ return {
82
+ success: true,
83
+ facts: parsed.facts,
84
+ costUsd: response.costUsd,
85
+ };
86
+ }
87
+
88
+ /**
89
+ * Flatten the transcript into a plain-text rendering suitable for the
90
+ * extraction prompt. Currently only emits text parts — file/data parts
91
+ * carry binary or structured payloads the extraction model can't reason
92
+ * about uniformly. Keeping this minimal also avoids accidentally leaking
93
+ * tool-call internals into the prompt body.
94
+ */
95
+ function renderTranscript(t: Transcript): string {
96
+ const lines: string[] = [];
97
+ for (const part of t.parts) {
98
+ if (part.kind === "text") {
99
+ lines.push(part.text);
100
+ }
101
+ }
102
+ return lines.join("\n");
103
+ }
@@ -0,0 +1,75 @@
1
+ /**
2
+ * Parsed shape of a single extracted fact. Mirrors the JSON schema the
3
+ * extraction prompt instructs the model to emit. The parser strips any
4
+ * fields not in this list — forward compatibility for prompt revisions
5
+ * that introduce new fields without coordinating with downstream code.
6
+ */
7
+ export interface ExtractedFact {
8
+ subject: string;
9
+ predicate: string;
10
+ object: string;
11
+ confidence: number;
12
+ isVerbatim: boolean;
13
+ }
14
+
15
+ export type ParseResult =
16
+ | { success: true; facts: ExtractedFact[] }
17
+ | { success: false; error: string };
18
+
19
+ /**
20
+ * Defensive JSON parser for the extraction LLM's response. The model
21
+ * should emit a top-level JSON array of fact objects per `prompt.md`,
22
+ * but real models occasionally drift (extra prose, missing fields,
23
+ * type mismatches). This parser:
24
+ *
25
+ * - Returns `{ success: false, error }` on any failure mode rather
26
+ * than throwing, so the auto-save handler can log and skip without
27
+ * killing the injected turn's tool-call execution.
28
+ * - Validates each entry's shape strictly: all five required fields
29
+ * must be present and the right primitive type. One bad entry
30
+ * fails the whole batch — partial writes would leave inconsistent
31
+ * storage, and the cost of a re-extraction is bounded.
32
+ * - Strips unknown keys to keep the storage schema clean across
33
+ * prompt-template revisions.
34
+ */
35
+ export function parseExtractionResponse(raw: string): ParseResult {
36
+ let parsed: unknown;
37
+ try {
38
+ parsed = JSON.parse(raw);
39
+ } catch (err) {
40
+ return { success: false, error: `failed to parse JSON: ${(err as Error).message}` };
41
+ }
42
+ if (!Array.isArray(parsed)) {
43
+ return { success: false, error: "extraction output is not a JSON array" };
44
+ }
45
+ const facts: ExtractedFact[] = [];
46
+ for (const [i, item] of parsed.entries()) {
47
+ if (item === null || typeof item !== "object") {
48
+ return { success: false, error: `entry ${i} is not an object` };
49
+ }
50
+ const e = item as Record<string, unknown>;
51
+ if (typeof e.subject !== "string") {
52
+ return { success: false, error: `entry ${i} missing/invalid subject` };
53
+ }
54
+ if (typeof e.predicate !== "string") {
55
+ return { success: false, error: `entry ${i} missing/invalid predicate` };
56
+ }
57
+ if (typeof e.object !== "string") {
58
+ return { success: false, error: `entry ${i} missing/invalid object` };
59
+ }
60
+ if (typeof e.confidence !== "number") {
61
+ return { success: false, error: `entry ${i} missing/invalid confidence` };
62
+ }
63
+ if (typeof e.isVerbatim !== "boolean") {
64
+ return { success: false, error: `entry ${i} missing/invalid isVerbatim` };
65
+ }
66
+ facts.push({
67
+ subject: e.subject,
68
+ predicate: e.predicate,
69
+ object: e.object,
70
+ confidence: e.confidence,
71
+ isVerbatim: e.isVerbatim,
72
+ });
73
+ }
74
+ return { success: true, facts };
75
+ }
@@ -0,0 +1,26 @@
1
+ You are a memory extractor. Given a conversation transcript between an agent and a peer, identify durable facts about THE PEER (not about the agent or other entities) that would be useful in future conversations.
2
+
3
+ # Transcript
4
+ {{TRANSCRIPT}}
5
+
6
+ # Output
7
+
8
+ Return a JSON array of fact objects. Each fact has these REQUIRED fields:
9
+ - subject: typically "peer", may be more specific
10
+ - predicate: a short verb-phrase (e.g. "name", "prefers", "works_at", "team", "asked_to_remember")
11
+ - object: the value
12
+ - confidence: a number 0-1, your confidence the fact is durable + accurate
13
+ - isVerbatim: true ONLY if the peer's exact phrasing matters and is captured exactly; otherwise false
14
+
15
+ Example:
16
+ [
17
+ {"subject": "peer", "predicate": "name", "object": "Sam", "confidence": 0.95, "isVerbatim": true},
18
+ {"subject": "peer", "predicate": "prefers", "object": "dark mode", "confidence": 0.8, "isVerbatim": false}
19
+ ]
20
+
21
+ # Rules
22
+
23
+ - Extract durable facts only — preferences, names, commitments, recurring topics. Skip transient ("today I'm tired"), agent-side facts ("the agent said hi"), or third-party gossip.
24
+ - DO NOT extract secrets, API keys, passwords, or anything the peer explicitly marked confidential. Skip credentials and sensitive PII entirely.
25
+ - If the conversation has nothing extractable, return [].
26
+ - Output ONLY the JSON array. No prose, no markdown, no explanation.