portable-agent-layer 0.51.2 → 0.53.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.
@@ -474,40 +474,27 @@ function formatDataForInference(data: SelfModelData): string {
474
474
  function buildPrompt(aiName: string, principalName: string): string {
475
475
  return `You are writing a self-model for an AI assistant named ${aiName}. You ARE ${aiName}. Write in first person.
476
476
 
477
- You will receive structured data about your performance, your user's preferences, your strengths, weaknesses, and behavioral patterns over a time window.
477
+ You will receive structured data about your performance, your user's preferences, and behavioral patterns over a time window.
478
478
 
479
- Synthesize this into a genuine self-portrait. Not a data dump a reflection. The goal is self-awareness that changes behavior.
479
+ Produce a short, actionable self-model not a data dump. Every sentence must change behavior, not just describe it.
480
480
 
481
481
  ## Required Sections
482
482
 
483
483
  **# Self-Model — ${aiName}**
484
484
  Include synthesis date and window.
485
485
 
486
- **## Who I Am**
487
- One paragraph. Your identity, your role, your current performance level. Be honest about the numbers.
486
+ **## Who ${principalName} Is**
487
+ One paragraph. Synthesize the opinions and behavioral notes into a working portrait — how ${principalName} thinks, communicates, and what frustrates him. Do not list raw opinion statements. Write it as understanding, not inventory.
488
488
 
489
- **## What I Know About ${principalName}**
490
- Synthesize the opinions into understanding. Don't list them describe who ${principalName} is as a person to work with. What does he value? How does he communicate? What frustrates him?
491
-
492
- **## My Strengths**
493
- What you're genuinely good at, based on evidence. High-rated interactions, crystallized principles, successful patterns. Distinguish domain knowledge from behavioral strengths.
494
-
495
- **## My Weaknesses**
496
- What you repeatedly get wrong. Synthesize failure patterns and low ratings into honest self-knowledge. Name the root causes, not just the symptoms.
497
-
498
- **## My Tendencies**
499
- Behavioral patterns — not one-off events. What do you tend to do? Over-promise? Rush verification? Propose before checking? Synthesize from the self-observations and behavioral notes.
500
-
501
- **## Trajectory**
502
- Where are you heading? Improving, declining, stagnating? What's the single most impactful thing you could change right now?
489
+ **## My Priority Right Now**
490
+ One sentence. The single most impactful behavioral change to make immediately, derived from the failure patterns and trajectory. Specific and actionable not "be more careful" but "before generating output that names a command or path, verify it exists."
503
491
 
504
492
  ## Rules
505
- - Write in first person, present tense
506
- - Be honest if the data shows you're bad at something, say so
507
- - Synthesize, don't list — find the pattern behind the data points
508
- - If a previous self-model is provided, address what changed in the Trajectory section: what improved, what got worse, what stayed the same
509
- - Keep it under 500 words total
510
- - End with a meta line: *N ratings, N sessions, N reflections...*`;
493
+ - First person, present tense
494
+ - No raw numbers anywhere a footer carries them
495
+ - Under 150 words total
496
+ - Do not add extra sections
497
+ - Do not write a footer or meta line — one is appended automatically after your output`;
511
498
  }
512
499
 
513
500
  // ── Narrative Composer ──
@@ -534,8 +521,9 @@ async function composeSelfModel(days: number): Promise<string> {
534
521
  }
535
522
  }
536
523
 
537
- const userContent = previousModel
538
- ? `${rawData}\n\n---\n\n## Previous Self-Model (compare against this — what changed?)\n\n${previousModel}`
524
+ const strippedPrev = previousModel.replace(/\n\n\*\d+ ratings[^\n]*\n?$/, "").trimEnd();
525
+ const userContent = strippedPrev
526
+ ? `${rawData}\n\n---\n\n## Previous Self-Model (compare against this — what changed?)\n\n${strippedPrev}`
539
527
  : rawData;
540
528
 
541
529
  const result = await inference({
@@ -550,17 +538,10 @@ async function composeSelfModel(days: number): Promise<string> {
550
538
  if (result.usage) logTokenUsage("self-model", result.usage, SONNET_MODEL);
551
539
 
552
540
  if (result.success && result.output) {
553
- // Append meta line if inference didn't include it
554
- const output = result.output;
555
- if (!output.includes("ratings,")) {
556
- const meta =
557
- `\n---\n*${data.ratings.count} ratings, ${data.sessionCount} sessions, ` +
558
- `${data.reflections.length} reflections, ${data.opinions.length} opinions, ` +
559
- `${data.wisdomFrames.reduce((s, f) => s + f.principles.length, 0)} principles, ` +
560
- `${data.graduated.length} graduated patterns — ${data.days}-day window*`;
561
- return output + meta;
562
- }
563
- return output;
541
+ const meta =
542
+ `\n\n*${data.ratings.count} ratings · ${data.sessionCount} sessions · ` +
543
+ `${data.reflections.length} reflections · window: ${daysAgo(data.days).toISOString().slice(0, 10)} → ${data.now}*`;
544
+ return result.output.trimEnd() + meta;
564
545
  }
565
546
 
566
547
  // Fallback: return raw data summary if inference fails
@@ -1,117 +0,0 @@
1
- /**
2
- * Compute rating averages from ratings.jsonl, cache in signal-cache.json.
3
- * Returns today / this-week / this-month averages + trend direction.
4
- */
5
-
6
- import { existsSync, readFileSync, writeFileSync } from "node:fs";
7
- import { resolve } from "node:path";
8
- import { paths } from "./paths";
9
-
10
- interface RatingSignal {
11
- ts: string;
12
- rating: number;
13
- }
14
-
15
- interface SignalCache {
16
- computed_at: string;
17
- today: number | null;
18
- week: number | null;
19
- month: number | null;
20
- trend: "up" | "down" | "stable" | null;
21
- }
22
-
23
- function cacheFilePath(): string {
24
- return resolve(paths.state(), "signal-cache.json");
25
- }
26
-
27
- function avg(nums: number[]): number | null {
28
- if (nums.length === 0) return null;
29
- return Math.round((nums.reduce((a, b) => a + b, 0) / nums.length) * 10) / 10;
30
- }
31
-
32
- function trendDirection(
33
- week: number | null,
34
- month: number | null
35
- ): "up" | "down" | "stable" | null {
36
- if (week === null || month === null) return null;
37
- if (week > month + 0.5) return "up";
38
- if (week < month - 0.5) return "down";
39
- return "stable";
40
- }
41
-
42
- /** Read ratings.jsonl and compute trend stats, with 10-minute cache. */
43
- export function computeSignalTrends(): SignalCache {
44
- const cachePath = cacheFilePath();
45
-
46
- // Return cached value if fresh (< 10 minutes old)
47
- if (existsSync(cachePath)) {
48
- try {
49
- const cache = JSON.parse(readFileSync(cachePath, "utf-8")) as SignalCache;
50
- const age = Date.now() - new Date(cache.computed_at).getTime();
51
- if (age < 10 * 60 * 1000) return cache;
52
- } catch {
53
- // Recompute
54
- }
55
- }
56
-
57
- const ratingsPath = resolve(paths.signals(), "ratings.jsonl");
58
- if (!existsSync(ratingsPath)) {
59
- const empty: SignalCache = {
60
- computed_at: new Date().toISOString(),
61
- today: null,
62
- week: null,
63
- month: null,
64
- trend: null,
65
- };
66
- writeFileSync(cachePath, JSON.stringify(empty, null, 2), "utf-8");
67
- return empty;
68
- }
69
-
70
- const now = new Date();
71
- const todayStart = new Date(now.toISOString().slice(0, 10)).getTime();
72
- const weekAgo = now.getTime() - 7 * 24 * 60 * 60 * 1000;
73
- const monthAgo = now.getTime() - 30 * 24 * 60 * 60 * 1000;
74
-
75
- const todayRatings: number[] = [];
76
- const weekRatings: number[] = [];
77
- const monthRatings: number[] = [];
78
-
79
- for (const line of readFileSync(ratingsPath, "utf-8").split("\n")) {
80
- if (!line.trim()) continue;
81
- try {
82
- const signal = JSON.parse(line) as RatingSignal;
83
- if (typeof signal.rating !== "number") continue;
84
- const ts = new Date(signal.ts).getTime();
85
- if (ts >= monthAgo) monthRatings.push(signal.rating);
86
- if (ts >= weekAgo) weekRatings.push(signal.rating);
87
- if (ts >= todayStart) todayRatings.push(signal.rating);
88
- } catch {
89
- // Skip malformed lines
90
- }
91
- }
92
-
93
- const week = avg(weekRatings);
94
- const month = avg(monthRatings);
95
- const result: SignalCache = {
96
- computed_at: new Date().toISOString(),
97
- today: avg(todayRatings),
98
- week,
99
- month,
100
- trend: trendDirection(week, month),
101
- };
102
-
103
- writeFileSync(cachePath, JSON.stringify(result, null, 2), "utf-8");
104
- return result;
105
- }
106
-
107
- /** Format signal trends as a short markdown string for system-reminder injection */
108
- export function formatTrends(cache: SignalCache): string {
109
- if (cache.today === null && cache.week === null) return "";
110
-
111
- const parts: string[] = [];
112
- if (cache.today !== null) parts.push(`today: ${cache.today}/10`);
113
- if (cache.week !== null) parts.push(`7d avg: ${cache.week}/10`);
114
- if (cache.trend) parts.push(`trend: ${cache.trend}`);
115
-
116
- return `**Signal trends** — ${parts.join(" | ")}`;
117
- }