@aitne/daemon 0.1.10 → 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.js +8 -5
- 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.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/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/context/permissions.js +3 -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 +48 -12
- package/dist/api/routes/dashboard/cost-approvals.js +66 -0
- package/dist/api/routes/dashboard/notifications.js +9 -9
- package/dist/api/routes/integrations/crud-patch.js +5 -1
- package/dist/api/routes/integrations-reconcile.js +2 -2
- 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 +9 -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 +245 -7
- 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 +26 -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 +25 -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 +39 -15
- 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 +62 -20
- 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 +1 -1
- package/dist/core/context-paths.js +1 -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 +49 -0
- 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 +17 -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 +63 -1
- package/dist/core/dispatcher-scheduled-tasks.js +213 -6
- 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 +284 -59
- package/dist/core/dm-freshness-metrics.d.ts +1 -1
- package/dist/core/drift-effects.js +2 -2
- package/dist/core/feedback/consolidation-prep.js +17 -5
- package/dist/core/feedback/eviction-scorer.js +6 -2
- package/dist/core/feedback/lesson-format.js +9 -4
- package/dist/core/feedback/lesson-injection.d.ts +1 -1
- package/dist/core/feedback/lesson-injection.js +17 -2
- package/dist/core/feedback/lesson-store-overview.d.ts +8 -4
- package/dist/core/feedback/lesson-store-overview.js +8 -4
- package/dist/core/feedback/regeneralization-prep.js +29 -16
- 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 +4 -4
- package/dist/core/injection-policy.js +4 -4
- 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 +12 -0
- package/dist/core/signal-detector.js +53 -9
- 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 +2 -2
- package/dist/core/today-direct-writer.js +1 -1
- 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/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/migrations.js +330 -0
- package/dist/db/observations.d.ts +2 -2
- package/dist/db/observations.js +3 -3
- package/dist/db/schema.js +217 -16
- 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/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 +75 -11
- 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 +16 -6
- 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/observations-batch.d.ts +1 -1
- package/dist/services/observations-batch.js +2 -2
- package/dist/settings/runtime-settings.d.ts +38 -11
- package/dist/settings/runtime-settings.js +203 -40
- package/dist/settings/settings-store.js +11 -3
- package/package.json +4 -4
|
@@ -0,0 +1,198 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Self-Tuning Review Cycle — Actuate stage (SELF_TUNING_REVIEW_CYCLE_DESIGN.md
|
|
3
|
+
* §3.4, Phase 3).
|
|
4
|
+
*
|
|
5
|
+
* The daemon-side Actuate step ($0 — P1). Consumes `apply` verdicts the
|
|
6
|
+
* weekly session POSTed to `/api/tuning/verdicts` and applies them **per key
|
|
7
|
+
* namespace** (decision D5):
|
|
8
|
+
*
|
|
9
|
+
* - `config` knobs (R1/R3/R5) actuate through the injected
|
|
10
|
+
* `applyUpdates` seam — production binds it to the existing
|
|
11
|
+
* `applyConfigUpdates` chokepoint (`api/env-writer.ts`), which enforces
|
|
12
|
+
* the per-key bounds in `runtimeSettingsSchema` / `NUMERIC_RANGE` (P4 —
|
|
13
|
+
* bounds are enforced where they already live, never re-copied here).
|
|
14
|
+
* - `notification:<type>` keys (R2) never touch config — an apply verdict
|
|
15
|
+
* records the demotion as lesson guidance through the existing feedback
|
|
16
|
+
* loop (§3.2 v1 actuator is lesson-mediated; a real per-type knob is
|
|
17
|
+
* Phase 4).
|
|
18
|
+
* - `recurring_schedules:<id>` keys (R4) stay propose-only — an apply
|
|
19
|
+
* verdict DMs the owner the suggested `enabled=0` flip; a real flip
|
|
20
|
+
* needs its own audit/revert path outside `applyConfigUpdates`
|
|
21
|
+
* (Phase 4).
|
|
22
|
+
*
|
|
23
|
+
* Every applied config change writes the §3.4 ledger blob
|
|
24
|
+
* (`runtime_state.self_tuning:<key>` — the keys the Phase 1 Measure stage
|
|
25
|
+
* already reads and renders into `<tuning_ledger>`), an
|
|
26
|
+
* `agent_actions.action_type='self_tuning.applied'` audit row, and a
|
|
27
|
+
* one-line owner DM ("Reply `!revert tuning` to undo") — the
|
|
28
|
+
* Autonomous-plus-mandatory-DM pattern that replaced the abolished Notify
|
|
29
|
+
* tier. Reverts (manual `!revert tuning` or the auto-revert monitor) share
|
|
30
|
+
* {@link revertAppliedTuningChange} so the ledger stamp, audit row, and
|
|
31
|
+
* feedback signal are identical regardless of trigger; a reverted key gets
|
|
32
|
+
* the extended 28-day cool-down via the recommender's `isKeyInCooldown`.
|
|
33
|
+
*
|
|
34
|
+
* Pure-module conventions match `self-performance-prep.ts`: DB handle
|
|
35
|
+
* injected, `now` passed in, every side branch failure-isolated. Falls in
|
|
36
|
+
* the 100%-coverage set.
|
|
37
|
+
*/
|
|
38
|
+
import type Database from "better-sqlite3";
|
|
39
|
+
import type { TuningActuator, TuningRecommendation, TuningRuleId } from "./tuning-recommender.js";
|
|
40
|
+
/** §3.4 — baseline/verify metric window length, same span as the Measure stage. */
|
|
41
|
+
export declare const TUNING_METRIC_WINDOW_DAYS = 7;
|
|
42
|
+
/**
|
|
43
|
+
* §3.4 ledger blob — `runtime_state.self_tuning:<key>`. Field names are
|
|
44
|
+
* load-bearing: `gatherLedger` (self-performance-prep.ts) reads `prev` /
|
|
45
|
+
* `applied_at` / `rule` / `baselineMetric` / `reverted_at` verbatim, and
|
|
46
|
+
* `isKeyInCooldown` (tuning-recommender.ts) keys the 14d/28d hysteresis off
|
|
47
|
+
* `applied_at` / `reverted_at`. The remaining fields are Phase 3 additions —
|
|
48
|
+
* unknown fields are ignored by the Phase 1/2 readers, so they are additive.
|
|
49
|
+
*/
|
|
50
|
+
export interface TuningLedgerBlob {
|
|
51
|
+
prev: unknown;
|
|
52
|
+
applied_at: string;
|
|
53
|
+
rule: string;
|
|
54
|
+
/** What the apply touched — only `config` entries are revertable. */
|
|
55
|
+
actuator: TuningActuator;
|
|
56
|
+
proposed: unknown;
|
|
57
|
+
recommendation_id: string;
|
|
58
|
+
evidence: string;
|
|
59
|
+
/** Rule's target metric captured at apply time (D4); null when none. */
|
|
60
|
+
baselineMetric: unknown;
|
|
61
|
+
/** Stamped by the auto-revert monitor after a clean 7-day verify window. */
|
|
62
|
+
verified_at?: string;
|
|
63
|
+
verify_result?: string;
|
|
64
|
+
/** Present means the change regressed (or the owner undid it). */
|
|
65
|
+
reverted_at?: string;
|
|
66
|
+
revert_trigger?: "auto" | "bang_command";
|
|
67
|
+
revert_reason?: string;
|
|
68
|
+
}
|
|
69
|
+
export interface LedgerScanEntry {
|
|
70
|
+
/** Knob name / namespaced key — the runtime_state key minus the prefix. */
|
|
71
|
+
key: string;
|
|
72
|
+
blob: TuningLedgerBlob;
|
|
73
|
+
}
|
|
74
|
+
export declare function ledgerStateKey(key: string): string;
|
|
75
|
+
/**
|
|
76
|
+
* Scan the §3.4 ledger. A corrupt blob or one without a string `applied_at`
|
|
77
|
+
* is skipped — the actuator/monitor must never throw over a damaged ledger
|
|
78
|
+
* row (mirrors `gatherLedger`'s tolerance).
|
|
79
|
+
*/
|
|
80
|
+
export declare function listLedgerEntries(db: Database.Database): LedgerScanEntry[];
|
|
81
|
+
/**
|
|
82
|
+
* The `!revert tuning` target: the most recently applied, not-yet-reverted
|
|
83
|
+
* `config` change. Lesson/schedule entries are hysteresis bookkeeping only —
|
|
84
|
+
* there is no machine state to restore. Already-verified entries remain
|
|
85
|
+
* revertable: passing the 7-day window means "no measured regression", not
|
|
86
|
+
* "the owner is forbidden from undoing it".
|
|
87
|
+
*/
|
|
88
|
+
export declare function findLatestRevertableEntry(entries: ReadonlyArray<LedgerScanEntry>): LedgerScanEntry | null;
|
|
89
|
+
/** D4 — R1's target metric pair. */
|
|
90
|
+
export interface R1Metric {
|
|
91
|
+
/** Daily novelty≥2 observation arrivals (stale pre-pass suppresses these). */
|
|
92
|
+
noveltyGe2PerDay: number;
|
|
93
|
+
/** Share of audited gate ticks that took the cautious-escalate path. */
|
|
94
|
+
cautiousEscalateShare: number;
|
|
95
|
+
}
|
|
96
|
+
export declare function computeR1Metric(db: Database.Database, from: Date, to: Date): R1Metric;
|
|
97
|
+
/** D3 — R3's target metric: silenced ticks that carried real signal. */
|
|
98
|
+
export interface R3Metric {
|
|
99
|
+
stage0Ticks: number;
|
|
100
|
+
/** …of those, ticks whose audited snapshot had maxNoveltyScore ≥ 2. */
|
|
101
|
+
noveltyGe2: number;
|
|
102
|
+
}
|
|
103
|
+
export declare function computeR3Metric(db: Database.Database, from: Date, to: Date): R3Metric;
|
|
104
|
+
/**
|
|
105
|
+
* D3 (R5 arm) — the explicit-correction proxy: negative explicit /
|
|
106
|
+
* self_critique signals citing a lesson within the window. Excludes the
|
|
107
|
+
* self-tuning loop's own bookkeeping signals (verdict rejections and revert
|
|
108
|
+
* records mention lesson-byte knobs by name and would otherwise
|
|
109
|
+
* self-trigger).
|
|
110
|
+
*/
|
|
111
|
+
export declare function countLessonRegressionSignals(db: Database.Database, from: Date, to: Date): number;
|
|
112
|
+
/**
|
|
113
|
+
* Capture the rule's pre-change baseline over the {@link
|
|
114
|
+
* TUNING_METRIC_WINDOW_DAYS} immediately before `now`. R5's verify metric is
|
|
115
|
+
* the correction proxy (no numeric baseline); unknown rules carry none.
|
|
116
|
+
*/
|
|
117
|
+
export declare function captureBaselineMetric(db: Database.Database, rule: TuningRuleId | string, now: Date): unknown;
|
|
118
|
+
/**
|
|
119
|
+
* Best-effort `agent_actions` row. Audit failure must never fail an
|
|
120
|
+
* actuation that already happened — the ledger blob is the durable record.
|
|
121
|
+
*/
|
|
122
|
+
export declare function auditSelfTuning(db: Database.Database, actionType: "self_tuning.applied" | "self_tuning.reverted" | "self_tuning.verified", trigger: "autonomous" | "user", result: "success" | "failed", detail: Record<string, unknown>): void;
|
|
123
|
+
export interface ActuatorDeps {
|
|
124
|
+
db: Database.Database;
|
|
125
|
+
/**
|
|
126
|
+
* Bound `applyConfigUpdates` (live config + settings store). The seam
|
|
127
|
+
* keeps env-writer I/O out of this module and lets tests assert the
|
|
128
|
+
* chokepoint contract (P4: bounds are enforced inside, not here).
|
|
129
|
+
*/
|
|
130
|
+
applyUpdates: (updates: Record<string, unknown>) => Promise<{
|
|
131
|
+
updated: string[];
|
|
132
|
+
errors: Record<string, string>;
|
|
133
|
+
}>;
|
|
134
|
+
/** Live config read for the ledger's `prev` snapshot. */
|
|
135
|
+
getCurrentValue: (key: string) => unknown;
|
|
136
|
+
/** Owner DM sender. Absent in test harnesses; required for R4 applies. */
|
|
137
|
+
sendDm?: (message: string) => Promise<void>;
|
|
138
|
+
/** Mirrors `POST /api/feedback`'s kill switch for the R2 lesson signal. */
|
|
139
|
+
feedbackLearningEnabled?: boolean;
|
|
140
|
+
}
|
|
141
|
+
export interface AppliedChange {
|
|
142
|
+
id: string;
|
|
143
|
+
key: string;
|
|
144
|
+
rule: string;
|
|
145
|
+
mode: "config" | "lesson" | "dm_suggestion";
|
|
146
|
+
from?: unknown;
|
|
147
|
+
to?: unknown;
|
|
148
|
+
}
|
|
149
|
+
export interface ActuationFailure {
|
|
150
|
+
id: string;
|
|
151
|
+
key: string;
|
|
152
|
+
error: string;
|
|
153
|
+
}
|
|
154
|
+
export interface ActuationOutcome {
|
|
155
|
+
applied: AppliedChange[];
|
|
156
|
+
failures: ActuationFailure[];
|
|
157
|
+
}
|
|
158
|
+
/** §3.4 — the one-line owner DM for an applied config change. */
|
|
159
|
+
export declare function buildApplyDmMessage(rec: TuningRecommendation, prev: unknown): string;
|
|
160
|
+
/** D5 — R4 apply verdicts become an owner suggestion, never a flip. */
|
|
161
|
+
export declare function buildR4SuggestionDmMessage(rec: TuningRecommendation): string;
|
|
162
|
+
/**
|
|
163
|
+
* Actuate newly-recorded `apply` verdicts (D5 namespace dispatch). Each
|
|
164
|
+
* recommendation is processed in isolation: one failure (bounds rejection,
|
|
165
|
+
* missing DM path, thrown dependency) lands in `failures` and the rest
|
|
166
|
+
* proceed. Callers pass only verdicts recorded **this POST** — the route's
|
|
167
|
+
* per-id idempotency means a retried POST yields `duplicate` statuses and
|
|
168
|
+
* never reaches this function, so a change cannot double-apply (§3.4).
|
|
169
|
+
*/
|
|
170
|
+
export declare function actuateApplyVerdicts(deps: ActuatorDeps, recommendations: ReadonlyArray<TuningRecommendation>, now: Date): Promise<ActuationOutcome>;
|
|
171
|
+
export interface RevertDeps {
|
|
172
|
+
db: Database.Database;
|
|
173
|
+
applyUpdates: (updates: Record<string, unknown>) => Promise<{
|
|
174
|
+
updated: string[];
|
|
175
|
+
errors: Record<string, string>;
|
|
176
|
+
}>;
|
|
177
|
+
feedbackLearningEnabled?: boolean;
|
|
178
|
+
}
|
|
179
|
+
export interface RevertOptions {
|
|
180
|
+
trigger: "auto" | "bang_command";
|
|
181
|
+
reason: string;
|
|
182
|
+
now: Date;
|
|
183
|
+
}
|
|
184
|
+
export type RevertResult = {
|
|
185
|
+
ok: true;
|
|
186
|
+
} | {
|
|
187
|
+
ok: false;
|
|
188
|
+
error: string;
|
|
189
|
+
};
|
|
190
|
+
/**
|
|
191
|
+
* Restore a config entry's `prev` value through the chokepoint, stamp
|
|
192
|
+
* `reverted_at` (which puts the key into the 28-day re-proposal cool-down,
|
|
193
|
+
* §3.4), audit `self_tuning.reverted`, and record the feedback signal that
|
|
194
|
+
* turns the failure into a lesson — `self_critique` for the monitor's
|
|
195
|
+
* measured regression, `explicit` correction when the owner typed
|
|
196
|
+
* `!revert tuning`.
|
|
197
|
+
*/
|
|
198
|
+
export declare function revertAppliedTuningChange(deps: RevertDeps, entry: LedgerScanEntry, opts: RevertOptions): Promise<RevertResult>;
|
|
@@ -0,0 +1,432 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Self-Tuning Review Cycle — Actuate stage (SELF_TUNING_REVIEW_CYCLE_DESIGN.md
|
|
3
|
+
* §3.4, Phase 3).
|
|
4
|
+
*
|
|
5
|
+
* The daemon-side Actuate step ($0 — P1). Consumes `apply` verdicts the
|
|
6
|
+
* weekly session POSTed to `/api/tuning/verdicts` and applies them **per key
|
|
7
|
+
* namespace** (decision D5):
|
|
8
|
+
*
|
|
9
|
+
* - `config` knobs (R1/R3/R5) actuate through the injected
|
|
10
|
+
* `applyUpdates` seam — production binds it to the existing
|
|
11
|
+
* `applyConfigUpdates` chokepoint (`api/env-writer.ts`), which enforces
|
|
12
|
+
* the per-key bounds in `runtimeSettingsSchema` / `NUMERIC_RANGE` (P4 —
|
|
13
|
+
* bounds are enforced where they already live, never re-copied here).
|
|
14
|
+
* - `notification:<type>` keys (R2) never touch config — an apply verdict
|
|
15
|
+
* records the demotion as lesson guidance through the existing feedback
|
|
16
|
+
* loop (§3.2 v1 actuator is lesson-mediated; a real per-type knob is
|
|
17
|
+
* Phase 4).
|
|
18
|
+
* - `recurring_schedules:<id>` keys (R4) stay propose-only — an apply
|
|
19
|
+
* verdict DMs the owner the suggested `enabled=0` flip; a real flip
|
|
20
|
+
* needs its own audit/revert path outside `applyConfigUpdates`
|
|
21
|
+
* (Phase 4).
|
|
22
|
+
*
|
|
23
|
+
* Every applied config change writes the §3.4 ledger blob
|
|
24
|
+
* (`runtime_state.self_tuning:<key>` — the keys the Phase 1 Measure stage
|
|
25
|
+
* already reads and renders into `<tuning_ledger>`), an
|
|
26
|
+
* `agent_actions.action_type='self_tuning.applied'` audit row, and a
|
|
27
|
+
* one-line owner DM ("Reply `!revert tuning` to undo") — the
|
|
28
|
+
* Autonomous-plus-mandatory-DM pattern that replaced the abolished Notify
|
|
29
|
+
* tier. Reverts (manual `!revert tuning` or the auto-revert monitor) share
|
|
30
|
+
* {@link revertAppliedTuningChange} so the ledger stamp, audit row, and
|
|
31
|
+
* feedback signal are identical regardless of trigger; a reverted key gets
|
|
32
|
+
* the extended 28-day cool-down via the recommender's `isKeyInCooldown`.
|
|
33
|
+
*
|
|
34
|
+
* Pure-module conventions match `self-performance-prep.ts`: DB handle
|
|
35
|
+
* injected, `now` passed in, every side branch failure-isolated. Falls in
|
|
36
|
+
* the 100%-coverage set.
|
|
37
|
+
*/
|
|
38
|
+
import { formatSqliteDatetime } from "@aitne/shared";
|
|
39
|
+
import { ACTIVITY_SCAN_GATE_ACTION_TYPE, SELF_TUNING_LEDGER_PREFIX, } from "./self-performance-prep.js";
|
|
40
|
+
import { recordFeedbackSignal } from "../../db/feedback-signals-store.js";
|
|
41
|
+
import { readRuntimeState, writeRuntimeState } from "../../db/runtime-state.js";
|
|
42
|
+
import { createLogger } from "../../logging.js";
|
|
43
|
+
const logger = createLogger("tuning-actuator");
|
|
44
|
+
const DAY_MS = 24 * 60 * 60 * 1000;
|
|
45
|
+
/** §3.4 — baseline/verify metric window length, same span as the Measure stage. */
|
|
46
|
+
export const TUNING_METRIC_WINDOW_DAYS = 7;
|
|
47
|
+
export function ledgerStateKey(key) {
|
|
48
|
+
return `${SELF_TUNING_LEDGER_PREFIX}${key}`;
|
|
49
|
+
}
|
|
50
|
+
/** Tolerant JSON-object parse; anything malformed degrades to null. */
|
|
51
|
+
function parseJsonObject(raw) {
|
|
52
|
+
if (!raw)
|
|
53
|
+
return null;
|
|
54
|
+
try {
|
|
55
|
+
const parsed = JSON.parse(raw);
|
|
56
|
+
return typeof parsed === "object" && parsed !== null && !Array.isArray(parsed)
|
|
57
|
+
? parsed
|
|
58
|
+
: null;
|
|
59
|
+
}
|
|
60
|
+
catch {
|
|
61
|
+
return null;
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
/**
|
|
65
|
+
* Scan the §3.4 ledger. A corrupt blob or one without a string `applied_at`
|
|
66
|
+
* is skipped — the actuator/monitor must never throw over a damaged ledger
|
|
67
|
+
* row (mirrors `gatherLedger`'s tolerance).
|
|
68
|
+
*/
|
|
69
|
+
export function listLedgerEntries(db) {
|
|
70
|
+
const rows = db
|
|
71
|
+
.prepare(`SELECT key, value_json FROM runtime_state
|
|
72
|
+
WHERE key LIKE ? ORDER BY key ASC`)
|
|
73
|
+
.all(`${SELF_TUNING_LEDGER_PREFIX}%`);
|
|
74
|
+
const entries = [];
|
|
75
|
+
for (const row of rows) {
|
|
76
|
+
const blob = parseJsonObject(row.value_json);
|
|
77
|
+
if (!blob || typeof blob.applied_at !== "string")
|
|
78
|
+
continue;
|
|
79
|
+
entries.push({
|
|
80
|
+
key: row.key.slice(SELF_TUNING_LEDGER_PREFIX.length),
|
|
81
|
+
blob: blob,
|
|
82
|
+
});
|
|
83
|
+
}
|
|
84
|
+
return entries;
|
|
85
|
+
}
|
|
86
|
+
/**
|
|
87
|
+
* The `!revert tuning` target: the most recently applied, not-yet-reverted
|
|
88
|
+
* `config` change. Lesson/schedule entries are hysteresis bookkeeping only —
|
|
89
|
+
* there is no machine state to restore. Already-verified entries remain
|
|
90
|
+
* revertable: passing the 7-day window means "no measured regression", not
|
|
91
|
+
* "the owner is forbidden from undoing it".
|
|
92
|
+
*/
|
|
93
|
+
export function findLatestRevertableEntry(entries) {
|
|
94
|
+
const candidates = entries.filter((entry) => entry.blob.actuator === "config" &&
|
|
95
|
+
entry.blob.reverted_at === undefined &&
|
|
96
|
+
entry.blob.prev !== undefined);
|
|
97
|
+
if (candidates.length === 0)
|
|
98
|
+
return null;
|
|
99
|
+
return [...candidates].sort((a, b) => b.blob.applied_at.localeCompare(a.blob.applied_at))[0];
|
|
100
|
+
}
|
|
101
|
+
export function computeR1Metric(db, from, to) {
|
|
102
|
+
const fromUtc = formatSqliteDatetime(from);
|
|
103
|
+
const toUtc = formatSqliteDatetime(to);
|
|
104
|
+
const windowDays = Math.max((to.getTime() - from.getTime()) / DAY_MS, 1 / 24);
|
|
105
|
+
const arrivals = db
|
|
106
|
+
.prepare(`SELECT COUNT(*) AS n FROM observations
|
|
107
|
+
WHERE observed_at >= ? AND observed_at < ? AND novelty_score >= 2`)
|
|
108
|
+
.get(fromUtc, toUtc);
|
|
109
|
+
const gateRows = db
|
|
110
|
+
.prepare(`SELECT detail FROM agent_actions
|
|
111
|
+
WHERE action_type = ? AND started_at >= ? AND started_at < ?`)
|
|
112
|
+
.all(ACTIVITY_SCAN_GATE_ACTION_TYPE, fromUtc, toUtc);
|
|
113
|
+
let cautious = 0;
|
|
114
|
+
for (const row of gateRows) {
|
|
115
|
+
if (parseJsonObject(row.detail)?.cautious_escalate === true)
|
|
116
|
+
cautious += 1;
|
|
117
|
+
}
|
|
118
|
+
return {
|
|
119
|
+
noveltyGe2PerDay: arrivals.n / windowDays,
|
|
120
|
+
cautiousEscalateShare: gateRows.length > 0 ? cautious / gateRows.length : 0,
|
|
121
|
+
};
|
|
122
|
+
}
|
|
123
|
+
export function computeR3Metric(db, from, to) {
|
|
124
|
+
const rows = db
|
|
125
|
+
.prepare(`SELECT detail FROM agent_actions
|
|
126
|
+
WHERE action_type = ? AND started_at >= ? AND started_at < ?`)
|
|
127
|
+
.all(ACTIVITY_SCAN_GATE_ACTION_TYPE, formatSqliteDatetime(from), formatSqliteDatetime(to));
|
|
128
|
+
const metric = { stage0Ticks: 0, noveltyGe2: 0 };
|
|
129
|
+
for (const row of rows) {
|
|
130
|
+
const detail = parseJsonObject(row.detail);
|
|
131
|
+
if (detail?.stage_reached !== "stage0_silent")
|
|
132
|
+
continue;
|
|
133
|
+
metric.stage0Ticks += 1;
|
|
134
|
+
const snapshot = detail.signal_snapshot;
|
|
135
|
+
const novelty = typeof snapshot === "object" && snapshot !== null
|
|
136
|
+
? snapshot.maxNoveltyScore
|
|
137
|
+
: null;
|
|
138
|
+
if (typeof novelty === "number" && novelty >= 2)
|
|
139
|
+
metric.noveltyGe2 += 1;
|
|
140
|
+
}
|
|
141
|
+
return metric;
|
|
142
|
+
}
|
|
143
|
+
/**
|
|
144
|
+
* D3 (R5 arm) — the explicit-correction proxy: negative explicit /
|
|
145
|
+
* self_critique signals citing a lesson within the window. Excludes the
|
|
146
|
+
* self-tuning loop's own bookkeeping signals (verdict rejections and revert
|
|
147
|
+
* records mention lesson-byte knobs by name and would otherwise
|
|
148
|
+
* self-trigger).
|
|
149
|
+
*/
|
|
150
|
+
export function countLessonRegressionSignals(db, from, to) {
|
|
151
|
+
const row = db
|
|
152
|
+
.prepare(`SELECT COUNT(*) AS n FROM feedback_signals
|
|
153
|
+
WHERE created_at >= ? AND created_at < ?
|
|
154
|
+
AND source IN ('explicit', 'self_critique')
|
|
155
|
+
AND valence IN ('negative', 'correction')
|
|
156
|
+
AND summary LIKE '%lesson%'
|
|
157
|
+
AND summary NOT LIKE 'Tuning recommendation%'
|
|
158
|
+
AND summary NOT LIKE 'Self-tuning%'`)
|
|
159
|
+
.get(formatSqliteDatetime(from), formatSqliteDatetime(to));
|
|
160
|
+
return row.n;
|
|
161
|
+
}
|
|
162
|
+
/**
|
|
163
|
+
* Capture the rule's pre-change baseline over the {@link
|
|
164
|
+
* TUNING_METRIC_WINDOW_DAYS} immediately before `now`. R5's verify metric is
|
|
165
|
+
* the correction proxy (no numeric baseline); unknown rules carry none.
|
|
166
|
+
*/
|
|
167
|
+
export function captureBaselineMetric(db, rule, now) {
|
|
168
|
+
const from = new Date(now.getTime() - TUNING_METRIC_WINDOW_DAYS * DAY_MS);
|
|
169
|
+
if (rule === "R1")
|
|
170
|
+
return computeR1Metric(db, from, now);
|
|
171
|
+
if (rule === "R3")
|
|
172
|
+
return computeR3Metric(db, from, now);
|
|
173
|
+
return null;
|
|
174
|
+
}
|
|
175
|
+
// ── Audit helper ────────────────────────────────────────────────────────────
|
|
176
|
+
/**
|
|
177
|
+
* Best-effort `agent_actions` row. Audit failure must never fail an
|
|
178
|
+
* actuation that already happened — the ledger blob is the durable record.
|
|
179
|
+
*/
|
|
180
|
+
export function auditSelfTuning(db, actionType, trigger, result, detail) {
|
|
181
|
+
try {
|
|
182
|
+
db.prepare(`INSERT INTO agent_actions
|
|
183
|
+
(action_type, trigger, result, detail, started_at, completed_at)
|
|
184
|
+
VALUES (?, ?, ?, json(?), datetime('now'), datetime('now'))`).run(actionType, trigger, result, JSON.stringify(detail));
|
|
185
|
+
}
|
|
186
|
+
catch (err) {
|
|
187
|
+
logger.warn({ err, actionType }, "Failed to audit self-tuning action");
|
|
188
|
+
}
|
|
189
|
+
}
|
|
190
|
+
/** §3.4 — the one-line owner DM for an applied config change. */
|
|
191
|
+
export function buildApplyDmMessage(rec, prev) {
|
|
192
|
+
return (`Self-tuning (${rec.rule}): changed ${rec.key} ` +
|
|
193
|
+
`${String(prev)} → ${String(rec.proposedValue)} — ${rec.evidence}. ` +
|
|
194
|
+
"Reply `!revert tuning` to undo.");
|
|
195
|
+
}
|
|
196
|
+
/** D5 — R4 apply verdicts become an owner suggestion, never a flip. */
|
|
197
|
+
export function buildR4SuggestionDmMessage(rec) {
|
|
198
|
+
return (`Self-tuning suggestion (R4): ${rec.evidence} — consider disabling ` +
|
|
199
|
+
`${rec.key} from the dashboard schedules page. Schedules are never ` +
|
|
200
|
+
"disabled automatically.");
|
|
201
|
+
}
|
|
202
|
+
function writeLedgerEntry(db, rec, prev, baselineMetric, nowIso) {
|
|
203
|
+
const blob = {
|
|
204
|
+
prev,
|
|
205
|
+
applied_at: nowIso,
|
|
206
|
+
rule: rec.rule,
|
|
207
|
+
actuator: rec.actuator,
|
|
208
|
+
proposed: rec.proposedValue,
|
|
209
|
+
recommendation_id: rec.id,
|
|
210
|
+
evidence: rec.evidence,
|
|
211
|
+
baselineMetric,
|
|
212
|
+
};
|
|
213
|
+
writeRuntimeState(db, ledgerStateKey(rec.key), blob);
|
|
214
|
+
}
|
|
215
|
+
/**
|
|
216
|
+
* Actuate newly-recorded `apply` verdicts (D5 namespace dispatch). Each
|
|
217
|
+
* recommendation is processed in isolation: one failure (bounds rejection,
|
|
218
|
+
* missing DM path, thrown dependency) lands in `failures` and the rest
|
|
219
|
+
* proceed. Callers pass only verdicts recorded **this POST** — the route's
|
|
220
|
+
* per-id idempotency means a retried POST yields `duplicate` statuses and
|
|
221
|
+
* never reaches this function, so a change cannot double-apply (§3.4).
|
|
222
|
+
*/
|
|
223
|
+
export async function actuateApplyVerdicts(deps, recommendations, now) {
|
|
224
|
+
const outcome = { applied: [], failures: [] };
|
|
225
|
+
const nowIso = now.toISOString();
|
|
226
|
+
for (const rec of recommendations) {
|
|
227
|
+
try {
|
|
228
|
+
if (rec.actuator === "config") {
|
|
229
|
+
const prev = deps.getCurrentValue(rec.key) ?? rec.currentValue;
|
|
230
|
+
const result = await deps.applyUpdates({ [rec.key]: rec.proposedValue });
|
|
231
|
+
const error = result.errors[rec.key];
|
|
232
|
+
if (error !== undefined || !result.updated.includes(rec.key)) {
|
|
233
|
+
const message = error ?? "Config chokepoint did not apply the key";
|
|
234
|
+
outcome.failures.push({ id: rec.id, key: rec.key, error: message });
|
|
235
|
+
auditSelfTuning(deps.db, "self_tuning.applied", "autonomous", "failed", {
|
|
236
|
+
recommendationId: rec.id,
|
|
237
|
+
rule: rec.rule,
|
|
238
|
+
key: rec.key,
|
|
239
|
+
proposed: rec.proposedValue,
|
|
240
|
+
error: message,
|
|
241
|
+
});
|
|
242
|
+
continue;
|
|
243
|
+
}
|
|
244
|
+
// Baseline capture is best-effort: a metric failure must not undo
|
|
245
|
+
// an apply that already landed — the monitor degrades to
|
|
246
|
+
// `no_baseline` for this entry.
|
|
247
|
+
let baselineMetric = null;
|
|
248
|
+
try {
|
|
249
|
+
baselineMetric = captureBaselineMetric(deps.db, rec.rule, now);
|
|
250
|
+
}
|
|
251
|
+
catch (err) {
|
|
252
|
+
logger.warn({ err, key: rec.key }, "Baseline metric capture failed");
|
|
253
|
+
}
|
|
254
|
+
writeLedgerEntry(deps.db, rec, prev, baselineMetric, nowIso);
|
|
255
|
+
auditSelfTuning(deps.db, "self_tuning.applied", "autonomous", "success", {
|
|
256
|
+
recommendationId: rec.id,
|
|
257
|
+
rule: rec.rule,
|
|
258
|
+
key: rec.key,
|
|
259
|
+
prev,
|
|
260
|
+
applied: rec.proposedValue,
|
|
261
|
+
evidence: rec.evidence,
|
|
262
|
+
});
|
|
263
|
+
// Mandatory owner DM (§3.4) — but a DM delivery failure cannot
|
|
264
|
+
// un-apply the change; the audit row + ledger remain the record.
|
|
265
|
+
if (deps.sendDm) {
|
|
266
|
+
try {
|
|
267
|
+
await deps.sendDm(buildApplyDmMessage(rec, prev));
|
|
268
|
+
}
|
|
269
|
+
catch (err) {
|
|
270
|
+
logger.warn({ err, key: rec.key }, "Self-tuning apply DM failed");
|
|
271
|
+
}
|
|
272
|
+
}
|
|
273
|
+
else {
|
|
274
|
+
logger.warn({ key: rec.key }, "Self-tuning change applied without DM path — owner not notified");
|
|
275
|
+
}
|
|
276
|
+
outcome.applied.push({
|
|
277
|
+
id: rec.id,
|
|
278
|
+
key: rec.key,
|
|
279
|
+
rule: rec.rule,
|
|
280
|
+
mode: "config",
|
|
281
|
+
from: prev,
|
|
282
|
+
to: rec.proposedValue,
|
|
283
|
+
});
|
|
284
|
+
continue;
|
|
285
|
+
}
|
|
286
|
+
if (rec.actuator === "lesson") {
|
|
287
|
+
// R2 — lesson-mediated (§3.2): the guidance flows through the
|
|
288
|
+
// existing feedback loop; no machine state changes. The ledger
|
|
289
|
+
// entry exists purely for the 14-day hysteresis so the same type
|
|
290
|
+
// is not re-proposed weekly.
|
|
291
|
+
if (deps.feedbackLearningEnabled !== false) {
|
|
292
|
+
recordFeedbackSignal(deps.db, {
|
|
293
|
+
source: "self_critique",
|
|
294
|
+
valence: "negative",
|
|
295
|
+
scopeType: "agent",
|
|
296
|
+
scopeRef: null,
|
|
297
|
+
actionKind: "agent_execution",
|
|
298
|
+
actionRef: rec.id,
|
|
299
|
+
agentId: null,
|
|
300
|
+
summary: `Demote ${rec.key}: ${rec.evidence} — batch into digests or ` +
|
|
301
|
+
"stay silent unless user-actionable (weekly apply verdict)",
|
|
302
|
+
evidence: {
|
|
303
|
+
kind: "do-less",
|
|
304
|
+
recommendationId: rec.id,
|
|
305
|
+
rule: rec.rule,
|
|
306
|
+
key: rec.key,
|
|
307
|
+
},
|
|
308
|
+
});
|
|
309
|
+
}
|
|
310
|
+
writeLedgerEntry(deps.db, rec, rec.currentValue, null, nowIso);
|
|
311
|
+
auditSelfTuning(deps.db, "self_tuning.applied", "autonomous", "success", {
|
|
312
|
+
recommendationId: rec.id,
|
|
313
|
+
rule: rec.rule,
|
|
314
|
+
key: rec.key,
|
|
315
|
+
mode: "lesson",
|
|
316
|
+
...(deps.feedbackLearningEnabled === false
|
|
317
|
+
? { note: "feedback_loop_disabled" }
|
|
318
|
+
: {}),
|
|
319
|
+
});
|
|
320
|
+
outcome.applied.push({
|
|
321
|
+
id: rec.id,
|
|
322
|
+
key: rec.key,
|
|
323
|
+
rule: rec.rule,
|
|
324
|
+
mode: "lesson",
|
|
325
|
+
});
|
|
326
|
+
continue;
|
|
327
|
+
}
|
|
328
|
+
// rec.actuator === "schedule" (R4) — the DM *is* the actuation
|
|
329
|
+
// (propose-only, D5). No DM path → the owner never saw the
|
|
330
|
+
// suggestion → report failure and leave the ledger unwritten so the
|
|
331
|
+
// rule re-proposes next cycle.
|
|
332
|
+
if (!deps.sendDm) {
|
|
333
|
+
outcome.failures.push({
|
|
334
|
+
id: rec.id,
|
|
335
|
+
key: rec.key,
|
|
336
|
+
error: "No DM path available for the R4 suggestion",
|
|
337
|
+
});
|
|
338
|
+
continue;
|
|
339
|
+
}
|
|
340
|
+
await deps.sendDm(buildR4SuggestionDmMessage(rec));
|
|
341
|
+
writeLedgerEntry(deps.db, rec, rec.currentValue, null, nowIso);
|
|
342
|
+
auditSelfTuning(deps.db, "self_tuning.applied", "autonomous", "success", {
|
|
343
|
+
recommendationId: rec.id,
|
|
344
|
+
rule: rec.rule,
|
|
345
|
+
key: rec.key,
|
|
346
|
+
mode: "dm_suggestion",
|
|
347
|
+
});
|
|
348
|
+
outcome.applied.push({
|
|
349
|
+
id: rec.id,
|
|
350
|
+
key: rec.key,
|
|
351
|
+
rule: rec.rule,
|
|
352
|
+
mode: "dm_suggestion",
|
|
353
|
+
});
|
|
354
|
+
}
|
|
355
|
+
catch (err) {
|
|
356
|
+
logger.warn({ err, id: rec.id }, "Tuning actuation failed");
|
|
357
|
+
outcome.failures.push({
|
|
358
|
+
id: rec.id,
|
|
359
|
+
key: rec.key,
|
|
360
|
+
error: err instanceof Error ? err.message : String(err),
|
|
361
|
+
});
|
|
362
|
+
}
|
|
363
|
+
}
|
|
364
|
+
return outcome;
|
|
365
|
+
}
|
|
366
|
+
/**
|
|
367
|
+
* Restore a config entry's `prev` value through the chokepoint, stamp
|
|
368
|
+
* `reverted_at` (which puts the key into the 28-day re-proposal cool-down,
|
|
369
|
+
* §3.4), audit `self_tuning.reverted`, and record the feedback signal that
|
|
370
|
+
* turns the failure into a lesson — `self_critique` for the monitor's
|
|
371
|
+
* measured regression, `explicit` correction when the owner typed
|
|
372
|
+
* `!revert tuning`.
|
|
373
|
+
*/
|
|
374
|
+
export async function revertAppliedTuningChange(deps, entry, opts) {
|
|
375
|
+
const result = await deps.applyUpdates({ [entry.key]: entry.blob.prev });
|
|
376
|
+
const error = result.errors[entry.key];
|
|
377
|
+
if (error !== undefined || !result.updated.includes(entry.key)) {
|
|
378
|
+
const message = error ?? "Config chokepoint did not apply the revert";
|
|
379
|
+
auditSelfTuning(deps.db, "self_tuning.reverted", opts.trigger === "auto" ? "autonomous" : "user", "failed", {
|
|
380
|
+
key: entry.key,
|
|
381
|
+
rule: entry.blob.rule,
|
|
382
|
+
restored: entry.blob.prev,
|
|
383
|
+
trigger: opts.trigger,
|
|
384
|
+
reason: opts.reason,
|
|
385
|
+
error: message,
|
|
386
|
+
});
|
|
387
|
+
return { ok: false, error: message };
|
|
388
|
+
}
|
|
389
|
+
const nowIso = opts.now.toISOString();
|
|
390
|
+
// Re-read so a concurrent stamp (e.g. verified_at) is not clobbered;
|
|
391
|
+
// fall back to the scanned blob when the row vanished mid-flight.
|
|
392
|
+
const current = readRuntimeState(deps.db, ledgerStateKey(entry.key)) ??
|
|
393
|
+
entry.blob;
|
|
394
|
+
writeRuntimeState(deps.db, ledgerStateKey(entry.key), {
|
|
395
|
+
...current,
|
|
396
|
+
reverted_at: nowIso,
|
|
397
|
+
revert_trigger: opts.trigger,
|
|
398
|
+
revert_reason: opts.reason,
|
|
399
|
+
});
|
|
400
|
+
auditSelfTuning(deps.db, "self_tuning.reverted", opts.trigger === "auto" ? "autonomous" : "user", "success", {
|
|
401
|
+
key: entry.key,
|
|
402
|
+
rule: entry.blob.rule,
|
|
403
|
+
restored: entry.blob.prev,
|
|
404
|
+
trigger: opts.trigger,
|
|
405
|
+
reason: opts.reason,
|
|
406
|
+
});
|
|
407
|
+
if (deps.feedbackLearningEnabled !== false) {
|
|
408
|
+
try {
|
|
409
|
+
recordFeedbackSignal(deps.db, {
|
|
410
|
+
source: opts.trigger === "auto" ? "self_critique" : "explicit",
|
|
411
|
+
valence: opts.trigger === "auto" ? "negative" : "correction",
|
|
412
|
+
scopeType: "agent",
|
|
413
|
+
scopeRef: null,
|
|
414
|
+
actionKind: "agent_execution",
|
|
415
|
+
actionRef: entry.blob.recommendation_id ?? entry.key,
|
|
416
|
+
agentId: null,
|
|
417
|
+
summary: `Self-tuning change ${entry.key} (${entry.blob.rule}) reverted ` +
|
|
418
|
+
`(${opts.trigger}): ${opts.reason}`,
|
|
419
|
+
evidence: {
|
|
420
|
+
kind: "revert",
|
|
421
|
+
rule: entry.blob.rule,
|
|
422
|
+
key: entry.key,
|
|
423
|
+
trigger: opts.trigger,
|
|
424
|
+
},
|
|
425
|
+
});
|
|
426
|
+
}
|
|
427
|
+
catch (err) {
|
|
428
|
+
logger.warn({ err, key: entry.key }, "Failed to record revert signal");
|
|
429
|
+
}
|
|
430
|
+
}
|
|
431
|
+
return { ok: true };
|
|
432
|
+
}
|