@aitne/daemon 0.1.9 → 0.1.11
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.
- package/dist/adapters/adapter-watchdog.d.ts +70 -0
- package/dist/adapters/adapter-watchdog.js +115 -0
- package/dist/adapters/discord.d.ts +17 -1
- package/dist/adapters/discord.js +33 -0
- package/dist/adapters/notification-manager.d.ts +27 -1
- package/dist/adapters/notification-manager.js +54 -39
- package/dist/adapters/slack-adapter.d.ts +26 -1
- package/dist/adapters/slack-adapter.js +41 -0
- package/dist/adapters/telegram-adapter.d.ts +18 -1
- package/dist/adapters/telegram-adapter.js +41 -2
- package/dist/adapters/types.d.ts +20 -0
- package/dist/adapters/whatsapp-adapter.d.ts +26 -7
- package/dist/adapters/whatsapp-adapter.js +74 -21
- package/dist/api/env-writer.d.ts +1 -0
- package/dist/api/env-writer.js +17 -7
- package/dist/api/helpers/agent-errors-registry.d.ts +5 -5
- package/dist/api/helpers/agent-errors-registry.js +5 -5
- package/dist/api/routes/agent-schedule.js +5 -1
- package/dist/api/routes/agent.js +33 -12
- package/dist/api/routes/agents/index.js +75 -16
- package/dist/api/routes/agents/views.d.ts +37 -2
- package/dist/api/routes/agents/views.js +64 -2
- package/dist/api/routes/apple-calendar.js +4 -1
- package/dist/api/routes/background-task.d.ts +22 -0
- package/dist/api/routes/background-task.js +338 -0
- package/dist/api/routes/browser-history.js +9 -1
- package/dist/api/routes/calendar.js +12 -2
- package/dist/api/routes/context/path-resolve.js +6 -1
- package/dist/api/routes/context/permissions.js +12 -2
- package/dist/api/routes/context/snapshots.js +0 -3
- package/dist/api/routes/context/write.js +3 -17
- package/dist/api/routes/dashboard/config.js +58 -12
- package/dist/api/routes/dashboard/cost-approvals.js +66 -0
- package/dist/api/routes/dashboard/notifications.js +9 -9
- package/dist/api/routes/dashboard/oauth-google.js +5 -3
- package/dist/api/routes/feedback.d.ts +3 -0
- package/dist/api/routes/feedback.js +349 -0
- package/dist/api/routes/git.js +10 -3
- package/dist/api/routes/github.js +5 -1
- package/dist/api/routes/integrations/crud-patch.js +5 -1
- package/dist/api/routes/integrations-reconcile.js +2 -2
- package/dist/api/routes/mcp.js +65 -13
- package/dist/api/routes/notion.d.ts +1 -1
- package/dist/api/routes/observations.js +7 -7
- package/dist/api/routes/obsidian.d.ts +1 -1
- package/dist/api/routes/receipts.js +5 -1
- package/dist/api/routes/setup-migrate.js +1 -1
- package/dist/api/routes/setup.js +1 -1
- package/dist/api/routes/task-flows.d.ts +1 -1
- package/dist/api/routes/task-flows.js +1 -1
- package/dist/api/routes/tuning.d.ts +29 -0
- package/dist/api/routes/tuning.js +304 -0
- package/dist/api/server.d.ts +44 -16
- package/dist/api/server.js +12 -0
- package/dist/bootstrap/adapters.d.ts +19 -0
- package/dist/bootstrap/adapters.js +61 -0
- package/dist/bootstrap/api.d.ts +5 -3
- package/dist/bootstrap/api.js +45 -13
- package/dist/bootstrap/catchup.d.ts +1 -1
- package/dist/bootstrap/catchup.js +11 -11
- package/dist/bootstrap/event-pipeline.d.ts +11 -0
- package/dist/bootstrap/event-pipeline.js +246 -8
- package/dist/bootstrap/observers.js +9 -6
- package/dist/bootstrap/schedule-helpers.d.ts +104 -6
- package/dist/bootstrap/schedule-helpers.js +172 -19
- package/dist/config.js +32 -12
- package/dist/core/agent-core.d.ts +33 -1
- package/dist/core/agent-core.js +36 -1
- package/dist/core/agents/activity-scan-cadence.d.ts +103 -0
- package/dist/core/agents/activity-scan-cadence.js +127 -0
- package/dist/core/agents/agent-route-override.d.ts +53 -0
- package/dist/core/agents/agent-route-override.js +69 -0
- package/dist/core/agents/builtin-registry.d.ts +51 -14
- package/dist/core/agents/builtin-registry.js +92 -15
- package/dist/core/agents/config-gate-reconcile.d.ts +38 -0
- package/dist/core/agents/config-gate-reconcile.js +51 -0
- package/dist/core/agents/cron-substitute.d.ts +1 -1
- package/dist/core/agents/cron-substitute.js +1 -1
- package/dist/core/agents/custom-routine-migration.d.ts +60 -0
- package/dist/core/agents/custom-routine-migration.js +149 -0
- package/dist/core/agents/firing-blocked.d.ts +1 -1
- package/dist/core/agents/hourly-cadence.d.ts +102 -0
- package/dist/core/agents/hourly-cadence.js +126 -0
- package/dist/core/agents/loader-boot.js +23 -0
- package/dist/core/agents/loader.d.ts +19 -0
- package/dist/core/agents/loader.js +34 -2
- package/dist/core/agents/override-merge.d.ts +1 -1
- package/dist/core/agents/override-merge.js +9 -1
- package/dist/core/agents/recurrence-convert.d.ts +1 -1
- package/dist/core/agents/recurrence-convert.js +1 -1
- package/dist/core/agents/recurring-schedule-adapter.js +8 -0
- package/dist/core/alerts.js +6 -6
- package/dist/core/backends/auth-health-monitor.d.ts +2 -2
- package/dist/core/backends/auth-health-monitor.js +1 -1
- package/dist/core/backends/backend-router.d.ts +27 -1
- package/dist/core/backends/backend-router.js +165 -1
- package/dist/core/backends/claude-code-core.d.ts +71 -31
- package/dist/core/backends/claude-code-core.js +282 -54
- package/dist/core/backends/cli-quota-guards.d.ts +29 -1
- package/dist/core/backends/cli-quota-guards.js +40 -5
- package/dist/core/backends/codex-core.d.ts +6 -0
- package/dist/core/backends/codex-core.js +22 -6
- package/dist/core/backends/failure-spend.d.ts +58 -0
- package/dist/core/backends/failure-spend.js +137 -0
- package/dist/core/backends/gemini-cli-core.d.ts +6 -0
- package/dist/core/backends/gemini-cli-core.js +38 -6
- package/dist/core/backends/model-registry.d.ts +1 -1
- package/dist/core/backends/model-registry.js +4 -4
- package/dist/core/backends/opencode-core.d.ts +1 -1
- package/dist/core/backends/opencode-core.js +5 -5
- package/dist/core/backends/plan-presets.js +47 -18
- package/dist/core/bang-commands/commands-cost.js +3 -1
- package/dist/core/bang-commands/commands-report.js +4 -3
- package/dist/core/bang-commands/commands-research.js +4 -1
- package/dist/core/bang-commands/commands-revert-tuning.d.ts +18 -0
- package/dist/core/bang-commands/commands-revert-tuning.js +63 -0
- package/dist/core/bang-commands/commands-stop-start.js +3 -3
- package/dist/core/bang-commands/commands-task-control.d.ts +19 -0
- package/dist/core/bang-commands/commands-task-control.js +147 -0
- package/dist/core/bang-commands/commands-wiki.js +5 -5
- package/dist/core/bang-commands/index.d.ts +2 -0
- package/dist/core/bang-commands/index.js +12 -0
- package/dist/core/bang-commands/registry.d.ts +12 -0
- package/dist/core/browser-history/research-cluster-fanout.d.ts +28 -14
- package/dist/core/browser-history/research-cluster-fanout.js +39 -16
- package/dist/core/channel-timeline.d.ts +5 -1
- package/dist/core/channel-timeline.js +13 -0
- package/dist/core/context/index-reconciler.js +5 -2
- package/dist/core/context/policy-index-reconciler.d.ts +6 -4
- package/dist/core/context/policy-index-runner.js +25 -6
- package/dist/core/context-builder-calendar.js +10 -2
- package/dist/core/context-builder-conversation.d.ts +8 -1
- package/dist/core/context-builder-conversation.js +41 -7
- package/dist/core/context-builder-yesterday.js +4 -3
- package/dist/core/context-builder.d.ts +7 -2
- package/dist/core/context-builder.js +193 -5
- package/dist/core/context-file-serializer.d.ts +1 -1
- package/dist/core/context-file-serializer.js +1 -1
- package/dist/core/context-health.js +2 -2
- package/dist/core/context-paths.d.ts +11 -1
- package/dist/core/context-paths.js +17 -1
- package/dist/core/context-validation/prepare-write.js +1 -1
- package/dist/core/context-validation/routine-rulebook.d.ts +1 -1
- package/dist/core/context-vault-aliases.d.ts +0 -13
- package/dist/core/context-vault-aliases.js +37 -0
- package/dist/core/custom-routines.d.ts +99 -0
- package/dist/core/custom-routines.js +187 -0
- package/dist/core/daemon-api-cli.js +50 -1
- package/dist/core/day-boundary.d.ts +46 -0
- package/dist/core/day-boundary.js +40 -0
- package/dist/core/dispatcher-activity-scan.d.ts +221 -0
- package/dist/core/dispatcher-activity-scan.js +775 -0
- package/dist/core/dispatcher-error-handling.d.ts +6 -11
- package/dist/core/dispatcher-error-handling.js +38 -62
- package/dist/core/dispatcher-hourly-check.js +6 -1
- package/dist/core/dispatcher-message-handler.d.ts +10 -0
- package/dist/core/dispatcher-message-handler.js +24 -0
- package/dist/core/dispatcher-morning-routine.d.ts +6 -6
- package/dist/core/dispatcher-morning-routine.js +13 -13
- package/dist/core/dispatcher-result-processor.d.ts +33 -0
- package/dist/core/dispatcher-result-processor.js +167 -11
- package/dist/core/dispatcher-scheduled-background-task.d.ts +42 -0
- package/dist/core/dispatcher-scheduled-background-task.js +89 -0
- package/dist/core/dispatcher-scheduled-tasks.d.ts +104 -1
- package/dist/core/dispatcher-scheduled-tasks.js +480 -8
- package/dist/core/dispatcher-task-delivery.d.ts +105 -0
- package/dist/core/dispatcher-task-delivery.js +555 -0
- package/dist/core/dispatcher-types.d.ts +48 -9
- package/dist/core/dispatcher-types.js +3 -3
- package/dist/core/dispatcher.d.ts +112 -31
- package/dist/core/dispatcher.js +297 -60
- package/dist/core/dm-freshness-metrics.d.ts +1 -1
- package/dist/core/drift-effects.js +2 -2
- package/dist/core/feedback/consolidation-prep.d.ts +94 -0
- package/dist/core/feedback/consolidation-prep.js +254 -0
- package/dist/core/feedback/eviction-scorer.d.ts +81 -0
- package/dist/core/feedback/eviction-scorer.js +136 -0
- package/dist/core/feedback/lesson-format.d.ts +79 -0
- package/dist/core/feedback/lesson-format.js +199 -0
- package/dist/core/feedback/lesson-injection.d.ts +98 -0
- package/dist/core/feedback/lesson-injection.js +174 -0
- package/dist/core/feedback/lesson-merge.d.ts +51 -0
- package/dist/core/feedback/lesson-merge.js +88 -0
- package/dist/core/feedback/lesson-store-overview.d.ts +46 -0
- package/dist/core/feedback/lesson-store-overview.js +42 -0
- package/dist/core/feedback/promotion-gate.d.ts +69 -0
- package/dist/core/feedback/promotion-gate.js +117 -0
- package/dist/core/feedback/regeneralization-prep.d.ts +87 -0
- package/dist/core/feedback/regeneralization-prep.js +152 -0
- package/dist/core/feedback/scope-parser.d.ts +86 -0
- package/dist/core/feedback/scope-parser.js +141 -0
- package/dist/core/feedback/self-performance-prep.d.ts +186 -0
- package/dist/core/feedback/self-performance-prep.js +541 -0
- package/dist/core/feedback/tuning-actuator.d.ts +198 -0
- package/dist/core/feedback/tuning-actuator.js +432 -0
- package/dist/core/feedback/tuning-recommender.d.ts +247 -0
- package/dist/core/feedback/tuning-recommender.js +580 -0
- package/dist/core/feedback/tuning-revert-monitor.d.ts +90 -0
- package/dist/core/feedback/tuning-revert-monitor.js +213 -0
- package/dist/core/health-monitor.d.ts +6 -0
- package/dist/core/health-monitor.js +1 -1
- package/dist/core/injection-policy.d.ts +83 -1
- package/dist/core/injection-policy.js +61 -3
- package/dist/core/integration-main-backend.js +4 -0
- package/dist/core/management-md.d.ts +2 -2
- package/dist/core/management-md.js +51 -13
- package/dist/core/morning/orchestrator.d.ts +2 -2
- package/dist/core/morning/orchestrator.js +2 -2
- package/dist/core/notification-gate.d.ts +64 -0
- package/dist/core/notification-gate.js +51 -0
- package/dist/core/notification-rate-limit.d.ts +40 -0
- package/dist/core/notification-rate-limit.js +50 -0
- package/dist/core/policy-files.d.ts +1 -1
- package/dist/core/policy-files.js +2 -2
- package/dist/core/pre-pass-freshness.d.ts +4 -4
- package/dist/core/retention.d.ts +5 -0
- package/dist/core/retention.js +20 -4
- package/dist/core/review-context.d.ts +1 -1
- package/dist/core/review-context.js +10 -5
- package/dist/core/roadmap-write-lock.d.ts +2 -1
- package/dist/core/roadmap-write-lock.js +15 -10
- package/dist/core/routine-acquisition-plan.d.ts +47 -1
- package/dist/core/routine-acquisition-plan.js +78 -20
- package/dist/core/routine-fetch-window-retry.js +7 -4
- package/dist/core/routine-fetch-window-runner.d.ts +39 -3
- package/dist/core/routine-fetch-window-runner.js +264 -13
- package/dist/core/routine-windows.d.ts +2 -2
- package/dist/core/routine-windows.js +8 -5
- package/dist/core/scheduler.d.ts +175 -16
- package/dist/core/scheduler.js +559 -102
- package/dist/core/signal-detector.d.ts +51 -1
- package/dist/core/signal-detector.js +321 -24
- package/dist/core/skills-compiler-denied-tools.js +2 -2
- package/dist/core/skills-compiler-skill-index.d.ts +2 -2
- package/dist/core/skills-compiler-skill-index.js +2 -2
- package/dist/core/skills-compiler-variants.d.ts +1 -1
- package/dist/core/skills-compiler-variants.js +8 -0
- package/dist/core/skills-compiler.d.ts +29 -26
- package/dist/core/skills-compiler.js +117 -81
- package/dist/core/skills-manifest.d.ts +37 -0
- package/dist/core/skills-manifest.js +73 -2
- package/dist/core/sleep-inhibitor.d.ts +79 -0
- package/dist/core/sleep-inhibitor.js +132 -0
- package/dist/core/slim-system-prompt-loader.d.ts +77 -0
- package/dist/core/slim-system-prompt-loader.js +141 -0
- package/dist/core/spawn-gates.d.ts +126 -0
- package/dist/core/spawn-gates.js +180 -0
- package/dist/core/today-direct-writer.d.ts +60 -14
- package/dist/core/today-direct-writer.js +90 -13
- package/dist/core/today-write-lock.d.ts +4 -2
- package/dist/core/today-write-lock.js +30 -20
- package/dist/core/wake-detector.d.ts +55 -0
- package/dist/core/wake-detector.js +80 -0
- package/dist/core/wiki/compile-lock.d.ts +1 -1
- package/dist/core/wiki/compile-lock.js +1 -1
- package/dist/core/wiki/wiki-fts.js +13 -6
- package/dist/core/workdir.js +15 -6
- package/dist/db/activity-scan-signals.d.ts +77 -0
- package/dist/db/activity-scan-signals.js +378 -0
- package/dist/db/agents-store.d.ts +28 -0
- package/dist/db/agents-store.js +62 -0
- package/dist/db/background-task-clarifications-store.d.ts +81 -0
- package/dist/db/background-task-clarifications-store.js +152 -0
- package/dist/db/background-task-store.d.ts +207 -0
- package/dist/db/background-task-store.js +380 -0
- package/dist/db/browser-history-store.d.ts +39 -6
- package/dist/db/browser-history-store.js +51 -7
- package/dist/db/browser-task-clarifications-store.d.ts +12 -0
- package/dist/db/browser-task-clarifications-store.js +35 -5
- package/dist/db/browser-task-store.d.ts +3 -0
- package/dist/db/browser-task-store.js +29 -4
- package/dist/db/deferred-dm.d.ts +86 -0
- package/dist/db/deferred-dm.js +199 -0
- package/dist/db/feedback-signals-store.d.ts +77 -0
- package/dist/db/feedback-signals-store.js +144 -0
- package/dist/db/migrations.js +380 -0
- package/dist/db/observations.d.ts +2 -2
- package/dist/db/observations.js +3 -3
- package/dist/db/schema.js +260 -22
- package/dist/db/voice-transcripts-store.d.ts +1 -1
- package/dist/index.js +86 -29
- package/dist/messaging/browser-task-mcp-notifier.d.ts +12 -70
- package/dist/messaging/browser-task-mcp-notifier.js +30 -151
- package/dist/messaging/browser-task-screenshot-attachment.d.ts +15 -0
- package/dist/messaging/browser-task-screenshot-attachment.js +63 -0
- package/dist/observers/delegated-sync-worker.d.ts +6 -6
- package/dist/observers/delegated-sync-worker.js +10 -10
- package/dist/observers/git-delegated-cron.d.ts +1 -1
- package/dist/observers/git-delegated-cron.js +2 -2
- package/dist/observers/github-poller-classifier.d.ts +3 -3
- package/dist/observers/github-poller-classifier.js +3 -3
- package/dist/observers/imminent-event-scheduler.d.ts +1 -1
- package/dist/observers/imminent-event-scheduler.js +1 -1
- package/dist/observers/mail-poller.d.ts +1 -0
- package/dist/observers/mail-poller.js +42 -3
- package/dist/observers/observation-summarizer/summarizer-client.d.ts +2 -2
- package/dist/observers/observation-summarizer/summarizer-client.js +2 -2
- package/dist/observers/observation-summarizer/worker.d.ts +2 -2
- package/dist/observers/observation-summarizer/worker.js +4 -4
- package/dist/observers/obsidian-watcher.d.ts +1 -1
- package/dist/observers/obsidian-watcher.js +1 -1
- package/dist/safety/agent-write-tracker.d.ts +4 -4
- package/dist/safety/agent-write-tracker.js +4 -4
- package/dist/safety/always-disallowed.d.ts +1 -1
- package/dist/safety/always-disallowed.js +39 -0
- package/dist/safety/audit.d.ts +43 -5
- package/dist/safety/audit.js +86 -18
- package/dist/safety/risk-classifier.d.ts +6 -0
- package/dist/safety/risk-classifier.js +97 -18
- package/dist/scheduler/activity-scan-gate.d.ts +86 -0
- package/dist/scheduler/activity-scan-gate.js +132 -0
- package/dist/services/background-task/background-task-budget.d.ts +80 -0
- package/dist/services/background-task/background-task-budget.js +91 -0
- package/dist/services/background-task/background-task-driver.d.ts +105 -0
- package/dist/services/background-task/background-task-driver.js +416 -0
- package/dist/services/background-task/background-task-runner.d.ts +96 -0
- package/dist/services/background-task/background-task-runner.js +673 -0
- package/dist/services/background-task/background-task-tools.d.ts +84 -0
- package/dist/services/background-task/background-task-tools.js +247 -0
- package/dist/services/background-task/background-task-transition-events.d.ts +43 -0
- package/dist/services/background-task/background-task-transition-events.js +54 -0
- package/dist/services/browser-history/automation/egress-denylist.d.ts +1 -1
- package/dist/services/browser-history/automation/egress-denylist.js +34 -8
- package/dist/services/browser-history/lifecycle/platform.js +44 -2
- package/dist/services/browser-history/managed-chromium/sandbox-launcher.js +0 -1
- package/dist/services/browser-task/browser-task-runner.js +53 -8
- package/dist/services/mcp/probe.js +30 -8
- package/dist/services/observations-batch.d.ts +1 -1
- package/dist/services/observations-batch.js +2 -2
- package/dist/settings/runtime-settings.d.ts +45 -12
- package/dist/settings/runtime-settings.js +215 -40
- package/dist/settings/settings-store.js +11 -3
- package/package.json +4 -4
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Feedback Learning Loop — lesson MD format (FEEDBACK_LEARNING_LOOP_DESIGN.md §3.4).
|
|
3
|
+
*
|
|
4
|
+
* A *lesson* is a generalized, injectable directive rendered as a markdown
|
|
5
|
+
* bullet that extends the existing Learned-Context convention with a
|
|
6
|
+
* machine-readable trailer:
|
|
7
|
+
*
|
|
8
|
+
* - [2026-06-07] Keep the weekly report's budget section even when spend is
|
|
9
|
+
* flat — owner flagged it missing twice.
|
|
10
|
+
* <!-- ev=2 kind=correction src=explicit conf=high last=2026-06-07 -->
|
|
11
|
+
*
|
|
12
|
+
* Optional `<!-- provisional -->` marks a lesson stored but excluded from
|
|
13
|
+
* injection until it promotes (§4 step 4).
|
|
14
|
+
*
|
|
15
|
+
* This module is pure (no I/O): parse a `## Lessons` section body into typed
|
|
16
|
+
* {@link Lesson}s and serialize them back byte-stably. It is the shared
|
|
17
|
+
* vocabulary for the promotion gate, eviction scorer, merge/dedup, and the
|
|
18
|
+
* consolidation worksheet — the §4 division-of-labour mechanical layer. The
|
|
19
|
+
* LLM authors *prose*; this code owns the *structure*.
|
|
20
|
+
*/
|
|
21
|
+
export type LessonKind = "preference" | "correction" | "do-more" | "do-less" | "constraint";
|
|
22
|
+
export type LessonSource = "explicit" | "behavioral" | "self_critique";
|
|
23
|
+
export type LessonConfidence = "high" | "medium" | "low";
|
|
24
|
+
export interface Lesson {
|
|
25
|
+
/** Leading `[YYYY-MM-DD]` — creation date, drives age-based pruning. */
|
|
26
|
+
date: string;
|
|
27
|
+
/** Human-readable directive prose, newlines collapsed to single spaces. */
|
|
28
|
+
text: string;
|
|
29
|
+
/** Evidence count (weighted sum, §4 step 4) — drives promotion + eviction. */
|
|
30
|
+
ev: number;
|
|
31
|
+
kind: LessonKind;
|
|
32
|
+
src: LessonSource;
|
|
33
|
+
conf: LessonConfidence;
|
|
34
|
+
/** Last reinforced `YYYY-MM-DD` — staleness pruning keys on this, NOT date. */
|
|
35
|
+
last: string;
|
|
36
|
+
/** Stored but excluded from injection until promoted. */
|
|
37
|
+
provisional: boolean;
|
|
38
|
+
}
|
|
39
|
+
export declare const LESSON_KINDS: ReadonlySet<string>;
|
|
40
|
+
export declare const LESSON_SOURCES: ReadonlySet<string>;
|
|
41
|
+
export declare const LESSON_CONFIDENCES: ReadonlySet<string>;
|
|
42
|
+
/**
|
|
43
|
+
* Extract a single markdown `## <header>` section body from a file, returning
|
|
44
|
+
* the lines between the header and the next `## `/`# ` heading (exclusive),
|
|
45
|
+
* or `null` when the header is absent. CRLF-tolerant.
|
|
46
|
+
*/
|
|
47
|
+
export declare function extractMarkdownSection(md: string, header: string): string | null;
|
|
48
|
+
/**
|
|
49
|
+
* Parse a `## Lessons` section body into typed lessons. Non-lesson lines
|
|
50
|
+
* (blank lines, the `<!-- scope: … -->` header comment, the `[...N omitted]`
|
|
51
|
+
* eviction marker, stray prose) are ignored. Continuation lines (indented)
|
|
52
|
+
* fold into the preceding lesson's prose. Malformed entries degrade to
|
|
53
|
+
* defaults rather than throwing, so a hand-edited file never crashes the
|
|
54
|
+
* nightly pass.
|
|
55
|
+
*/
|
|
56
|
+
export declare function parseLessonsSection(sectionBody: string): Lesson[];
|
|
57
|
+
/**
|
|
58
|
+
* Render one lesson as a markdown bullet with its trailer. Prose stays on the
|
|
59
|
+
* first line; the trailer follows on a 2-space-indented continuation line so
|
|
60
|
+
* it survives `trimBulletEntries` / `clearEntriesBefore` continuation rules.
|
|
61
|
+
*/
|
|
62
|
+
export declare function formatLesson(lesson: Lesson): string;
|
|
63
|
+
/**
|
|
64
|
+
* Render a full `## Lessons` section: the `<!-- scope: … -->` header comment
|
|
65
|
+
* (carrying the cap for at-a-glance review), the lesson bullets, and an
|
|
66
|
+
* optional eviction marker. `scopeLabel` is the {@link formatScope} string.
|
|
67
|
+
*/
|
|
68
|
+
export declare function formatLessonsSection(lessons: ReadonlyArray<Lesson>, opts: {
|
|
69
|
+
scopeLabel: string;
|
|
70
|
+
capBytes: number;
|
|
71
|
+
maxEntries: number;
|
|
72
|
+
omittedMarker?: string | null;
|
|
73
|
+
}): string;
|
|
74
|
+
/** UTF-8 byte length of a serialized lessons section — the cap unit (§6). */
|
|
75
|
+
export declare function lessonsSectionByteLength(lessons: ReadonlyArray<Lesson>, opts: {
|
|
76
|
+
scopeLabel: string;
|
|
77
|
+
capBytes: number;
|
|
78
|
+
maxEntries: number;
|
|
79
|
+
}): number;
|
|
@@ -0,0 +1,199 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Feedback Learning Loop — lesson MD format (FEEDBACK_LEARNING_LOOP_DESIGN.md §3.4).
|
|
3
|
+
*
|
|
4
|
+
* A *lesson* is a generalized, injectable directive rendered as a markdown
|
|
5
|
+
* bullet that extends the existing Learned-Context convention with a
|
|
6
|
+
* machine-readable trailer:
|
|
7
|
+
*
|
|
8
|
+
* - [2026-06-07] Keep the weekly report's budget section even when spend is
|
|
9
|
+
* flat — owner flagged it missing twice.
|
|
10
|
+
* <!-- ev=2 kind=correction src=explicit conf=high last=2026-06-07 -->
|
|
11
|
+
*
|
|
12
|
+
* Optional `<!-- provisional -->` marks a lesson stored but excluded from
|
|
13
|
+
* injection until it promotes (§4 step 4).
|
|
14
|
+
*
|
|
15
|
+
* This module is pure (no I/O): parse a `## Lessons` section body into typed
|
|
16
|
+
* {@link Lesson}s and serialize them back byte-stably. It is the shared
|
|
17
|
+
* vocabulary for the promotion gate, eviction scorer, merge/dedup, and the
|
|
18
|
+
* consolidation worksheet — the §4 division-of-labour mechanical layer. The
|
|
19
|
+
* LLM authors *prose*; this code owns the *structure*.
|
|
20
|
+
*/
|
|
21
|
+
export const LESSON_KINDS = new Set([
|
|
22
|
+
"preference",
|
|
23
|
+
"correction",
|
|
24
|
+
"do-more",
|
|
25
|
+
"do-less",
|
|
26
|
+
"constraint",
|
|
27
|
+
]);
|
|
28
|
+
export const LESSON_SOURCES = new Set([
|
|
29
|
+
"explicit",
|
|
30
|
+
"behavioral",
|
|
31
|
+
"self_critique",
|
|
32
|
+
]);
|
|
33
|
+
export const LESSON_CONFIDENCES = new Set(["high", "medium", "low"]);
|
|
34
|
+
const DATE_RE = /^\d{4}-\d{2}-\d{2}$/;
|
|
35
|
+
/** Bullet entry start: `- [YYYY-MM-DD] …` (rest of the line is prose). */
|
|
36
|
+
const ENTRY_START_RE = /^-\s+\[(\d{4}-\d{2}-\d{2})\]\s?(.*)$/;
|
|
37
|
+
/**
|
|
38
|
+
* Eviction marker `- [...N … omitted …]` — emitted by the scorer, never a
|
|
39
|
+
* lesson (its bracket holds `...`, not a date). Skipped on parse so re-reading
|
|
40
|
+
* a previously-evicted section does not fold the marker text into the
|
|
41
|
+
* preceding lesson's prose.
|
|
42
|
+
*/
|
|
43
|
+
const OMITTED_MARKER_RE = /^\s*-\s*\[\.\.\./;
|
|
44
|
+
/** Any HTML comment — trailer attrs + the `provisional` marker both ride these. */
|
|
45
|
+
const COMMENT_RE = /<!--([\s\S]*?)-->/g;
|
|
46
|
+
const PROVISIONAL_RE = /<!--\s*provisional\s*-->/i;
|
|
47
|
+
function isDate(value) {
|
|
48
|
+
return DATE_RE.test(value);
|
|
49
|
+
}
|
|
50
|
+
function coerceKind(value) {
|
|
51
|
+
return value && LESSON_KINDS.has(value) ? value : "preference";
|
|
52
|
+
}
|
|
53
|
+
function coerceSource(value) {
|
|
54
|
+
return value && LESSON_SOURCES.has(value)
|
|
55
|
+
? value
|
|
56
|
+
: "behavioral";
|
|
57
|
+
}
|
|
58
|
+
function coerceConf(value) {
|
|
59
|
+
return value && LESSON_CONFIDENCES.has(value)
|
|
60
|
+
? value
|
|
61
|
+
: "low";
|
|
62
|
+
}
|
|
63
|
+
/** Parse `ev=2 kind=correction src=explicit conf=high last=2026-06-07`. */
|
|
64
|
+
function parseTrailerAttrs(raw) {
|
|
65
|
+
const out = {};
|
|
66
|
+
for (const token of raw.trim().split(/\s+/)) {
|
|
67
|
+
const eq = token.indexOf("=");
|
|
68
|
+
if (eq <= 0)
|
|
69
|
+
continue;
|
|
70
|
+
const key = token.slice(0, eq);
|
|
71
|
+
const value = token.slice(eq + 1);
|
|
72
|
+
if (key.length > 0 && value.length > 0)
|
|
73
|
+
out[key] = value;
|
|
74
|
+
}
|
|
75
|
+
return out;
|
|
76
|
+
}
|
|
77
|
+
/**
|
|
78
|
+
* Extract a single markdown `## <header>` section body from a file, returning
|
|
79
|
+
* the lines between the header and the next `## `/`# ` heading (exclusive),
|
|
80
|
+
* or `null` when the header is absent. CRLF-tolerant.
|
|
81
|
+
*/
|
|
82
|
+
export function extractMarkdownSection(md, header) {
|
|
83
|
+
const lines = md.split(/\r?\n/);
|
|
84
|
+
const wanted = `## ${header}`;
|
|
85
|
+
const start = lines.findIndex((line) => line.trim() === wanted);
|
|
86
|
+
if (start < 0)
|
|
87
|
+
return null;
|
|
88
|
+
const body = [];
|
|
89
|
+
for (let i = start + 1; i < lines.length; i++) {
|
|
90
|
+
if (/^#{1,2}\s/.test(lines[i]))
|
|
91
|
+
break;
|
|
92
|
+
body.push(lines[i]);
|
|
93
|
+
}
|
|
94
|
+
return body.join("\n").trim();
|
|
95
|
+
}
|
|
96
|
+
/**
|
|
97
|
+
* Parse a `## Lessons` section body into typed lessons. Non-lesson lines
|
|
98
|
+
* (blank lines, the `<!-- scope: … -->` header comment, the `[...N omitted]`
|
|
99
|
+
* eviction marker, stray prose) are ignored. Continuation lines (indented)
|
|
100
|
+
* fold into the preceding lesson's prose. Malformed entries degrade to
|
|
101
|
+
* defaults rather than throwing, so a hand-edited file never crashes the
|
|
102
|
+
* nightly pass.
|
|
103
|
+
*/
|
|
104
|
+
export function parseLessonsSection(sectionBody) {
|
|
105
|
+
if (!sectionBody)
|
|
106
|
+
return [];
|
|
107
|
+
const lines = sectionBody.split(/\r?\n/);
|
|
108
|
+
const lessons = [];
|
|
109
|
+
let current = null;
|
|
110
|
+
const flush = () => {
|
|
111
|
+
if (!current)
|
|
112
|
+
return;
|
|
113
|
+
const joined = current.buffer.join("\n");
|
|
114
|
+
const provisional = PROVISIONAL_RE.test(joined);
|
|
115
|
+
// Merge attrs from every comment in the entry — `provisional` and any
|
|
116
|
+
// valueless tokens fall out in `parseTrailerAttrs` rather than breaking
|
|
117
|
+
// the whole trailer match (a hand-edited file never crashes the pass).
|
|
118
|
+
const attrs = {};
|
|
119
|
+
for (const match of joined.matchAll(COMMENT_RE)) {
|
|
120
|
+
Object.assign(attrs, parseTrailerAttrs(match[1]));
|
|
121
|
+
}
|
|
122
|
+
// Strip every HTML comment (trailer + provisional marker) from prose.
|
|
123
|
+
const text = joined
|
|
124
|
+
.replace(/<!--[\s\S]*?-->/g, " ")
|
|
125
|
+
.replace(/\s+/g, " ")
|
|
126
|
+
.trim();
|
|
127
|
+
const evNum = Number(attrs.ev);
|
|
128
|
+
const ev = Number.isFinite(evNum) && evNum > 0 ? evNum : 1;
|
|
129
|
+
const last = attrs.last && isDate(attrs.last) ? attrs.last : current.date;
|
|
130
|
+
lessons.push({
|
|
131
|
+
date: current.date,
|
|
132
|
+
text,
|
|
133
|
+
ev,
|
|
134
|
+
kind: coerceKind(attrs.kind),
|
|
135
|
+
src: coerceSource(attrs.src),
|
|
136
|
+
conf: coerceConf(attrs.conf),
|
|
137
|
+
last,
|
|
138
|
+
provisional,
|
|
139
|
+
});
|
|
140
|
+
current = null;
|
|
141
|
+
};
|
|
142
|
+
for (const line of lines) {
|
|
143
|
+
const startMatch = ENTRY_START_RE.exec(line);
|
|
144
|
+
if (startMatch) {
|
|
145
|
+
flush();
|
|
146
|
+
current = { date: startMatch[1], buffer: [startMatch[2]] };
|
|
147
|
+
continue;
|
|
148
|
+
}
|
|
149
|
+
if (OMITTED_MARKER_RE.test(line)) {
|
|
150
|
+
// The eviction marker terminates the current lesson and is not itself a
|
|
151
|
+
// lesson — neither a continuation nor a new entry.
|
|
152
|
+
flush();
|
|
153
|
+
continue;
|
|
154
|
+
}
|
|
155
|
+
if (current &&
|
|
156
|
+
(line.startsWith(" ") || line.trim().startsWith("<!--"))) {
|
|
157
|
+
// Continuation of the current lesson: indented prose, or a trailer
|
|
158
|
+
// comment the author placed on its own non-indented line.
|
|
159
|
+
current.buffer.push(line.trim());
|
|
160
|
+
}
|
|
161
|
+
else if (current) {
|
|
162
|
+
// Blank line or non-indented stray prose ends the current lesson.
|
|
163
|
+
// The stray line itself is ignored (module contract: non-lesson
|
|
164
|
+
// lines are ignored) — folding it into the preceding lesson would
|
|
165
|
+
// absorb a hand-written note into an injectable directive and
|
|
166
|
+
// re-serialize it permanently on the next consolidation.
|
|
167
|
+
flush();
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
flush();
|
|
171
|
+
return lessons;
|
|
172
|
+
}
|
|
173
|
+
/**
|
|
174
|
+
* Render one lesson as a markdown bullet with its trailer. Prose stays on the
|
|
175
|
+
* first line; the trailer follows on a 2-space-indented continuation line so
|
|
176
|
+
* it survives `trimBulletEntries` / `clearEntriesBefore` continuation rules.
|
|
177
|
+
*/
|
|
178
|
+
export function formatLesson(lesson) {
|
|
179
|
+
const trailer = `<!-- ev=${lesson.ev} kind=${lesson.kind} src=${lesson.src} ` +
|
|
180
|
+
`conf=${lesson.conf} last=${lesson.last} -->`;
|
|
181
|
+
const provisional = lesson.provisional ? " <!-- provisional -->" : "";
|
|
182
|
+
return `- [${lesson.date}] ${lesson.text}\n ${trailer}${provisional}`;
|
|
183
|
+
}
|
|
184
|
+
/**
|
|
185
|
+
* Render a full `## Lessons` section: the `<!-- scope: … -->` header comment
|
|
186
|
+
* (carrying the cap for at-a-glance review), the lesson bullets, and an
|
|
187
|
+
* optional eviction marker. `scopeLabel` is the {@link formatScope} string.
|
|
188
|
+
*/
|
|
189
|
+
export function formatLessonsSection(lessons, opts) {
|
|
190
|
+
const header = `<!-- scope: ${opts.scopeLabel} · cap: ${opts.capBytes}B · ${opts.maxEntries} entries -->`;
|
|
191
|
+
const parts = [header, ...lessons.map((lesson) => formatLesson(lesson))];
|
|
192
|
+
if (opts.omittedMarker)
|
|
193
|
+
parts.push(opts.omittedMarker);
|
|
194
|
+
return parts.join("\n");
|
|
195
|
+
}
|
|
196
|
+
/** UTF-8 byte length of a serialized lessons section — the cap unit (§6). */
|
|
197
|
+
export function lessonsSectionByteLength(lessons, opts) {
|
|
198
|
+
return Buffer.byteLength(formatLessonsSection(lessons, opts), "utf-8");
|
|
199
|
+
}
|
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Feedback Learning Loop — Stage 3 inject renderer (FEEDBACK_LEARNING_LOOP_DESIGN.md §5/§6).
|
|
3
|
+
*
|
|
4
|
+
* Renders the `<agent_lessons>` block `ContextBuilder` pushes onto DM /
|
|
5
|
+
* notify-deciding turns. Pure logic, no I/O — the FS read (the lessons file)
|
|
6
|
+
* lives in the coverage-excluded builder; this module owns the *structure* and
|
|
7
|
+
* the *cap*, and is in the 100%-covered `core/feedback/*` subset (§8).
|
|
8
|
+
*
|
|
9
|
+
* Three variants, matching the §5/§6 split — all pack the highest-signal lessons
|
|
10
|
+
* under their cap via one shared {@link packByScore} core, differing only in the
|
|
11
|
+
* wrapper tag, preamble, cap unit, entry cap, and whether an over-cap is an
|
|
12
|
+
* operability signal:
|
|
13
|
+
*
|
|
14
|
+
* - **global** (DM + review cadences + defined-agent runs) — emit every
|
|
15
|
+
* *active* (non-provisional) lesson from `policies/agent-lessons.md` when the
|
|
16
|
+
* body fits the cap. Over cap the block **degrades to the top-N lessons by
|
|
17
|
+
* score** (v1.5 §11.6) and the builder warns: the cap stays a hard,
|
|
18
|
+
* non-bypassable guarantee (the emitted body never exceeds it), but the agent
|
|
19
|
+
* keeps the highest-signal lessons instead of losing all of them. A degrade
|
|
20
|
+
* only happens if consolidation failed to pre-cap the file, so it is an
|
|
21
|
+
* operability signal (`overflow`), not a routine path.
|
|
22
|
+
* - **self** (Phase 4 — any run bound to an Agent slug) — identical render +
|
|
23
|
+
* degrade discipline to global, but the source is the per-agent
|
|
24
|
+
* `policies/agents/<slug>/lessons.md` and the wrapper is
|
|
25
|
+
* `<agent_lessons scope="self">` with a self-facing preamble. Capped at
|
|
26
|
+
* `feedbackLessonMaxBytesPerAgent`; over-cap degrades + warns exactly like
|
|
27
|
+
* global. Selected via `opts.selfScope`.
|
|
28
|
+
* - **slim** (hourly notify turn) — top-N by eviction score, greedily packed
|
|
29
|
+
* so the *whole* block stays under the hard 2048-byte budget (§6). Tail
|
|
30
|
+
* dropping is routine here (tight hourly budget), so the slim path never
|
|
31
|
+
* reports `overflow`; it just drops the lowest-signal tail.
|
|
32
|
+
*
|
|
33
|
+
* Provisional lessons are excluded from injection (§4 step 4) — they are stored
|
|
34
|
+
* for corroboration but must not yet bind behaviour. The machine-readable
|
|
35
|
+
* trailers (`<!-- ev=… -->`) are dropped: the agent consumes the directive
|
|
36
|
+
* prose, not the consolidator's bookkeeping.
|
|
37
|
+
*/
|
|
38
|
+
/**
|
|
39
|
+
* Hard inject-time byte cap for the slim hourly notify-discipline variant (§6
|
|
40
|
+
* table: "slim notify-discipline subset injected to activity_scan · hard 2048 at
|
|
41
|
+
* inject"). Exported so the builder and tests share one constant.
|
|
42
|
+
*/
|
|
43
|
+
export declare const AGENT_LESSONS_SLIM_CAP_BYTES = 2048;
|
|
44
|
+
/**
|
|
45
|
+
* Belt-and-braces entry cap for the slim variant: the byte cap is the binding
|
|
46
|
+
* constraint, but a small entry cap keeps the hourly turn focused on the
|
|
47
|
+
* highest-signal lessons even if they are individually tiny.
|
|
48
|
+
*/
|
|
49
|
+
export declare const AGENT_LESSONS_SLIM_MAX_ENTRIES = 12;
|
|
50
|
+
export interface AgentLessonsBlockResult {
|
|
51
|
+
/** The `<agent_lessons>`-wrapped block, or `null` when nothing is injected. */
|
|
52
|
+
block: string | null;
|
|
53
|
+
/**
|
|
54
|
+
* Set **only** on the global path when the full body exceeded `capBytes` and
|
|
55
|
+
* lessons had to be dropped to fit — the builder logs a warning.
|
|
56
|
+
* - `bytes` is the full (over-cap) body size that triggered the degrade;
|
|
57
|
+
* - `dropped` is how many lessons were left out (all of them when not even
|
|
58
|
+
* the single highest-scored lesson fits, in which case `block` is `null`).
|
|
59
|
+
*
|
|
60
|
+
* `null` on the slim path (tail-dropping there is routine, not a warning
|
|
61
|
+
* condition) and whenever everything fit. The cap is never breached either
|
|
62
|
+
* way: this signals a degrade, not a cap bypass.
|
|
63
|
+
*/
|
|
64
|
+
overflow: {
|
|
65
|
+
bytes: number;
|
|
66
|
+
cap: number;
|
|
67
|
+
dropped: number;
|
|
68
|
+
} | null;
|
|
69
|
+
}
|
|
70
|
+
export interface RenderAgentLessonsOptions {
|
|
71
|
+
/** Defensive byte cap for the rendered body (global/self) / whole block (slim). */
|
|
72
|
+
capBytes: number;
|
|
73
|
+
/** Slim hourly variant: top-N by score, packed under the hard byte cap. */
|
|
74
|
+
slim: boolean;
|
|
75
|
+
/** ISO timestamp used to score lessons for ranking + degrade. */
|
|
76
|
+
nowIso: string;
|
|
77
|
+
/** Override the slim entry cap (defaults to {@link AGENT_LESSONS_SLIM_MAX_ENTRIES}). */
|
|
78
|
+
maxSlimEntries?: number;
|
|
79
|
+
/**
|
|
80
|
+
* Phase 4 — render the per-agent (`agent:<slug>`) block: wrap as
|
|
81
|
+
* `<agent_lessons scope="self">` with the self preamble, same body cap +
|
|
82
|
+
* degrade discipline as global. Ignored when {@link RenderAgentLessonsOptions.slim}
|
|
83
|
+
* is set (the slim variant is global-only by construction).
|
|
84
|
+
*/
|
|
85
|
+
selfScope?: boolean;
|
|
86
|
+
}
|
|
87
|
+
/**
|
|
88
|
+
* Render the `<agent_lessons>` block for a surface, or `null` when there is
|
|
89
|
+
* nothing to inject (no file, no `## Lessons` section, no active lessons, or —
|
|
90
|
+
* global/self path only — not even the single highest-scored lesson fits the
|
|
91
|
+
* cap). When the global/self body is over cap but some lessons fit, the block
|
|
92
|
+
* degrades to the top-N by score and `overflow` is set so the caller can warn.
|
|
93
|
+
*
|
|
94
|
+
* Variant selection: `slim` → the hourly notify block; else `selfScope` → the
|
|
95
|
+
* per-agent `<agent_lessons scope="self">` block; else the global block. `slim`
|
|
96
|
+
* and `selfScope` are mutually exclusive by construction (slim wins).
|
|
97
|
+
*/
|
|
98
|
+
export declare function renderAgentLessonsBlock(fileMd: string | null, opts: RenderAgentLessonsOptions): AgentLessonsBlockResult;
|
|
@@ -0,0 +1,174 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Feedback Learning Loop — Stage 3 inject renderer (FEEDBACK_LEARNING_LOOP_DESIGN.md §5/§6).
|
|
3
|
+
*
|
|
4
|
+
* Renders the `<agent_lessons>` block `ContextBuilder` pushes onto DM /
|
|
5
|
+
* notify-deciding turns. Pure logic, no I/O — the FS read (the lessons file)
|
|
6
|
+
* lives in the coverage-excluded builder; this module owns the *structure* and
|
|
7
|
+
* the *cap*, and is in the 100%-covered `core/feedback/*` subset (§8).
|
|
8
|
+
*
|
|
9
|
+
* Three variants, matching the §5/§6 split — all pack the highest-signal lessons
|
|
10
|
+
* under their cap via one shared {@link packByScore} core, differing only in the
|
|
11
|
+
* wrapper tag, preamble, cap unit, entry cap, and whether an over-cap is an
|
|
12
|
+
* operability signal:
|
|
13
|
+
*
|
|
14
|
+
* - **global** (DM + review cadences + defined-agent runs) — emit every
|
|
15
|
+
* *active* (non-provisional) lesson from `policies/agent-lessons.md` when the
|
|
16
|
+
* body fits the cap. Over cap the block **degrades to the top-N lessons by
|
|
17
|
+
* score** (v1.5 §11.6) and the builder warns: the cap stays a hard,
|
|
18
|
+
* non-bypassable guarantee (the emitted body never exceeds it), but the agent
|
|
19
|
+
* keeps the highest-signal lessons instead of losing all of them. A degrade
|
|
20
|
+
* only happens if consolidation failed to pre-cap the file, so it is an
|
|
21
|
+
* operability signal (`overflow`), not a routine path.
|
|
22
|
+
* - **self** (Phase 4 — any run bound to an Agent slug) — identical render +
|
|
23
|
+
* degrade discipline to global, but the source is the per-agent
|
|
24
|
+
* `policies/agents/<slug>/lessons.md` and the wrapper is
|
|
25
|
+
* `<agent_lessons scope="self">` with a self-facing preamble. Capped at
|
|
26
|
+
* `feedbackLessonMaxBytesPerAgent`; over-cap degrades + warns exactly like
|
|
27
|
+
* global. Selected via `opts.selfScope`.
|
|
28
|
+
* - **slim** (hourly notify turn) — top-N by eviction score, greedily packed
|
|
29
|
+
* so the *whole* block stays under the hard 2048-byte budget (§6). Tail
|
|
30
|
+
* dropping is routine here (tight hourly budget), so the slim path never
|
|
31
|
+
* reports `overflow`; it just drops the lowest-signal tail.
|
|
32
|
+
*
|
|
33
|
+
* Provisional lessons are excluded from injection (§4 step 4) — they are stored
|
|
34
|
+
* for corroboration but must not yet bind behaviour. The machine-readable
|
|
35
|
+
* trailers (`<!-- ev=… -->`) are dropped: the agent consumes the directive
|
|
36
|
+
* prose, not the consolidator's bookkeeping.
|
|
37
|
+
*/
|
|
38
|
+
import { extractMarkdownSection, parseLessonsSection, } from "./lesson-format.js";
|
|
39
|
+
import { scoreLesson } from "./eviction-scorer.js";
|
|
40
|
+
/**
|
|
41
|
+
* Hard inject-time byte cap for the slim hourly notify-discipline variant (§6
|
|
42
|
+
* table: "slim notify-discipline subset injected to activity_scan · hard 2048 at
|
|
43
|
+
* inject"). Exported so the builder and tests share one constant.
|
|
44
|
+
*/
|
|
45
|
+
export const AGENT_LESSONS_SLIM_CAP_BYTES = 2048;
|
|
46
|
+
/**
|
|
47
|
+
* Belt-and-braces entry cap for the slim variant: the byte cap is the binding
|
|
48
|
+
* constraint, but a small entry cap keeps the hourly turn focused on the
|
|
49
|
+
* highest-signal lessons even if they are individually tiny.
|
|
50
|
+
*/
|
|
51
|
+
export const AGENT_LESSONS_SLIM_MAX_ENTRIES = 12;
|
|
52
|
+
const GLOBAL_STYLE = {
|
|
53
|
+
openTag: "<agent_lessons>",
|
|
54
|
+
preamble: "Lessons calibrated from past owner feedback and your own reviews. Treat each " +
|
|
55
|
+
"as a standing directive and prefer it over your defaults when they conflict.",
|
|
56
|
+
};
|
|
57
|
+
const SELF_STYLE = {
|
|
58
|
+
openTag: '<agent_lessons scope="self">',
|
|
59
|
+
preamble: "Lessons calibrated specifically from feedback on THIS agent's own past " +
|
|
60
|
+
"output. Treat each as a standing directive for your work and prefer it over " +
|
|
61
|
+
"your defaults when they conflict.",
|
|
62
|
+
};
|
|
63
|
+
const SLIM_PREAMBLE = "Your highest-signal operating lessons, calibrated from past feedback. Weigh " +
|
|
64
|
+
"these before deciding whether to notify the owner.";
|
|
65
|
+
/** Parse the `## Lessons` section and keep only injectable (active) lessons. */
|
|
66
|
+
function activeLessons(fileMd) {
|
|
67
|
+
const section = extractMarkdownSection(fileMd, "Lessons");
|
|
68
|
+
if (!section)
|
|
69
|
+
return [];
|
|
70
|
+
return parseLessonsSection(section).filter((lesson) => !lesson.provisional && lesson.text.length > 0);
|
|
71
|
+
}
|
|
72
|
+
/**
|
|
73
|
+
* Neutralise XML-tag breakout in lesson prose. Lesson text is
|
|
74
|
+
* LLM-synthesized during consolidation from signals that can carry
|
|
75
|
+
* untrusted excerpts, and the rendered block is framed to the model as
|
|
76
|
+
* standing directives — a body containing `</agent_lessons>` must not be
|
|
77
|
+
* able to close the wrapper and forge a sibling high-trust block. Same
|
|
78
|
+
* escape set the worksheet builders' `xmlEscape` applies to the same text
|
|
79
|
+
* on the consolidation side (`consolidation-prep.ts`).
|
|
80
|
+
*/
|
|
81
|
+
function escapeLessonText(text) {
|
|
82
|
+
return text
|
|
83
|
+
.replace(/&/g, "&")
|
|
84
|
+
.replace(/</g, "<")
|
|
85
|
+
.replace(/>/g, ">");
|
|
86
|
+
}
|
|
87
|
+
/** One lesson as an agent-facing bullet (trailer + date stripped). */
|
|
88
|
+
function bulletFor(lesson) {
|
|
89
|
+
return `- ${escapeLessonText(lesson.text)}`;
|
|
90
|
+
}
|
|
91
|
+
function wrap(style, bullets) {
|
|
92
|
+
return [style.openTag, style.preamble, ...bullets, "</agent_lessons>"].join("\n");
|
|
93
|
+
}
|
|
94
|
+
/**
|
|
95
|
+
* Greedily keep the highest-scored lessons whose serialized form — produced by
|
|
96
|
+
* `render`, the cap-measured unit — stays within `capBytes`, up to `maxEntries`.
|
|
97
|
+
* Returns the kept bullets (highest-score-first) and how many lessons dropped.
|
|
98
|
+
*
|
|
99
|
+
* Strict score-prefix: stops at the first lesson that would overflow — a
|
|
100
|
+
* lower-scored shorter tail is never swapped in, since that would violate
|
|
101
|
+
* "top-N by score". Scoring is the same `scoreLesson` consolidation eviction
|
|
102
|
+
* uses, so inject-time ranking matches on-disk ranking. Measuring and keeping
|
|
103
|
+
* the same `bullet` value guarantees what was size-checked is exactly what
|
|
104
|
+
* lands in the block.
|
|
105
|
+
*/
|
|
106
|
+
function packByScore(lessons, nowIso, capBytes, maxEntries, render) {
|
|
107
|
+
const ranked = [...lessons].sort((a, b) => scoreLesson(b, nowIso) - scoreLesson(a, nowIso));
|
|
108
|
+
const kept = [];
|
|
109
|
+
for (const lesson of ranked) {
|
|
110
|
+
if (kept.length >= maxEntries)
|
|
111
|
+
break;
|
|
112
|
+
const bullet = bulletFor(lesson);
|
|
113
|
+
if (Buffer.byteLength(render([...kept, bullet]), "utf-8") > capBytes)
|
|
114
|
+
break;
|
|
115
|
+
kept.push(bullet);
|
|
116
|
+
}
|
|
117
|
+
return { kept, dropped: ranked.length - kept.length };
|
|
118
|
+
}
|
|
119
|
+
function renderBody(lessons, capBytes, nowIso, style) {
|
|
120
|
+
// Cap on the rendered body — matches the `<management_rules>` precedent
|
|
121
|
+
// (check the content bytes, then wrap unconditionally). The wrapper tag /
|
|
122
|
+
// preamble are the only per-variant difference between global and self.
|
|
123
|
+
const bodyBytes = Buffer.byteLength(lessons.map(bulletFor).join("\n"), "utf-8");
|
|
124
|
+
if (bodyBytes <= capBytes) {
|
|
125
|
+
return { block: wrap(style, lessons.map(bulletFor)), overflow: null };
|
|
126
|
+
}
|
|
127
|
+
// Over cap → graceful degradation (v1.5 §11.6): keep the highest-scored
|
|
128
|
+
// lessons whose body still fits and warn, rather than dropping all of them.
|
|
129
|
+
// The cap stays a hard, non-bypassable guarantee (the emitted body never
|
|
130
|
+
// exceeds it); consolidation should have pre-capped the file, so a degrade
|
|
131
|
+
// here is an operability signal the builder logs.
|
|
132
|
+
const { kept, dropped } = packByScore(lessons, nowIso, capBytes, Number.POSITIVE_INFINITY, (bullets) => bullets.join("\n"));
|
|
133
|
+
const overflow = { bytes: bodyBytes, cap: capBytes, dropped };
|
|
134
|
+
// Even the single highest-scored bullet can exceed the cap → empty kept set.
|
|
135
|
+
if (kept.length === 0)
|
|
136
|
+
return { block: null, overflow };
|
|
137
|
+
return { block: wrap(style, kept), overflow };
|
|
138
|
+
}
|
|
139
|
+
function renderSlim(lessons, opts) {
|
|
140
|
+
const maxEntries = opts.maxSlimEntries ?? AGENT_LESSONS_SLIM_MAX_ENTRIES;
|
|
141
|
+
// Measure the *whole* block so the hard 2048 budget covers preamble + tags,
|
|
142
|
+
// not just the bullets. Tail-dropping is routine on the tight hourly turn, so
|
|
143
|
+
// the slim path never reports `overflow`. Slim is global-only — always the
|
|
144
|
+
// plain `<agent_lessons>` wrapper with the notify-discipline preamble.
|
|
145
|
+
const slimStyle = {
|
|
146
|
+
openTag: GLOBAL_STYLE.openTag,
|
|
147
|
+
preamble: SLIM_PREAMBLE,
|
|
148
|
+
};
|
|
149
|
+
const { kept } = packByScore(lessons, opts.nowIso, opts.capBytes, maxEntries, (bullets) => wrap(slimStyle, bullets));
|
|
150
|
+
if (kept.length === 0)
|
|
151
|
+
return { block: null, overflow: null };
|
|
152
|
+
return { block: wrap(slimStyle, kept), overflow: null };
|
|
153
|
+
}
|
|
154
|
+
/**
|
|
155
|
+
* Render the `<agent_lessons>` block for a surface, or `null` when there is
|
|
156
|
+
* nothing to inject (no file, no `## Lessons` section, no active lessons, or —
|
|
157
|
+
* global/self path only — not even the single highest-scored lesson fits the
|
|
158
|
+
* cap). When the global/self body is over cap but some lessons fit, the block
|
|
159
|
+
* degrades to the top-N by score and `overflow` is set so the caller can warn.
|
|
160
|
+
*
|
|
161
|
+
* Variant selection: `slim` → the hourly notify block; else `selfScope` → the
|
|
162
|
+
* per-agent `<agent_lessons scope="self">` block; else the global block. `slim`
|
|
163
|
+
* and `selfScope` are mutually exclusive by construction (slim wins).
|
|
164
|
+
*/
|
|
165
|
+
export function renderAgentLessonsBlock(fileMd, opts) {
|
|
166
|
+
if (!fileMd)
|
|
167
|
+
return { block: null, overflow: null };
|
|
168
|
+
const lessons = activeLessons(fileMd);
|
|
169
|
+
if (lessons.length === 0)
|
|
170
|
+
return { block: null, overflow: null };
|
|
171
|
+
if (opts.slim)
|
|
172
|
+
return renderSlim(lessons, opts);
|
|
173
|
+
return renderBody(lessons, opts.capBytes, opts.nowIso, opts.selfScope ? SELF_STYLE : GLOBAL_STYLE);
|
|
174
|
+
}
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Feedback Learning Loop — mechanical merge / dedup (FEEDBACK_LEARNING_LOOP_DESIGN.md §4 step 3, §6).
|
|
3
|
+
*
|
|
4
|
+
* The *mechanical* half of "merge, don't append": group incoming signals by a
|
|
5
|
+
* normalised summary so identical reports collapse into one candidate, and
|
|
6
|
+
* collapse near-duplicate existing lessons (summing their `ev`) before the
|
|
7
|
+
* eviction scorer runs (§6: "Near-duplicates are merged … before eviction is
|
|
8
|
+
* even considered").
|
|
9
|
+
*
|
|
10
|
+
* The *semantic* half — judging whether a candidate matches an existing
|
|
11
|
+
* lesson's *intent* and phrasing the generalization — is the LLM's job (§4
|
|
12
|
+
* division of labour). This module never paraphrases; it only collapses exact
|
|
13
|
+
* normalised-text matches, which is safe to do deterministically.
|
|
14
|
+
*/
|
|
15
|
+
import type { Lesson } from "./lesson-format.js";
|
|
16
|
+
/**
|
|
17
|
+
* Normalise a summary for mechanical equality: lowercase, strip punctuation,
|
|
18
|
+
* collapse whitespace. Two signals/lessons with the same normalised form are
|
|
19
|
+
* treated as the same candidate. Conservative — only *identical* phrasings
|
|
20
|
+
* collapse; anything semantic is left to the LLM.
|
|
21
|
+
*/
|
|
22
|
+
export declare function normalizeSummary(summary: string): string;
|
|
23
|
+
export interface SignalLike {
|
|
24
|
+
id: number;
|
|
25
|
+
summary: string;
|
|
26
|
+
}
|
|
27
|
+
export interface SignalGroup<T extends SignalLike> {
|
|
28
|
+
/** Normalised-summary key shared by every member. */
|
|
29
|
+
key: string;
|
|
30
|
+
/** Representative (first-seen) raw summary, for display. */
|
|
31
|
+
summary: string;
|
|
32
|
+
members: T[];
|
|
33
|
+
}
|
|
34
|
+
/**
|
|
35
|
+
* Group signals by normalised summary, preserving first-seen order for both
|
|
36
|
+
* the groups and their members. Empty / whitespace-only summaries are kept
|
|
37
|
+
* under a stable empty key rather than dropped, so no signal id is lost from
|
|
38
|
+
* the consume set.
|
|
39
|
+
*/
|
|
40
|
+
export declare function groupSignalsBySummary<T extends SignalLike>(signals: ReadonlyArray<T>): SignalGroup<T>[];
|
|
41
|
+
/**
|
|
42
|
+
* Collapse near-duplicate lessons (identical normalised text) into one, summing
|
|
43
|
+
* `ev`, keeping the earliest `date`, the latest `last`, the max confidence, the
|
|
44
|
+
* strongest `kind` (constraint outranks a softer kind), and OR-ing
|
|
45
|
+
* `provisional` to `false` if any duplicate is active. Order is stable: the
|
|
46
|
+
* first occurrence's position is retained.
|
|
47
|
+
*
|
|
48
|
+
* Deterministic and lossless on `ev` — the summed evidence flows straight into
|
|
49
|
+
* the eviction score so a merged lesson is *harder* to evict, never easier.
|
|
50
|
+
*/
|
|
51
|
+
export declare function dedupeLessons(lessons: ReadonlyArray<Lesson>): Lesson[];
|