johns-harness 2026.9.25 → 2026.9.27

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/CHANGELOG.md CHANGED
@@ -1,5 +1,15 @@
1
1
  # Changelog
2
2
 
3
+ ## 2026.9.27
4
+
5
+ - Family 74: the local active-session pressure guard compacts on exactly one trigger, estimated active-context tokens against the active model's declared context window (`ceil(0.7 x window)`, floored at `max(compaction.reserveTokensFloor, 50000)`; undeclared windows fall back to the runtime default, `DEFAULT_CONTEXT_TOKENS`). The fixed 1 MB transcript-bytes, 220-message and 3 GiB heap backstops are removed as triggers, so a session with 221 small messages and 528 KB no longer compacts early while a large context still compacts on real token pressure. The 5-minute cooldown, the 24-message floor and the failed-attempt growth gate remain as anti-thrash throttles, and the emergency compaction path is untouched. `contextRatio`, `minIntervalMs` and `minMessagesAfterCompaction` are the config surface; legacy `transcriptBytes`/`messageCount`/`heapUsedBytes` keys are accepted and ignored.
6
+ - Roll back with `npm i -g johns-harness@2026.9.26`.
7
+
8
+ ## 2026.9.26
9
+
10
+ - Family 73: the `content-story`, `dashboard` and `cold-outreach` skills are removed from the bundled set on request, leaving eight generic bundled skills (`code-ops`, `web-research`, `plan-archival`, `todos`, `arch-viz`, `gcp-logs`, `activity-logging`, `outlook`). They load by description match exactly as before, workspace copies override them, and `enabled: false` and `allowBundled` still apply. Nothing else changes from 2026.9.25.
11
+ - Roll back with `npm i -g johns-harness@2026.9.25`.
12
+
3
13
  ## 2026.9.25
4
14
 
5
15
  - Family 72: eleven generic skills from the author's workspace ship as bundled skills (`code-ops`, `web-research`, `plan-archival`, `todos`, `dashboard`, `arch-viz`, `content-story`, `cold-outreach`, `gcp-logs`, `activity-logging`, `outlook`), converted to fully generic wording first: no owner names, paths, repos, accounts, or products. They load by description match like every other bundled skill (none is always-on), workspace copies override them, and `enabled: false` and `allowBundled` still apply. `RULES.md` lists all of them.
package/README.md CHANGED
@@ -85,8 +85,8 @@ node johnness.mjs --help
85
85
  ### Verify
86
86
 
87
87
  ```bash
88
- npm test # 914 tests against the installed dependency tree
89
- bash scripts/verify-patches.sh # 458 checks across every patch family
88
+ npm test # 915 tests against the installed dependency tree
89
+ bash scripts/verify-patches.sh # 459 checks across every patch family
90
90
  ```
91
91
 
92
92
  ## Launch a swarm with one command
@@ -132,7 +132,7 @@ Coding agents today run as isolated processes. Two agents on the same repository
132
132
  - **Memory built for production.** Tiered recall that traces from consolidated reflections down to the verbatim record, surviving compaction and shared across the swarm. Installed and enabled by default.
133
133
  - **Production authority per commit.** Promotion is granted to an exact SHA, never to a branch name, so one worker's authorization never covers another worker's commits.
134
134
  - **An append-only action log.** Agents record every consequential action to `ACTION_LOG.md` and read it before acting.
135
- - **A fixed runtime surface.** The core does not grow on a schedule. New mechanisms enter only to solve a demonstrated production failure, one narrow, tested, reversible patch at a time: 72 patch families, each with a regression test and a rollback path.
135
+ - **A fixed runtime surface.** The core does not grow on a schedule. New mechanisms enter only to solve a demonstrated production failure, one narrow, tested, reversible patch at a time: 74 patch families, each with a regression test and a rollback path.
136
136
  - **Reliability with a definition.** Bounded retries, explicit failure states, exact-once continuation, context-pressure recovery, durable delivery, and supervised gateway recovery.
137
137
  - **Coding first, phone optional.** Direct the work at a high level from a chat channel while agents plan, implement, test, coordinate, and stage releases unattended.
138
138
  - **Local voice.** Optional native Whisper transcription of inbound audio, entirely on your machine. Off by default. [Setup](docs/NATIVE-WHISPER-MUSE.md).
@@ -199,7 +199,7 @@ The log is a [`RULES.md`](RULES.md) workspace convention enforced through the ac
199
199
 
200
200
  ## Patch families
201
201
 
202
- The runtime carries 72 patch families plus the 65.1 safe-update amendment. Each is a production fix applied at the source with a regression test, a durable patch marker, and a rollback path. A verifier runs 458 checks against the package tree and installed dependencies, and 914 tests run against the installed dependency tree.
202
+ The runtime carries 74 patch families plus the 65.1 safe-update amendment. Each is a production fix applied at the source with a regression test, a durable patch marker, and a rollback path. A verifier runs 459 checks against the package tree and installed dependencies, and 915 tests run against the installed dependency tree.
203
203
 
204
204
  Every family follows the same six steps: reproduce the failure, trace the exact runtime path, make the smallest source-level change that restores the invariant, add a regression test and a patch marker, run the verifier, and retain rollback artifacts. The full index: [`docs/PATCHES.md`](docs/PATCHES.md).
205
205
 
@@ -213,12 +213,12 @@ Every family follows the same six steps: reproduce the failure, trace the exact
213
213
  | `--dev` | Dev profile under `~/.johnness-dev` with its own gateway port and shifted derived ports |
214
214
  | Rules | [`RULES.md`](RULES.md) ships 19 operating-policy sections injected on every call. Fill in the placeholders; keep long procedures in skills. |
215
215
  | Orchestrator skill | Bundled and loaded implicitly on every install. Decide whether to pass the user's words through or expand them, then keep the briefing brief. Override with a workspace `skills/orchestrator/SKILL.md`. |
216
- | Bundled generic skills | Eleven generic skills ship in `skills/` and load by description match: `code-ops`, `web-research`, `plan-archival`, `todos`, `dashboard`, `arch-viz`, `content-story`, `cold-outreach`, `gcp-logs`, `activity-logging`, `outlook`. Workspace copies override; `enabled: false` and `allowBundled` still apply. |
216
+ | Bundled generic skills | Eight generic skills ship in `skills/` and load by description match: `code-ops`, `web-research`, `plan-archival`, `todos`, `arch-viz`, `gcp-logs`, `activity-logging`, `outlook`. Workspace copies override; `enabled: false` and `allowBundled` still apply. |
217
217
 
218
218
  ## Documentation
219
219
 
220
220
  - [`docs/index.md`](docs/index.md): runtime docs, from gateway and channels to plugins, nodes, and CLI reference.
221
- - [`docs/PATCHES.md`](docs/PATCHES.md): the 72 patch families, one record each.
221
+ - [`docs/PATCHES.md`](docs/PATCHES.md): the 74 patch families, one record each.
222
222
  - [`docs/NATIVE-WHISPER-MUSE.md`](docs/NATIVE-WHISPER-MUSE.md): local Whisper transcription setup.
223
223
  - [`RULES.md`](RULES.md): the default operating rules, including Rule Zero on production authorization.
224
224
 
@@ -7894,9 +7894,7 @@ function resolveTranscriptPolicy(params) {
7894
7894
 
7895
7895
  //#region johnness local active-session pressure guard
7896
7896
  const LOCAL_PRESSURE_GUARD_DEFAULTS = {
7897
- transcriptBytes: 1048576,
7898
- messageCount: 220,
7899
- heapUsedBytes: 3221225472,
7897
+ contextRatio: 0.7,
7900
7898
  minIntervalMs: 300000,
7901
7899
  minMessagesAfterCompaction: 24
7902
7900
  };
@@ -7907,14 +7905,24 @@ function localPressureGuardNumber(value, fallback) {
7907
7905
  function localPressureGuardSettings(cfg) {
7908
7906
  const raw = cfg?.agents?.defaults?.activeSessionPressure ?? cfg?.session?.activePressure ?? {};
7909
7907
  if (raw?.enabled === false) return null;
7908
+ /* JOHNNESS_PATCH_CONTEXT_WINDOW_COMPACTION: legacy transcriptBytes/messageCount/heapUsedBytes keys are accepted without error and ignored. Config surface: enabled, contextRatio, minIntervalMs, minMessagesAfterCompaction. */
7909
+ const contextRatio = localPressureGuardNumber(raw.contextRatio, LOCAL_PRESSURE_GUARD_DEFAULTS.contextRatio);
7910
7910
  return {
7911
- transcriptBytes: localPressureGuardNumber(raw.transcriptBytes ?? raw.maxTranscriptBytes, LOCAL_PRESSURE_GUARD_DEFAULTS.transcriptBytes),
7912
- messageCount: localPressureGuardNumber(raw.messageCount ?? raw.maxMessages, LOCAL_PRESSURE_GUARD_DEFAULTS.messageCount),
7913
- heapUsedBytes: localPressureGuardNumber(raw.heapUsedBytes ?? raw.maxHeapUsedBytes, LOCAL_PRESSURE_GUARD_DEFAULTS.heapUsedBytes),
7911
+ contextRatio: contextRatio > 0 ? contextRatio : LOCAL_PRESSURE_GUARD_DEFAULTS.contextRatio,
7914
7912
  minIntervalMs: localPressureGuardNumber(raw.minIntervalMs, LOCAL_PRESSURE_GUARD_DEFAULTS.minIntervalMs),
7915
7913
  minMessagesAfterCompaction: localPressureGuardNumber(raw.minMessagesAfterCompaction, LOCAL_PRESSURE_GUARD_DEFAULTS.minMessagesAfterCompaction)
7916
7914
  };
7917
7915
  }
7916
+ /* JOHNNESS_PATCH_CONTEXT_WINDOW_COMPACTION: single pressure trigger — estimated active-context tokens vs the active model's declared context window; the window falls back to DEFAULT_CONTEXT_TOKENS when undeclared. See docs/PATCHES.md family 74. */
7917
+ function localPressureGuardContextWindowTokens(model) {
7918
+ const declared = typeof model?.contextWindow === "number" && Number.isFinite(model.contextWindow) ? Math.floor(model.contextWindow) : 0;
7919
+ return declared > 0 ? declared : DEFAULT_CONTEXT_TOKENS;
7920
+ }
7921
+ function localPressureGuardContextThreshold(settings, contextWindowTokens, cfg) {
7922
+ const reserveTokensFloor = localPressureGuardNumber(cfg?.agents?.defaults?.compaction?.reserveTokensFloor, 2e4);
7923
+ const floor = Math.max(reserveTokensFloor, 50000);
7924
+ return Math.max(Math.ceil(settings.contextRatio * contextWindowTokens), floor);
7925
+ }
7918
7926
  /* JOHNNESS_PATCH_APPEND_ONLY_PRESSURE_BASELINE: derive a restart-safe byte baseline from the append-only JSONL so transcriptBytes pressure measures NEW growth since the last successful compaction rather than total (never-shrinking) file size. See docs/PATCHES.md family 22. Scans getEntries() newest->oldest for the newest SUCCESSFUL johnness.active-pressure-refresh marker (failed !== true) and returns its recorded post-compaction size (postCompactionBytes, else transcriptBytes). Returns 0 for legacy sessions with no such marker, so a first over-limit session still compacts once. */
7919
7927
  function computeActivePressureBaselineBytes(sessionManager) {
7920
7928
  try {
@@ -8072,12 +8080,12 @@ function compactionRotationNumber(value, fallback) {
8072
8080
  return Number.isFinite(parsed) && parsed >= 0 ? parsed : fallback;
8073
8081
  }
8074
8082
  function compactionRotationSettings(cfg) {
8075
- const pressure = localPressureGuardSettings(cfg) ?? { transcriptBytes: 1048576 };
8076
8083
  const raw = cfg?.agents?.defaults?.activeSessionPressure ?? cfg?.session?.activePressure ?? {};
8077
8084
  if (raw?.rotateAfterCompaction === false) return null;
8078
- const threshold = compactionRotationNumber(pressure?.transcriptBytes, 1048576);
8085
+ /* JOHNNESS_PATCH_CONTEXT_WINDOW_COMPACTION: the legacy transcriptBytes threshold no longer exists; the rotation size budget keeps its historical 1 MiB-derived default so default rotation behavior is unchanged. */
8086
+ const threshold = 1048576;
8079
8087
  const configuredMax = compactionRotationNumber(raw.rotationMaxBytes, 0);
8080
- const maxBytes = configuredMax > 0 ? configuredMax : threshold > 0 ? Math.max(131072, Math.min(524288, Math.floor(threshold * .45))) : 524288;
8088
+ const maxBytes = configuredMax > 0 ? configuredMax : Math.max(131072, Math.min(524288, Math.floor(threshold * .45)));
8081
8089
  return {
8082
8090
  maxBytes,
8083
8091
  maxMessages: Math.max(4, Math.floor(compactionRotationNumber(raw.rotationMaxMessages, 80))),
@@ -8213,18 +8221,20 @@ async function maybeRefreshActiveSessionForPressure(params) {
8213
8221
  const heapUsedBytes = typeof process?.memoryUsage === "function" ? process.memoryUsage().heapUsed : 0;
8214
8222
  const __hasCompactedContext = Array.isArray(params.session.messages) && params.session.messages.some((message) => message?.role === "compactionSummary");
8215
8223
  const __alreadyRotated = params.sessionManager.getEntries?.().some((entry) => entry?.type === "custom" && entry?.customType === "johnness.compaction-session-rotation");
8216
- if (settings.transcriptBytes > 0 && transcriptBytes >= settings.transcriptBytes && __hasCompactedContext && !__alreadyRotated) {
8224
+ /* JOHNNESS_PATCH_CONTEXT_WINDOW_COMPACTION: the existing-compacted-lineage rotation follows the same single context-token trigger; the byte gate is gone (family 74). */
8225
+ const __cwcWindowTokens = localPressureGuardContextWindowTokens(params.session?.model);
8226
+ const __cwcThresholdTokens = localPressureGuardContextThreshold(settings, __cwcWindowTokens, params.config);
8227
+ const __cwcEstimatedTokens = estimateMessagesTokens(messages);
8228
+ if (__cwcEstimatedTokens >= __cwcThresholdTokens && __hasCompactedContext && !__alreadyRotated) {
8217
8229
  const __existingRotation = await rotateCompactedSessionLineage({ config: params.config, session: params.session, sessionManager: params.sessionManager, sessionFile: params.sessionFile, sessionKey: params.sessionKey, sessionId: params.sessionId, reason: "existing-compacted-lineage" });
8218
8230
  if (__existingRotation.rotated) return { refreshed: true, rotated: true, reasons: ["existing-compacted-lineage"], transcriptBytes: __existingRotation.transcriptBytes, messageCount: __existingRotation.messageCount, heapUsedBytes, sessionId: __existingRotation.sessionId, sessionFile: __existingRotation.sessionFile, oldSessionId: __existingRotation.oldSessionId, oldSessionFile: __existingRotation.oldSessionFile };
8219
8231
  }
8220
8232
  const reasons = [];
8221
- /* JOHNNESS_PATCH_APPEND_ONLY_PRESSURE_BASELINE: compare NEW growth since the last successful compaction (restart-safe baseline from the append-only JSONL), not the total never-shrinking file size (family 22). */
8233
+ /* JOHNNESS_PATCH_CONTEXT_WINDOW_COMPACTION: single trigger — estimated active-context tokens vs the context-window threshold (family 74). Legacy byte/message/heap heuristics no longer gate; the family 22 append-only baseline below is computed and recorded for observability only. */
8222
8234
  const __aopBaselineBytes = computeActivePressureBaselineBytes(params.sessionManager);
8223
8235
  const __aopEffectiveBytes = effectiveTranscriptPressureBytes(transcriptBytes, __aopBaselineBytes);
8224
- if (settings.transcriptBytes > 0 && __aopEffectiveBytes >= settings.transcriptBytes) reasons.push("transcriptBytes=" + __aopEffectiveBytes + "/" + settings.transcriptBytes + (__aopBaselineBytes > 0 ? " (new since baseline=" + __aopBaselineBytes + "; raw=" + transcriptBytes + ")" : ""));
8225
- if (settings.messageCount > 0 && messageCount >= settings.messageCount) reasons.push("messages=" + messageCount + "/" + settings.messageCount);
8226
- if (settings.heapUsedBytes > 0 && heapUsedBytes >= settings.heapUsedBytes) reasons.push("heapUsed=" + heapUsedBytes + "/" + settings.heapUsedBytes);
8227
- if (reasons.length === 0) return { refreshed: false, reason: "below threshold", transcriptBytes, messageCount, heapUsedBytes };
8236
+ if (__cwcEstimatedTokens >= __cwcThresholdTokens) reasons.push("contextTokens=" + __cwcEstimatedTokens + "/" + __cwcThresholdTokens);
8237
+ if (reasons.length === 0) return { refreshed: false, reason: "below threshold", contextTokens: __cwcEstimatedTokens, contextThreshold: __cwcThresholdTokens, baselineBytes: __aopBaselineBytes, effectiveTranscriptBytes: __aopEffectiveBytes, transcriptBytes, messageCount, heapUsedBytes };
8228
8238
  let last = 0;
8229
8239
  let lastFailed = false;
8230
8240
  let lastBytes = 0;
@@ -16150,13 +16160,14 @@ async function maybeCompactActiveRunForPressure(params) {
16150
16160
  } catch {}
16151
16161
  const heapUsedBytes = typeof process?.memoryUsage === "function" ? process.memoryUsage().heapUsed : 0;
16152
16162
  const reasons = [];
16153
- /* JOHNNESS_PATCH_APPEND_ONLY_PRESSURE_BASELINE: compare NEW growth since the last successful compaction (restart-safe baseline from the append-only JSONL), not the total never-shrinking file size (family 22). */
16163
+ /* JOHNNESS_PATCH_CONTEXT_WINDOW_COMPACTION: single trigger — estimated active-context tokens vs the context-window threshold (family 74). Legacy byte/message/heap heuristics no longer gate; the family 22 append-only baseline below is computed and recorded for observability only. */
16154
16164
  const __aopBaselineBytes = computeActivePressureBaselineBytes(params.sessionManager);
16155
16165
  const __aopEffectiveBytes = effectiveTranscriptPressureBytes(transcriptBytes, __aopBaselineBytes);
16156
- if (settings.transcriptBytes > 0 && __aopEffectiveBytes >= settings.transcriptBytes) reasons.push("transcriptBytes=" + __aopEffectiveBytes + "/" + settings.transcriptBytes + (__aopBaselineBytes > 0 ? " (new since baseline=" + __aopBaselineBytes + "; raw=" + transcriptBytes + ")" : ""));
16157
- if (settings.messageCount > 0 && messageCount >= settings.messageCount) reasons.push("messages=" + messageCount + "/" + settings.messageCount);
16158
- if (settings.heapUsedBytes > 0 && heapUsedBytes >= settings.heapUsedBytes) reasons.push("heapUsed=" + heapUsedBytes + "/" + settings.heapUsedBytes);
16159
- if (reasons.length === 0) return { refreshed: false, reason: "below threshold", transcriptBytes, messageCount, heapUsedBytes };
16166
+ const __cwcWindowTokens = localPressureGuardContextWindowTokens(params.session?.model);
16167
+ const __cwcThresholdTokens = localPressureGuardContextThreshold(settings, __cwcWindowTokens, params.config);
16168
+ const __cwcEstimatedTokens = estimateMessagesTokens(messages);
16169
+ if (__cwcEstimatedTokens >= __cwcThresholdTokens) reasons.push("contextTokens=" + __cwcEstimatedTokens + "/" + __cwcThresholdTokens);
16170
+ if (reasons.length === 0) return { refreshed: false, reason: "below threshold", contextTokens: __cwcEstimatedTokens, contextThreshold: __cwcThresholdTokens, baselineBytes: __aopBaselineBytes, effectiveTranscriptBytes: __aopEffectiveBytes, transcriptBytes, messageCount, heapUsedBytes };
16160
16171
  let last = 0;
16161
16172
  let lastFailed = false;
16162
16173
  let lastBytes = 0;
@@ -96130,9 +96130,7 @@ function buildEmbeddedExtensionFactories(params) {
96130
96130
 
96131
96131
  //#region johnness local active-session pressure guard
96132
96132
  const LOCAL_PRESSURE_GUARD_DEFAULTS = {
96133
- transcriptBytes: 1048576,
96134
- messageCount: 220,
96135
- heapUsedBytes: 3221225472,
96133
+ contextRatio: 0.7,
96136
96134
  minIntervalMs: 300000,
96137
96135
  minMessagesAfterCompaction: 24
96138
96136
  };
@@ -96143,14 +96141,24 @@ function localPressureGuardNumber(value, fallback) {
96143
96141
  function localPressureGuardSettings(cfg) {
96144
96142
  const raw = cfg?.agents?.defaults?.activeSessionPressure ?? cfg?.session?.activePressure ?? {};
96145
96143
  if (raw?.enabled === false) return null;
96144
+ /* JOHNNESS_PATCH_CONTEXT_WINDOW_COMPACTION: legacy transcriptBytes/messageCount/heapUsedBytes keys are accepted without error and ignored. Config surface: enabled, contextRatio, minIntervalMs, minMessagesAfterCompaction. */
96145
+ const contextRatio = localPressureGuardNumber(raw.contextRatio, LOCAL_PRESSURE_GUARD_DEFAULTS.contextRatio);
96146
96146
  return {
96147
- transcriptBytes: localPressureGuardNumber(raw.transcriptBytes ?? raw.maxTranscriptBytes, LOCAL_PRESSURE_GUARD_DEFAULTS.transcriptBytes),
96148
- messageCount: localPressureGuardNumber(raw.messageCount ?? raw.maxMessages, LOCAL_PRESSURE_GUARD_DEFAULTS.messageCount),
96149
- heapUsedBytes: localPressureGuardNumber(raw.heapUsedBytes ?? raw.maxHeapUsedBytes, LOCAL_PRESSURE_GUARD_DEFAULTS.heapUsedBytes),
96147
+ contextRatio: contextRatio > 0 ? contextRatio : LOCAL_PRESSURE_GUARD_DEFAULTS.contextRatio,
96150
96148
  minIntervalMs: localPressureGuardNumber(raw.minIntervalMs, LOCAL_PRESSURE_GUARD_DEFAULTS.minIntervalMs),
96151
96149
  minMessagesAfterCompaction: localPressureGuardNumber(raw.minMessagesAfterCompaction, LOCAL_PRESSURE_GUARD_DEFAULTS.minMessagesAfterCompaction)
96152
96150
  };
96153
96151
  }
96152
+ /* JOHNNESS_PATCH_CONTEXT_WINDOW_COMPACTION: single pressure trigger — estimated active-context tokens vs the active model's declared context window; the window falls back to DEFAULT_CONTEXT_TOKENS when undeclared. See docs/PATCHES.md family 74. */
96153
+ function localPressureGuardContextWindowTokens(model) {
96154
+ const declared = typeof model?.contextWindow === "number" && Number.isFinite(model.contextWindow) ? Math.floor(model.contextWindow) : 0;
96155
+ return declared > 0 ? declared : DEFAULT_CONTEXT_TOKENS;
96156
+ }
96157
+ function localPressureGuardContextThreshold(settings, contextWindowTokens, cfg) {
96158
+ const reserveTokensFloor = localPressureGuardNumber(cfg?.agents?.defaults?.compaction?.reserveTokensFloor, 2e4);
96159
+ const floor = Math.max(reserveTokensFloor, 50000);
96160
+ return Math.max(Math.ceil(settings.contextRatio * contextWindowTokens), floor);
96161
+ }
96154
96162
  /* JOHNNESS_PATCH_APPEND_ONLY_PRESSURE_BASELINE: derive a restart-safe byte baseline from the append-only JSONL so transcriptBytes pressure measures NEW growth since the last successful compaction rather than total (never-shrinking) file size. See docs/PATCHES.md family 22. Scans getEntries() newest->oldest for the newest SUCCESSFUL johnness.active-pressure-refresh marker (failed !== true) and returns its recorded post-compaction size (postCompactionBytes, else transcriptBytes). Returns 0 for legacy sessions with no such marker, so a first over-limit session still compacts once. */
96155
96163
  function computeActivePressureBaselineBytes(sessionManager) {
96156
96164
  try {
@@ -96308,12 +96316,12 @@ function compactionRotationNumber(value, fallback) {
96308
96316
  return Number.isFinite(parsed) && parsed >= 0 ? parsed : fallback;
96309
96317
  }
96310
96318
  function compactionRotationSettings(cfg) {
96311
- const pressure = localPressureGuardSettings(cfg) ?? { transcriptBytes: 1048576 };
96312
96319
  const raw = cfg?.agents?.defaults?.activeSessionPressure ?? cfg?.session?.activePressure ?? {};
96313
96320
  if (raw?.rotateAfterCompaction === false) return null;
96314
- const threshold = compactionRotationNumber(pressure?.transcriptBytes, 1048576);
96321
+ /* JOHNNESS_PATCH_CONTEXT_WINDOW_COMPACTION: the legacy transcriptBytes threshold no longer exists; the rotation size budget keeps its historical 1 MiB-derived default so default rotation behavior is unchanged. */
96322
+ const threshold = 1048576;
96315
96323
  const configuredMax = compactionRotationNumber(raw.rotationMaxBytes, 0);
96316
- const maxBytes = configuredMax > 0 ? configuredMax : threshold > 0 ? Math.max(131072, Math.min(524288, Math.floor(threshold * .45))) : 524288;
96324
+ const maxBytes = configuredMax > 0 ? configuredMax : Math.max(131072, Math.min(524288, Math.floor(threshold * .45)));
96317
96325
  return {
96318
96326
  maxBytes,
96319
96327
  maxMessages: Math.max(4, Math.floor(compactionRotationNumber(raw.rotationMaxMessages, 80))),
@@ -96449,18 +96457,20 @@ async function maybeRefreshActiveSessionForPressure(params) {
96449
96457
  const heapUsedBytes = typeof process?.memoryUsage === "function" ? process.memoryUsage().heapUsed : 0;
96450
96458
  const __hasCompactedContext = Array.isArray(params.session.messages) && params.session.messages.some((message) => message?.role === "compactionSummary");
96451
96459
  const __alreadyRotated = params.sessionManager.getEntries?.().some((entry) => entry?.type === "custom" && entry?.customType === "johnness.compaction-session-rotation");
96452
- if (settings.transcriptBytes > 0 && transcriptBytes >= settings.transcriptBytes && __hasCompactedContext && !__alreadyRotated) {
96460
+ /* JOHNNESS_PATCH_CONTEXT_WINDOW_COMPACTION: the existing-compacted-lineage rotation follows the same single context-token trigger; the byte gate is gone (family 74). */
96461
+ const __cwcWindowTokens = localPressureGuardContextWindowTokens(params.session?.model);
96462
+ const __cwcThresholdTokens = localPressureGuardContextThreshold(settings, __cwcWindowTokens, params.config);
96463
+ const __cwcEstimatedTokens = estimateMessagesTokens(messages);
96464
+ if (__cwcEstimatedTokens >= __cwcThresholdTokens && __hasCompactedContext && !__alreadyRotated) {
96453
96465
  const __existingRotation = await rotateCompactedSessionLineage({ config: params.config, session: params.session, sessionManager: params.sessionManager, sessionFile: params.sessionFile, sessionKey: params.sessionKey, sessionId: params.sessionId, reason: "existing-compacted-lineage" });
96454
96466
  if (__existingRotation.rotated) return { refreshed: true, rotated: true, reasons: ["existing-compacted-lineage"], transcriptBytes: __existingRotation.transcriptBytes, messageCount: __existingRotation.messageCount, heapUsedBytes, sessionId: __existingRotation.sessionId, sessionFile: __existingRotation.sessionFile, oldSessionId: __existingRotation.oldSessionId, oldSessionFile: __existingRotation.oldSessionFile };
96455
96467
  }
96456
96468
  const reasons = [];
96457
- /* JOHNNESS_PATCH_APPEND_ONLY_PRESSURE_BASELINE: compare NEW growth since the last successful compaction (restart-safe baseline from the append-only JSONL), not the total never-shrinking file size (family 22). */
96469
+ /* JOHNNESS_PATCH_CONTEXT_WINDOW_COMPACTION: single trigger — estimated active-context tokens vs the context-window threshold (family 74). Legacy byte/message/heap heuristics no longer gate; the family 22 append-only baseline below is computed and recorded for observability only. */
96458
96470
  const __aopBaselineBytes = computeActivePressureBaselineBytes(params.sessionManager);
96459
96471
  const __aopEffectiveBytes = effectiveTranscriptPressureBytes(transcriptBytes, __aopBaselineBytes);
96460
- if (settings.transcriptBytes > 0 && __aopEffectiveBytes >= settings.transcriptBytes) reasons.push("transcriptBytes=" + __aopEffectiveBytes + "/" + settings.transcriptBytes + (__aopBaselineBytes > 0 ? " (new since baseline=" + __aopBaselineBytes + "; raw=" + transcriptBytes + ")" : ""));
96461
- if (settings.messageCount > 0 && messageCount >= settings.messageCount) reasons.push("messages=" + messageCount + "/" + settings.messageCount);
96462
- if (settings.heapUsedBytes > 0 && heapUsedBytes >= settings.heapUsedBytes) reasons.push("heapUsed=" + heapUsedBytes + "/" + settings.heapUsedBytes);
96463
- if (reasons.length === 0) return { refreshed: false, reason: "below threshold", transcriptBytes, messageCount, heapUsedBytes };
96472
+ if (__cwcEstimatedTokens >= __cwcThresholdTokens) reasons.push("contextTokens=" + __cwcEstimatedTokens + "/" + __cwcThresholdTokens);
96473
+ if (reasons.length === 0) return { refreshed: false, reason: "below threshold", contextTokens: __cwcEstimatedTokens, contextThreshold: __cwcThresholdTokens, baselineBytes: __aopBaselineBytes, effectiveTranscriptBytes: __aopEffectiveBytes, transcriptBytes, messageCount, heapUsedBytes };
96464
96474
  let last = 0;
96465
96475
  let lastFailed = false;
96466
96476
  let lastBytes = 0;
@@ -101878,13 +101888,14 @@ async function maybeCompactActiveRunForPressure(params) {
101878
101888
  } catch {}
101879
101889
  const heapUsedBytes = typeof process?.memoryUsage === "function" ? process.memoryUsage().heapUsed : 0;
101880
101890
  const reasons = [];
101881
- /* JOHNNESS_PATCH_APPEND_ONLY_PRESSURE_BASELINE: compare NEW growth since the last successful compaction (restart-safe baseline from the append-only JSONL), not the total never-shrinking file size (family 22). */
101891
+ /* JOHNNESS_PATCH_CONTEXT_WINDOW_COMPACTION: single trigger — estimated active-context tokens vs the context-window threshold (family 74). Legacy byte/message/heap heuristics no longer gate; the family 22 append-only baseline below is computed and recorded for observability only. */
101882
101892
  const __aopBaselineBytes = computeActivePressureBaselineBytes(params.sessionManager);
101883
101893
  const __aopEffectiveBytes = effectiveTranscriptPressureBytes(transcriptBytes, __aopBaselineBytes);
101884
- if (settings.transcriptBytes > 0 && __aopEffectiveBytes >= settings.transcriptBytes) reasons.push("transcriptBytes=" + __aopEffectiveBytes + "/" + settings.transcriptBytes + (__aopBaselineBytes > 0 ? " (new since baseline=" + __aopBaselineBytes + "; raw=" + transcriptBytes + ")" : ""));
101885
- if (settings.messageCount > 0 && messageCount >= settings.messageCount) reasons.push("messages=" + messageCount + "/" + settings.messageCount);
101886
- if (settings.heapUsedBytes > 0 && heapUsedBytes >= settings.heapUsedBytes) reasons.push("heapUsed=" + heapUsedBytes + "/" + settings.heapUsedBytes);
101887
- if (reasons.length === 0) return { refreshed: false, reason: "below threshold", transcriptBytes, messageCount, heapUsedBytes };
101894
+ const __cwcWindowTokens = localPressureGuardContextWindowTokens(params.session?.model);
101895
+ const __cwcThresholdTokens = localPressureGuardContextThreshold(settings, __cwcWindowTokens, params.config);
101896
+ const __cwcEstimatedTokens = estimateMessagesTokens(messages);
101897
+ if (__cwcEstimatedTokens >= __cwcThresholdTokens) reasons.push("contextTokens=" + __cwcEstimatedTokens + "/" + __cwcThresholdTokens);
101898
+ if (reasons.length === 0) return { refreshed: false, reason: "below threshold", contextTokens: __cwcEstimatedTokens, contextThreshold: __cwcThresholdTokens, baselineBytes: __aopBaselineBytes, effectiveTranscriptBytes: __aopEffectiveBytes, transcriptBytes, messageCount, heapUsedBytes };
101888
101899
  let last = 0;
101889
101900
  let lastFailed = false;
101890
101901
  let lastBytes = 0;
@@ -96120,9 +96120,7 @@ function buildEmbeddedExtensionFactories(params) {
96120
96120
 
96121
96121
  //#region johnness local active-session pressure guard
96122
96122
  const LOCAL_PRESSURE_GUARD_DEFAULTS = {
96123
- transcriptBytes: 1048576,
96124
- messageCount: 220,
96125
- heapUsedBytes: 3221225472,
96123
+ contextRatio: 0.7,
96126
96124
  minIntervalMs: 300000,
96127
96125
  minMessagesAfterCompaction: 24
96128
96126
  };
@@ -96133,14 +96131,24 @@ function localPressureGuardNumber(value, fallback) {
96133
96131
  function localPressureGuardSettings(cfg) {
96134
96132
  const raw = cfg?.agents?.defaults?.activeSessionPressure ?? cfg?.session?.activePressure ?? {};
96135
96133
  if (raw?.enabled === false) return null;
96134
+ /* JOHNNESS_PATCH_CONTEXT_WINDOW_COMPACTION: legacy transcriptBytes/messageCount/heapUsedBytes keys are accepted without error and ignored. Config surface: enabled, contextRatio, minIntervalMs, minMessagesAfterCompaction. */
96135
+ const contextRatio = localPressureGuardNumber(raw.contextRatio, LOCAL_PRESSURE_GUARD_DEFAULTS.contextRatio);
96136
96136
  return {
96137
- transcriptBytes: localPressureGuardNumber(raw.transcriptBytes ?? raw.maxTranscriptBytes, LOCAL_PRESSURE_GUARD_DEFAULTS.transcriptBytes),
96138
- messageCount: localPressureGuardNumber(raw.messageCount ?? raw.maxMessages, LOCAL_PRESSURE_GUARD_DEFAULTS.messageCount),
96139
- heapUsedBytes: localPressureGuardNumber(raw.heapUsedBytes ?? raw.maxHeapUsedBytes, LOCAL_PRESSURE_GUARD_DEFAULTS.heapUsedBytes),
96137
+ contextRatio: contextRatio > 0 ? contextRatio : LOCAL_PRESSURE_GUARD_DEFAULTS.contextRatio,
96140
96138
  minIntervalMs: localPressureGuardNumber(raw.minIntervalMs, LOCAL_PRESSURE_GUARD_DEFAULTS.minIntervalMs),
96141
96139
  minMessagesAfterCompaction: localPressureGuardNumber(raw.minMessagesAfterCompaction, LOCAL_PRESSURE_GUARD_DEFAULTS.minMessagesAfterCompaction)
96142
96140
  };
96143
96141
  }
96142
+ /* JOHNNESS_PATCH_CONTEXT_WINDOW_COMPACTION: single pressure trigger — estimated active-context tokens vs the active model's declared context window; the window falls back to DEFAULT_CONTEXT_TOKENS when undeclared. See docs/PATCHES.md family 74. */
96143
+ function localPressureGuardContextWindowTokens(model) {
96144
+ const declared = typeof model?.contextWindow === "number" && Number.isFinite(model.contextWindow) ? Math.floor(model.contextWindow) : 0;
96145
+ return declared > 0 ? declared : DEFAULT_CONTEXT_TOKENS;
96146
+ }
96147
+ function localPressureGuardContextThreshold(settings, contextWindowTokens, cfg) {
96148
+ const reserveTokensFloor = localPressureGuardNumber(cfg?.agents?.defaults?.compaction?.reserveTokensFloor, 2e4);
96149
+ const floor = Math.max(reserveTokensFloor, 50000);
96150
+ return Math.max(Math.ceil(settings.contextRatio * contextWindowTokens), floor);
96151
+ }
96144
96152
  /* JOHNNESS_PATCH_APPEND_ONLY_PRESSURE_BASELINE: derive a restart-safe byte baseline from the append-only JSONL so transcriptBytes pressure measures NEW growth since the last successful compaction rather than total (never-shrinking) file size. See docs/PATCHES.md family 22. Scans getEntries() newest->oldest for the newest SUCCESSFUL johnness.active-pressure-refresh marker (failed !== true) and returns its recorded post-compaction size (postCompactionBytes, else transcriptBytes). Returns 0 for legacy sessions with no such marker, so a first over-limit session still compacts once. */
96145
96153
  function computeActivePressureBaselineBytes(sessionManager) {
96146
96154
  try {
@@ -96298,12 +96306,12 @@ function compactionRotationNumber(value, fallback) {
96298
96306
  return Number.isFinite(parsed) && parsed >= 0 ? parsed : fallback;
96299
96307
  }
96300
96308
  function compactionRotationSettings(cfg) {
96301
- const pressure = localPressureGuardSettings(cfg) ?? { transcriptBytes: 1048576 };
96302
96309
  const raw = cfg?.agents?.defaults?.activeSessionPressure ?? cfg?.session?.activePressure ?? {};
96303
96310
  if (raw?.rotateAfterCompaction === false) return null;
96304
- const threshold = compactionRotationNumber(pressure?.transcriptBytes, 1048576);
96311
+ /* JOHNNESS_PATCH_CONTEXT_WINDOW_COMPACTION: the legacy transcriptBytes threshold no longer exists; the rotation size budget keeps its historical 1 MiB-derived default so default rotation behavior is unchanged. */
96312
+ const threshold = 1048576;
96305
96313
  const configuredMax = compactionRotationNumber(raw.rotationMaxBytes, 0);
96306
- const maxBytes = configuredMax > 0 ? configuredMax : threshold > 0 ? Math.max(131072, Math.min(524288, Math.floor(threshold * .45))) : 524288;
96314
+ const maxBytes = configuredMax > 0 ? configuredMax : Math.max(131072, Math.min(524288, Math.floor(threshold * .45)));
96307
96315
  return {
96308
96316
  maxBytes,
96309
96317
  maxMessages: Math.max(4, Math.floor(compactionRotationNumber(raw.rotationMaxMessages, 80))),
@@ -96439,18 +96447,20 @@ async function maybeRefreshActiveSessionForPressure(params) {
96439
96447
  const heapUsedBytes = typeof process?.memoryUsage === "function" ? process.memoryUsage().heapUsed : 0;
96440
96448
  const __hasCompactedContext = Array.isArray(params.session.messages) && params.session.messages.some((message) => message?.role === "compactionSummary");
96441
96449
  const __alreadyRotated = params.sessionManager.getEntries?.().some((entry) => entry?.type === "custom" && entry?.customType === "johnness.compaction-session-rotation");
96442
- if (settings.transcriptBytes > 0 && transcriptBytes >= settings.transcriptBytes && __hasCompactedContext && !__alreadyRotated) {
96450
+ /* JOHNNESS_PATCH_CONTEXT_WINDOW_COMPACTION: the existing-compacted-lineage rotation follows the same single context-token trigger; the byte gate is gone (family 74). */
96451
+ const __cwcWindowTokens = localPressureGuardContextWindowTokens(params.session?.model);
96452
+ const __cwcThresholdTokens = localPressureGuardContextThreshold(settings, __cwcWindowTokens, params.config);
96453
+ const __cwcEstimatedTokens = estimateMessagesTokens(messages);
96454
+ if (__cwcEstimatedTokens >= __cwcThresholdTokens && __hasCompactedContext && !__alreadyRotated) {
96443
96455
  const __existingRotation = await rotateCompactedSessionLineage({ config: params.config, session: params.session, sessionManager: params.sessionManager, sessionFile: params.sessionFile, sessionKey: params.sessionKey, sessionId: params.sessionId, reason: "existing-compacted-lineage" });
96444
96456
  if (__existingRotation.rotated) return { refreshed: true, rotated: true, reasons: ["existing-compacted-lineage"], transcriptBytes: __existingRotation.transcriptBytes, messageCount: __existingRotation.messageCount, heapUsedBytes, sessionId: __existingRotation.sessionId, sessionFile: __existingRotation.sessionFile, oldSessionId: __existingRotation.oldSessionId, oldSessionFile: __existingRotation.oldSessionFile };
96445
96457
  }
96446
96458
  const reasons = [];
96447
- /* JOHNNESS_PATCH_APPEND_ONLY_PRESSURE_BASELINE: compare NEW growth since the last successful compaction (restart-safe baseline from the append-only JSONL), not the total never-shrinking file size (family 22). */
96459
+ /* JOHNNESS_PATCH_CONTEXT_WINDOW_COMPACTION: single trigger — estimated active-context tokens vs the context-window threshold (family 74). Legacy byte/message/heap heuristics no longer gate; the family 22 append-only baseline below is computed and recorded for observability only. */
96448
96460
  const __aopBaselineBytes = computeActivePressureBaselineBytes(params.sessionManager);
96449
96461
  const __aopEffectiveBytes = effectiveTranscriptPressureBytes(transcriptBytes, __aopBaselineBytes);
96450
- if (settings.transcriptBytes > 0 && __aopEffectiveBytes >= settings.transcriptBytes) reasons.push("transcriptBytes=" + __aopEffectiveBytes + "/" + settings.transcriptBytes + (__aopBaselineBytes > 0 ? " (new since baseline=" + __aopBaselineBytes + "; raw=" + transcriptBytes + ")" : ""));
96451
- if (settings.messageCount > 0 && messageCount >= settings.messageCount) reasons.push("messages=" + messageCount + "/" + settings.messageCount);
96452
- if (settings.heapUsedBytes > 0 && heapUsedBytes >= settings.heapUsedBytes) reasons.push("heapUsed=" + heapUsedBytes + "/" + settings.heapUsedBytes);
96453
- if (reasons.length === 0) return { refreshed: false, reason: "below threshold", transcriptBytes, messageCount, heapUsedBytes };
96462
+ if (__cwcEstimatedTokens >= __cwcThresholdTokens) reasons.push("contextTokens=" + __cwcEstimatedTokens + "/" + __cwcThresholdTokens);
96463
+ if (reasons.length === 0) return { refreshed: false, reason: "below threshold", contextTokens: __cwcEstimatedTokens, contextThreshold: __cwcThresholdTokens, baselineBytes: __aopBaselineBytes, effectiveTranscriptBytes: __aopEffectiveBytes, transcriptBytes, messageCount, heapUsedBytes };
96454
96464
  let last = 0;
96455
96465
  let lastFailed = false;
96456
96466
  let lastBytes = 0;
@@ -101868,13 +101878,14 @@ async function maybeCompactActiveRunForPressure(params) {
101868
101878
  } catch {}
101869
101879
  const heapUsedBytes = typeof process?.memoryUsage === "function" ? process.memoryUsage().heapUsed : 0;
101870
101880
  const reasons = [];
101871
- /* JOHNNESS_PATCH_APPEND_ONLY_PRESSURE_BASELINE: compare NEW growth since the last successful compaction (restart-safe baseline from the append-only JSONL), not the total never-shrinking file size (family 22). */
101881
+ /* JOHNNESS_PATCH_CONTEXT_WINDOW_COMPACTION: single trigger — estimated active-context tokens vs the context-window threshold (family 74). Legacy byte/message/heap heuristics no longer gate; the family 22 append-only baseline below is computed and recorded for observability only. */
101872
101882
  const __aopBaselineBytes = computeActivePressureBaselineBytes(params.sessionManager);
101873
101883
  const __aopEffectiveBytes = effectiveTranscriptPressureBytes(transcriptBytes, __aopBaselineBytes);
101874
- if (settings.transcriptBytes > 0 && __aopEffectiveBytes >= settings.transcriptBytes) reasons.push("transcriptBytes=" + __aopEffectiveBytes + "/" + settings.transcriptBytes + (__aopBaselineBytes > 0 ? " (new since baseline=" + __aopBaselineBytes + "; raw=" + transcriptBytes + ")" : ""));
101875
- if (settings.messageCount > 0 && messageCount >= settings.messageCount) reasons.push("messages=" + messageCount + "/" + settings.messageCount);
101876
- if (settings.heapUsedBytes > 0 && heapUsedBytes >= settings.heapUsedBytes) reasons.push("heapUsed=" + heapUsedBytes + "/" + settings.heapUsedBytes);
101877
- if (reasons.length === 0) return { refreshed: false, reason: "below threshold", transcriptBytes, messageCount, heapUsedBytes };
101884
+ const __cwcWindowTokens = localPressureGuardContextWindowTokens(params.session?.model);
101885
+ const __cwcThresholdTokens = localPressureGuardContextThreshold(settings, __cwcWindowTokens, params.config);
101886
+ const __cwcEstimatedTokens = estimateMessagesTokens(messages);
101887
+ if (__cwcEstimatedTokens >= __cwcThresholdTokens) reasons.push("contextTokens=" + __cwcEstimatedTokens + "/" + __cwcThresholdTokens);
101888
+ if (reasons.length === 0) return { refreshed: false, reason: "below threshold", contextTokens: __cwcEstimatedTokens, contextThreshold: __cwcThresholdTokens, baselineBytes: __aopBaselineBytes, effectiveTranscriptBytes: __aopEffectiveBytes, transcriptBytes, messageCount, heapUsedBytes };
101878
101889
  let last = 0;
101879
101890
  let lastFailed = false;
101880
101891
  let lastBytes = 0;
@@ -74132,9 +74132,7 @@ function buildEmbeddedExtensionFactories(params) {
74132
74132
 
74133
74133
  //#region johnness local active-session pressure guard
74134
74134
  const LOCAL_PRESSURE_GUARD_DEFAULTS = {
74135
- transcriptBytes: 1048576,
74136
- messageCount: 220,
74137
- heapUsedBytes: 3221225472,
74135
+ contextRatio: 0.7,
74138
74136
  minIntervalMs: 300000,
74139
74137
  minMessagesAfterCompaction: 24
74140
74138
  };
@@ -74145,14 +74143,24 @@ function localPressureGuardNumber(value, fallback) {
74145
74143
  function localPressureGuardSettings(cfg) {
74146
74144
  const raw = cfg?.agents?.defaults?.activeSessionPressure ?? cfg?.session?.activePressure ?? {};
74147
74145
  if (raw?.enabled === false) return null;
74146
+ /* JOHNNESS_PATCH_CONTEXT_WINDOW_COMPACTION: legacy transcriptBytes/messageCount/heapUsedBytes keys are accepted without error and ignored. Config surface: enabled, contextRatio, minIntervalMs, minMessagesAfterCompaction. */
74147
+ const contextRatio = localPressureGuardNumber(raw.contextRatio, LOCAL_PRESSURE_GUARD_DEFAULTS.contextRatio);
74148
74148
  return {
74149
- transcriptBytes: localPressureGuardNumber(raw.transcriptBytes ?? raw.maxTranscriptBytes, LOCAL_PRESSURE_GUARD_DEFAULTS.transcriptBytes),
74150
- messageCount: localPressureGuardNumber(raw.messageCount ?? raw.maxMessages, LOCAL_PRESSURE_GUARD_DEFAULTS.messageCount),
74151
- heapUsedBytes: localPressureGuardNumber(raw.heapUsedBytes ?? raw.maxHeapUsedBytes, LOCAL_PRESSURE_GUARD_DEFAULTS.heapUsedBytes),
74149
+ contextRatio: contextRatio > 0 ? contextRatio : LOCAL_PRESSURE_GUARD_DEFAULTS.contextRatio,
74152
74150
  minIntervalMs: localPressureGuardNumber(raw.minIntervalMs, LOCAL_PRESSURE_GUARD_DEFAULTS.minIntervalMs),
74153
74151
  minMessagesAfterCompaction: localPressureGuardNumber(raw.minMessagesAfterCompaction, LOCAL_PRESSURE_GUARD_DEFAULTS.minMessagesAfterCompaction)
74154
74152
  };
74155
74153
  }
74154
+ /* JOHNNESS_PATCH_CONTEXT_WINDOW_COMPACTION: single pressure trigger — estimated active-context tokens vs the active model's declared context window; the window falls back to DEFAULT_CONTEXT_TOKENS when undeclared. See docs/PATCHES.md family 74. */
74155
+ function localPressureGuardContextWindowTokens(model) {
74156
+ const declared = typeof model?.contextWindow === "number" && Number.isFinite(model.contextWindow) ? Math.floor(model.contextWindow) : 0;
74157
+ return declared > 0 ? declared : DEFAULT_CONTEXT_TOKENS;
74158
+ }
74159
+ function localPressureGuardContextThreshold(settings, contextWindowTokens, cfg) {
74160
+ const reserveTokensFloor = localPressureGuardNumber(cfg?.agents?.defaults?.compaction?.reserveTokensFloor, 2e4);
74161
+ const floor = Math.max(reserveTokensFloor, 50000);
74162
+ return Math.max(Math.ceil(settings.contextRatio * contextWindowTokens), floor);
74163
+ }
74156
74164
  /* JOHNNESS_PATCH_APPEND_ONLY_PRESSURE_BASELINE: derive a restart-safe byte baseline from the append-only JSONL so transcriptBytes pressure measures NEW growth since the last successful compaction rather than total (never-shrinking) file size. See docs/PATCHES.md family 22. Scans getEntries() newest->oldest for the newest SUCCESSFUL johnness.active-pressure-refresh marker (failed !== true) and returns its recorded post-compaction size (postCompactionBytes, else transcriptBytes). Returns 0 for legacy sessions with no such marker, so a first over-limit session still compacts once. */
74157
74165
  function computeActivePressureBaselineBytes(sessionManager) {
74158
74166
  try {
@@ -74310,12 +74318,12 @@ function compactionRotationNumber(value, fallback) {
74310
74318
  return Number.isFinite(parsed) && parsed >= 0 ? parsed : fallback;
74311
74319
  }
74312
74320
  function compactionRotationSettings(cfg) {
74313
- const pressure = localPressureGuardSettings(cfg) ?? { transcriptBytes: 1048576 };
74314
74321
  const raw = cfg?.agents?.defaults?.activeSessionPressure ?? cfg?.session?.activePressure ?? {};
74315
74322
  if (raw?.rotateAfterCompaction === false) return null;
74316
- const threshold = compactionRotationNumber(pressure?.transcriptBytes, 1048576);
74323
+ /* JOHNNESS_PATCH_CONTEXT_WINDOW_COMPACTION: the legacy transcriptBytes threshold no longer exists; the rotation size budget keeps its historical 1 MiB-derived default so default rotation behavior is unchanged. */
74324
+ const threshold = 1048576;
74317
74325
  const configuredMax = compactionRotationNumber(raw.rotationMaxBytes, 0);
74318
- const maxBytes = configuredMax > 0 ? configuredMax : threshold > 0 ? Math.max(131072, Math.min(524288, Math.floor(threshold * .45))) : 524288;
74326
+ const maxBytes = configuredMax > 0 ? configuredMax : Math.max(131072, Math.min(524288, Math.floor(threshold * .45)));
74319
74327
  return {
74320
74328
  maxBytes,
74321
74329
  maxMessages: Math.max(4, Math.floor(compactionRotationNumber(raw.rotationMaxMessages, 80))),
@@ -74451,18 +74459,20 @@ async function maybeRefreshActiveSessionForPressure(params) {
74451
74459
  const heapUsedBytes = typeof process?.memoryUsage === "function" ? process.memoryUsage().heapUsed : 0;
74452
74460
  const __hasCompactedContext = Array.isArray(params.session.messages) && params.session.messages.some((message) => message?.role === "compactionSummary");
74453
74461
  const __alreadyRotated = params.sessionManager.getEntries?.().some((entry) => entry?.type === "custom" && entry?.customType === "johnness.compaction-session-rotation");
74454
- if (settings.transcriptBytes > 0 && transcriptBytes >= settings.transcriptBytes && __hasCompactedContext && !__alreadyRotated) {
74462
+ /* JOHNNESS_PATCH_CONTEXT_WINDOW_COMPACTION: the existing-compacted-lineage rotation follows the same single context-token trigger; the byte gate is gone (family 74). */
74463
+ const __cwcWindowTokens = localPressureGuardContextWindowTokens(params.session?.model);
74464
+ const __cwcThresholdTokens = localPressureGuardContextThreshold(settings, __cwcWindowTokens, params.config);
74465
+ const __cwcEstimatedTokens = estimateMessagesTokens(messages);
74466
+ if (__cwcEstimatedTokens >= __cwcThresholdTokens && __hasCompactedContext && !__alreadyRotated) {
74455
74467
  const __existingRotation = await rotateCompactedSessionLineage({ config: params.config, session: params.session, sessionManager: params.sessionManager, sessionFile: params.sessionFile, sessionKey: params.sessionKey, sessionId: params.sessionId, reason: "existing-compacted-lineage" });
74456
74468
  if (__existingRotation.rotated) return { refreshed: true, rotated: true, reasons: ["existing-compacted-lineage"], transcriptBytes: __existingRotation.transcriptBytes, messageCount: __existingRotation.messageCount, heapUsedBytes, sessionId: __existingRotation.sessionId, sessionFile: __existingRotation.sessionFile, oldSessionId: __existingRotation.oldSessionId, oldSessionFile: __existingRotation.oldSessionFile };
74457
74469
  }
74458
74470
  const reasons = [];
74459
- /* JOHNNESS_PATCH_APPEND_ONLY_PRESSURE_BASELINE: compare NEW growth since the last successful compaction (restart-safe baseline from the append-only JSONL), not the total never-shrinking file size (family 22). */
74471
+ /* JOHNNESS_PATCH_CONTEXT_WINDOW_COMPACTION: single trigger — estimated active-context tokens vs the context-window threshold (family 74). Legacy byte/message/heap heuristics no longer gate; the family 22 append-only baseline below is computed and recorded for observability only. */
74460
74472
  const __aopBaselineBytes = computeActivePressureBaselineBytes(params.sessionManager);
74461
74473
  const __aopEffectiveBytes = effectiveTranscriptPressureBytes(transcriptBytes, __aopBaselineBytes);
74462
- if (settings.transcriptBytes > 0 && __aopEffectiveBytes >= settings.transcriptBytes) reasons.push("transcriptBytes=" + __aopEffectiveBytes + "/" + settings.transcriptBytes + (__aopBaselineBytes > 0 ? " (new since baseline=" + __aopBaselineBytes + "; raw=" + transcriptBytes + ")" : ""));
74463
- if (settings.messageCount > 0 && messageCount >= settings.messageCount) reasons.push("messages=" + messageCount + "/" + settings.messageCount);
74464
- if (settings.heapUsedBytes > 0 && heapUsedBytes >= settings.heapUsedBytes) reasons.push("heapUsed=" + heapUsedBytes + "/" + settings.heapUsedBytes);
74465
- if (reasons.length === 0) return { refreshed: false, reason: "below threshold", transcriptBytes, messageCount, heapUsedBytes };
74474
+ if (__cwcEstimatedTokens >= __cwcThresholdTokens) reasons.push("contextTokens=" + __cwcEstimatedTokens + "/" + __cwcThresholdTokens);
74475
+ if (reasons.length === 0) return { refreshed: false, reason: "below threshold", contextTokens: __cwcEstimatedTokens, contextThreshold: __cwcThresholdTokens, baselineBytes: __aopBaselineBytes, effectiveTranscriptBytes: __aopEffectiveBytes, transcriptBytes, messageCount, heapUsedBytes };
74466
74476
  let last = 0;
74467
74477
  let lastFailed = false;
74468
74478
  let lastBytes = 0;
@@ -79883,13 +79893,14 @@ async function maybeCompactActiveRunForPressure(params) {
79883
79893
  } catch {}
79884
79894
  const heapUsedBytes = typeof process?.memoryUsage === "function" ? process.memoryUsage().heapUsed : 0;
79885
79895
  const reasons = [];
79886
- /* JOHNNESS_PATCH_APPEND_ONLY_PRESSURE_BASELINE: compare NEW growth since the last successful compaction (restart-safe baseline from the append-only JSONL), not the total never-shrinking file size (family 22). */
79896
+ /* JOHNNESS_PATCH_CONTEXT_WINDOW_COMPACTION: single trigger — estimated active-context tokens vs the context-window threshold (family 74). Legacy byte/message/heap heuristics no longer gate; the family 22 append-only baseline below is computed and recorded for observability only. */
79887
79897
  const __aopBaselineBytes = computeActivePressureBaselineBytes(params.sessionManager);
79888
79898
  const __aopEffectiveBytes = effectiveTranscriptPressureBytes(transcriptBytes, __aopBaselineBytes);
79889
- if (settings.transcriptBytes > 0 && __aopEffectiveBytes >= settings.transcriptBytes) reasons.push("transcriptBytes=" + __aopEffectiveBytes + "/" + settings.transcriptBytes + (__aopBaselineBytes > 0 ? " (new since baseline=" + __aopBaselineBytes + "; raw=" + transcriptBytes + ")" : ""));
79890
- if (settings.messageCount > 0 && messageCount >= settings.messageCount) reasons.push("messages=" + messageCount + "/" + settings.messageCount);
79891
- if (settings.heapUsedBytes > 0 && heapUsedBytes >= settings.heapUsedBytes) reasons.push("heapUsed=" + heapUsedBytes + "/" + settings.heapUsedBytes);
79892
- if (reasons.length === 0) return { refreshed: false, reason: "below threshold", transcriptBytes, messageCount, heapUsedBytes };
79899
+ const __cwcWindowTokens = localPressureGuardContextWindowTokens(params.session?.model);
79900
+ const __cwcThresholdTokens = localPressureGuardContextThreshold(settings, __cwcWindowTokens, params.config);
79901
+ const __cwcEstimatedTokens = estimateMessagesTokens(messages);
79902
+ if (__cwcEstimatedTokens >= __cwcThresholdTokens) reasons.push("contextTokens=" + __cwcEstimatedTokens + "/" + __cwcThresholdTokens);
79903
+ if (reasons.length === 0) return { refreshed: false, reason: "below threshold", contextTokens: __cwcEstimatedTokens, contextThreshold: __cwcThresholdTokens, baselineBytes: __aopBaselineBytes, effectiveTranscriptBytes: __aopEffectiveBytes, transcriptBytes, messageCount, heapUsedBytes };
79893
79904
  let last = 0;
79894
79905
  let lastFailed = false;
79895
79906
  let lastBytes = 0;
package/docs/PATCHES.md CHANGED
@@ -17,7 +17,7 @@ they predate ~2026-06-06).
17
17
 
18
18
  ## Index
19
19
 
20
- Family 01 is retired. Families 02 through 41, 43 through 72, including the 65.1 amendment, are live on `main`; the `latest` and `extended` release lines converged at 2026.9.24. Family 42 is omitted; native Codex OAuth ownership is Family 58.
20
+ Family 01 is retired. Families 02 through 41, 43 through 74, including the 65.1 amendment, are live on `main`; the `latest` and `extended` release lines converged at 2026.9.24. Family 42 is omitted; native Codex OAuth ownership is Family 58.
21
21
 
22
22
  1. **01, announce queue TTL, retired:** Removed the five-minute age filter that could discard delayed sub-agent completion announcements. [Details](#01--announce-ttl-retired-2026-07-30)
23
23
  2. **02, failover rotation:** Returns model selection to the highest-priority profile after its cooldown ends. [Details](#02--failover-rotation-undocumented)
@@ -97,7 +97,9 @@ Family 01 is retired. Families 02 through 41, 43 through 72, including the 65.1
97
97
 
98
98
  71. **71, native-first Jiti standard on latest:** The Family 68 native-first loader option ships on every artifact, the `latest` and `extended` lines converge, and a packed-artifact gate proves the tarball carries it. [Details](#family-71-native-first-jiti-standard-on-latest)
99
99
 
100
- 72. **72, bundled generic skills:** Eleven workspace skills (code operations, research, todos, dashboard, planning, content, outreach, logs, and more) ship as generic bundled skills that load by description match, with no owner names or personal paths. [Details](#family-72-bundled-generic-skills)
100
+ 72. **72, bundled generic skills:** Eight workspace skills (code operations, research, todos, planning, logs, and more) ship as generic bundled skills that load by description match, with no owner names or personal paths. [Details](#family-72-bundled-generic-skills)
101
+ 73. **73, prune bundled skills:** `content-story`, `dashboard` and `cold-outreach` are removed from the Family 72 bundled set on request after `2026.9.25`; the remaining eight ship unchanged. [Details](#family-73-prune-bundled-skills)
102
+ 74. **74, context-window compaction:** The local active-session pressure guard becomes single-trigger and model-aware: estimated active-context tokens against 70% of the active model's declared context window, floored at `max(compaction.reserveTokensFloor, 50000)`; the 1 MB byte, 220-message and 3 GiB heap triggers are removed. [Details](#family-74-context-window-compaction)
101
103
 
102
104
  ---
103
105
 
@@ -728,6 +730,7 @@ already-compacting / compaction-disabled / cooldown are no-ops.
728
730
  ---
729
731
 
730
732
  ## 22 - append-only-pressure-baseline (2026-07-29)
733
+ **Superseded by Family 74:** the append-only baseline is still computed and recorded on the `active-pressure-refresh` marker (see below), but nothing gates on it any more; the pressure trigger is now estimated context tokens against the active model's declared context window.
731
734
  **What:** The active-pressure `transcriptBytes` trigger now measures **NEW transcript
732
735
  growth since the last successful compaction** instead of the total, never-shrinking
733
736
  append-only file size. This removes the false-retrigger loop where a session that has
@@ -2627,9 +2630,9 @@ Roll back with `npm i -g johns-harness@2026.9.23` (no native-first Jiti) or `npm
2627
2630
 
2628
2631
  **Release:** `johns-harness@2026.9.25`. Published as `latest` and the `extended` dist-tag moves to it (the two lines converged at 2026.9.24).
2629
2632
 
2630
- Eleven skills from the author's personal workspace ship as bundled skills in `skills/<name>/SKILL.md`, made fully generic first: no owner names, no personal paths, repos, accounts, or products. Every one loads by description match like the other bundled skills (none is `always`-on, unlike the Family 70 orchestrator), a workspace or managed copy overrides it, `skills.entries.<name>.enabled: false` turns it off, and an `allowBundled` allowlist that omits it is respected.
2633
+ Eight skills from the author's personal workspace ship as bundled skills in `skills/<name>/SKILL.md`, made fully generic first: no owner names, no personal paths, repos, accounts, or products. Every one loads by description match like the other bundled skills (none is `always`-on, unlike the Family 70 orchestrator), a workspace or managed copy overrides it, `skills.entries.<name>.enabled: false` turns it off, and an `allowBundled` allowlist that omits it is respected. (Superseded by Family 73: from `2026.9.26` `dashboard`, `content-story` and `cold-outreach` are removed from the bundle.)
2631
2634
 
2632
- Shipped skills: `code-ops` (learnings protocol, config tracing, publishing safety), `web-research` (never give up deep research method), `plan-archival` (move finished plans to plans/archive and memory/completed-plans), `todos` (workspace memory/todos protocol), `dashboard` (command center updated after every action), `arch-viz` (sub-agent generated architecture visualization), `content-story` (Story, Concept, Drop framework), `cold-outreach` (Four Pillars cold DM framework), `gcp-logs` (Cloud Run log debugging via gcloud, placeholder service names), `activity-logging` (log outcomes, not tasks, plus the ACTION_LOG), `outlook` (outlook tool quick reference).
2635
+ Shipped skills: `code-ops` (learnings protocol, config tracing, publishing safety), `web-research` (never give up deep research method), `plan-archival` (move finished plans to plans/archive and memory/completed-plans), `todos` (workspace memory/todos protocol), `arch-viz` (sub-agent generated architecture visualization), `gcp-logs` (Cloud Run log debugging via gcloud, placeholder service names), `activity-logging` (log outcomes, not tasks, plus the ACTION_LOG), `outlook` (outlook tool quick reference).
2633
2636
 
2634
2637
  Decision table:
2635
2638
 
@@ -2639,10 +2642,10 @@ Decision table:
2639
2642
  | web-research | ship, converted | generic method, em dashes stripped |
2640
2643
  | plan-archival | ship, converted | personal paths replaced with workspace-relative |
2641
2644
  | todos | ship, converted | owner name and paths replaced |
2642
- | dashboard | ship, converted | names, projects and paths scrubbed |
2645
+ | dashboard | dropped | removed on request after 2026.9.25 |
2643
2646
  | arch-viz | ship, converted | personal paths and projects scrubbed |
2644
- | content-story | ship, converted | frontmatter added, em dashes stripped |
2645
- | cold-outreach | ship, converted | frontmatter added, product names scrubbed |
2647
+ | content-story | dropped | removed on request after 2026.9.25 |
2648
+ | cold-outreach | dropped | removed on request after 2026.9.25 |
2646
2649
  | gcp-logs | ship, converted | baked-in service names replaced with placeholders |
2647
2650
  | activity-logging | ship, converted | log names and paths made workspace-relative |
2648
2651
  | outlook | ship, converted | account domain scrubbed |
@@ -2655,6 +2658,30 @@ Decision table:
2655
2658
  | hyperframes, hyperframes-cli, hyperframes-media, website-to-hyperframes, impeccable | exclude | third-party vendored skills |
2656
2659
  | orchestrator | already shipped | Family 70 |
2657
2660
 
2658
- Verifier checks 72.1 through 72.5 cover presence with frontmatter, the generic wording contract (no owner, product or personal names and no `always` flag), no em dashes, the RULES.md skill table, and the packed artifact (the Family 71 gate now also proves all eleven skills are inside the tarball). `tests/bundled-skills.test.mjs` proves every skills chunk loads all eleven from an empty workspace, a workspace copy overrides, `enabled: false` disables, and the `allowBundled` allowlist still wins. `RULES.md` section 5 lists every bundled skill.
2661
+ Verifier checks 72.1 through 72.5 cover presence with frontmatter, the generic wording contract (no owner, product or personal names and no `always` flag), no em dashes, the RULES.md skill table, and the packed artifact (the Family 71 gate now also proves all eight skills are inside the tarball). `tests/bundled-skills.test.mjs` proves every skills chunk loads all eight from an empty workspace, a workspace copy overrides, `enabled: false` disables, and the `allowBundled` allowlist still wins. `RULES.md` section 5 lists every bundled skill.
2659
2662
 
2660
2663
  Roll back with `npm i -g johns-harness@2026.9.24`.
2664
+
2665
+ ## Family 73: prune bundled skills
2666
+
2667
+ **Release:** `johns-harness@2026.9.26`. Published as `latest` and the `extended` dist-tag moves to it.
2668
+
2669
+ Three of the Family 72 bundled skills are removed from the bundled set on request after `2026.9.25`: `content-story`, `dashboard` and `cold-outreach`. The remaining eight (`activity-logging`, `arch-viz`, `code-ops`, `gcp-logs`, `outlook`, `plan-archival`, `todos`, `web-research`) are unchanged, and a workspace or managed copy of a removed skill still overrides and loads as it always did. Nothing else differs from `2026.9.25`.
2670
+
2671
+ The counters move with the set: the verifier emits one check per remaining skill through the same 72.1 loop (455 checks in total), `tests/bundled-skills.test.mjs` asserts exactly eight bundled skills, `scripts/check-packed-artifact.mjs` fails closed unless a tarball carries all eight with frontmatter, and `README.md` and the pinned copy in `patches/59-migrate-cli.patch` carry the new counts together so check 59c stays green. `RULES.md` section 5 drops the three rows.
2672
+
2673
+ Roll back with `npm i -g johns-harness@2026.9.25`.
2674
+
2675
+ ## Family 74: context-window compaction
2676
+
2677
+ **Release:** `johns-harness@2026.9.27`. Published as `latest` and the `extended` dist-tag moves to it.
2678
+
2679
+ A live gateway session compacted at `messages=221/220` while its transcript held only 528 KB of a 1,048,576-byte budget. The guard was firing on a fixed message count, which is the wrong unit: 221 small messages can sit comfortably inside a large context window, and a few large tool outputs can overflow a small one. Family 74 makes the local active-session pressure guard single-trigger and model-aware.
2680
+
2681
+ The only trigger is estimated active-context tokens (`estimateMessagesTokens`, the estimator the compaction path already uses) against `max(ceil(contextRatio x activeModel.contextWindow), max(compaction.reserveTokensFloor, 50000))`, with `contextRatio` defaulting to 0.7. A model that declares no `contextWindow` falls back to the runtime default. Both the startup rotation gate and the in-loop refresh push only `contextTokens=<n>/<threshold>`.
2682
+
2683
+ The `transcriptBytes` (1 MB), `messageCount` (220) and `heapUsedBytes` (3 GiB) triggers are removed by design; the legacy config keys are accepted without error and ignored. The 5-minute cooldown, the 24-message post-compaction floor and the failed-attempt growth gate stay as anti-thrash throttles, and the emergency compaction path (Family 13) is untouched, so a genuine overflow is still caught. The Family 22 append-only baseline is still computed and recorded on the `active-pressure-refresh` marker for observability, but nothing gates on it. The config surface is `enabled`, `contextRatio`, `minIntervalMs`, `minMessagesAfterCompaction`.
2684
+
2685
+ Verifier checks 74.1 through 74.4 prove the marker, the absence of the byte/message/heap triggers, the threshold math and the config parsing in all four pressure bundles; `scripts/patch-context-window-compaction.py` applies the same change to any install root (both naming eras), is idempotent with `--check`, and fails closed on anchor drift. `patches/provider-catalog/changes.json` pins the four pressure bundles by hash, so `scripts/patch-provider-catalog.py` maps the Family 74 rewrite back to its pre-image in `family67_surface`, exactly as it already does for Family 68.
2686
+
2687
+ Roll back with `npm i -g johns-harness@2026.9.26`.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "johns-harness",
3
- "version": "2026.9.25",
3
+ "version": "2026.9.27",
4
4
  "description": "John's Harness: a production agent harness that runs autonomous coding agents as one cooperative swarm.",
5
5
  "keywords": [
6
6
  "ai-agents",
@@ -50,9 +50,6 @@ export const F72_SKILLS = [
50
50
  "activity-logging",
51
51
  "arch-viz",
52
52
  "code-ops",
53
- "cold-outreach",
54
- "content-story",
55
- "dashboard",
56
53
  "gcp-logs",
57
54
  "outlook",
58
55
  "plan-archival",
@@ -1,99 +0,0 @@
1
- ---
2
- name: cold-outreach
3
- description: Write cold DMs that get responses using the Four Pillars framework. Use when drafting outreach messages, direct messages, or connection requests for the user.
4
- ---
5
-
6
- # Cold Outreach / DM Skill
7
-
8
- Write cold DMs that get responses. Uses the Four Pillars framework.
9
-
10
- ## Required Reading
11
-
12
- Before ANY outreach, read the full playbook if one exists:
13
- - `memory/outreach-playbook.md` in the workspace
14
-
15
- ## The Four Pillars
16
-
17
- Every message must hit all 4:
18
-
19
- 1. **PERSONAL**: their thesis/worldview (what they REALLY believe)
20
- 2. **RELEVANT**: their current mission (what they're building NOW)
21
- 3. **TIMELY**: recent content (what's top of mind TODAY)
22
- 4. **POWERFUL**: peer energy (you're a collaborator, not a fan)
23
-
24
- ## The DM Flow
25
-
26
- | Step | What to Say | Goal |
27
- |------|-------------|------|
28
- | 1. Opener | React to THEIR thing + ask question | Start conversation |
29
- | 2. Listen | Let them share their pain | Understand their words |
30
- | 3. "Same" | "yeah same, I built something" | Create curiosity |
31
- | 4. Wait | They ask "what is it?" | Make them pull |
32
- | 5. Share | Now give them the product | Favor, not pitch |
33
-
34
- ## Opener Formula
35
-
36
- ```
37
- "[reaction to specific content]. [establish same problem]. [ask what they did about it]"
38
- ```
39
-
40
- **Example:**
41
- > "yo that piece on agents forgetting everything hit hard. been banging my head on the same problem. curious what you ended up doing about it"
42
-
43
- ## Anti-Patterns (NEVER)
44
-
45
- - ❌ Product name in opener
46
- - ❌ Install commands
47
- - ❌ "Your audience would love..."
48
- - ❌ Flattery ("love your content!")
49
- - ❌ Fan energy ("would you check out...")
50
- - ❌ Offering before they ask
51
-
52
- ## Good vs Bad
53
-
54
- **Bad (ad energy):**
55
- > "Hey [name] - been watching your agent coverage. I built [our product], a plugin that gives agents persistent memory. One-liner install. Think your audience would love it."
56
-
57
- **Good (peer energy):**
58
- > "yo that piece on agents forgetting everything hit hard. been banging my head on the same problem. curious what you ended up doing about it"
59
-
60
- ## Research Requirements
61
-
62
- Before writing ANY outreach:
63
- 1. Watch 2-3 recent videos: find their THESIS
64
- 2. Read social presence: what identity are they building?
65
- 3. Find ONE specific thing to react to
66
- 4. Know what they'll likely respond (from research)
67
- 5. Plan your step 3 response
68
-
69
- ## Output Format
70
-
71
- For each creator, produce:
72
-
73
- ```markdown
74
- ## [Creator Name]
75
-
76
- ### Research
77
- - **Thesis:** [what they really believe]
78
- - **Current mission:** [what they're building now]
79
- - **Recent content:** [specific thing to reference]
80
- - **Their solution:** [what they're currently doing about the problem]
81
-
82
- ### Opener
83
- > [the DM]
84
-
85
- ### Expected Response
86
- > [what they'll likely say]
87
-
88
- ### Step 3 Response
89
- > [our "same, I built something" response]
90
-
91
- ### Step 5 Share
92
- > [product share if they ask]
93
- ```
94
-
95
- ## Key Insight
96
-
97
- **Make them ask. Never offer first.**
98
-
99
- The goal is to create curiosity so strong that THEY pull the product out of you. Then sharing feels like a favor, not a pitch.
@@ -1,77 +0,0 @@
1
- ---
2
- name: content-story
3
- description: Generate content ideas using the Story, Concept, Drop framework. Use when brainstorming short-form video content, social posts, or creative concepts that need a hook and a payoff.
4
- ---
5
-
6
- # Content Story Skill
7
-
8
- Generate content ideas using the Story → Concept → Drop framework.
9
-
10
- ## The Framework
11
-
12
- Every piece of content needs three things:
13
-
14
- ### 1. The Story
15
- The journey. Beginning → destination. A problem that's relatable and a resolution that's satisfying.
16
- - The problem doesn't need to be big
17
- - The resolution doesn't need to be inspiring
18
- - But there MUST be a journey: something changes from start to finish
19
-
20
- ### 2. The Concept
21
- The viral component. What's on screen that makes someone WATCH?
22
- - A beautiful person
23
- - Something unexpected just happened
24
- - You need to see what comes next
25
- - A visual that stops the scroll
26
- - Curiosity gap: "wait, what?"
27
-
28
- ### 3. The Drop
29
- The climax. The moment the video makes sense. The payoff.
30
- - This is what people REWATCH for
31
- - Rewatches = algorithm fuel
32
- - The drop should feel earned: the story builds to it
33
- - Can be visual (reveal), emotional (surprise), or both
34
-
35
- ## Rules
36
-
37
- - **Would YOU watch it?** If no, don't post it
38
- - **15-30 seconds max** for reels: tight, no fat
39
- - **Music matters**: the right song makes or breaks the drop
40
- - **Film 10x more than you post**: capture everything, curate ruthlessly
41
- - **No talking heads, no "5 tips", no corporate energy**
42
- - **Raw + cinematic > polished + corporate**
43
- - **Not looking at the lens > looking at the lens**: candid energy even when staged
44
- - **Daily posting beats perfect posting**: consistency wins
45
-
46
- ## How to Use
47
-
48
- When brainstorming content, fill in:
49
-
50
- ```
51
- STORY: [the journey: what changes?]
52
- CONCEPT: [the hook: why do they watch?]
53
- DROP: [the payoff: what's the climax?]
54
- ```
55
-
56
- Then figure out:
57
- - What do you need to film?
58
- - What song fits?
59
- - What's the edit style? (slow-mo, transition, split screen, etc.)
60
-
61
- ## Content Types That Hit
62
-
63
- - **Main character POV**: walking, arriving, living. Viewer projects themselves into it
64
- - **Transitions**: before/after, location swap, outfit change, time shift
65
- - **Silent storytelling**: music + visuals + text overlay. Works sound on AND off
66
- - **Candid energy**: looks unplanned but isn't. Natural > posed
67
- - **Reveals**: camera pulls back or cuts to show something unexpected
68
- - **Contrast**: old life vs new life, morning vs night, alone vs crowd
69
-
70
- ## The Test
71
-
72
- Before posting, ask:
73
- 1. Does it have all three? (Story, Concept, Drop)
74
- 2. Would I stop scrolling for this?
75
- 3. Would I rewatch it?
76
-
77
- If yes to all three → post it.
@@ -1,129 +0,0 @@
1
- ---
2
- name: dashboard
3
- description: Maintain the user's command center dashboard in memory/dashboard.md. Update after any action (email sent, reply received, task completed, status change). Use when tracking pending items, project status, or giving the user a view of what's happening. The dashboard is the user's starting point; keep it current and useful.
4
- ---
5
-
6
- # The Dashboard
7
-
8
- The user's command center. Updated constantly. Shows what matters RIGHT NOW.
9
-
10
- ## Location
11
-
12
- - **File:** `memory/dashboard.md` in the workspace
13
- - If the workspace is a git repo, commit and push after every update so the user can view it from anywhere.
14
-
15
- ## Philosophy
16
-
17
- **Dynamic, not rigid.** Include what's relevant today. Sections appear/disappear based on what matters.
18
-
19
- **Curated, not comprehensive.** This is not a todo list dump. It is the user's "here's what you need to know right now."
20
-
21
- **Work AND life.** Personal stuff belongs here too if it is on the user's mind.
22
-
23
- ## Sections (Use What's Relevant)
24
-
25
- ```markdown
26
- # The Dashboard
27
- **Updated:** [timestamp in the user's timezone]
28
-
29
- ## Right Now
30
- [Most urgent/relevant thing: meeting in 30 min, decision needed, blocker resolved]
31
-
32
- ## Waiting On
33
- [People who owe the user something: name, what, how long]
34
-
35
- ## Today
36
- [What's on deck: meetings, tasks, deadlines]
37
-
38
- ## Hot Projects
39
- [Active projects with one-liner status + next action]
40
-
41
- ## Notes
42
- [Context, reminders, thoughts worth capturing]
43
- ```
44
-
45
- Not every section every day. Just what matters.
46
-
47
- ## When to Update
48
-
49
- | Trigger | Action |
50
- |---------|--------|
51
- | Email sent | Add to "Waiting On" with timestamp |
52
- | Reply received | Update or remove from "Waiting On" |
53
- | Task completed | Remove from "Today" or note completion |
54
- | Meeting coming up | Add to "Right Now" if within 1 hour |
55
- | Status change | Update "Hot Projects" |
56
- | New blocker | Add to "Right Now" or "Waiting On" |
57
- | Something resolved | Remove or celebrate in "Notes" |
58
-
59
- ## Prioritization (What Goes in "Right Now")
60
-
61
- 1. **Time-sensitive**: meeting in < 1 hour, deadline today
62
- 2. **Blocking**: something the user needs to unblock others
63
- 3. **Waiting too long**: pending response > 3 days
64
- 4. **Big news**: reply that changes things, win worth noting
65
-
66
- ## Format Rules
67
-
68
- - **Timestamps:** always in the user's timezone, 12-hour format (e.g., "1:45 PM")
69
- - **People:** name + company/context (e.g., "Alex (vendor)")
70
- - **Projects:** link to the project folder if one exists
71
- - **Keep it scannable:** tables for lists, bullets for notes
72
-
73
- ## After Every Update
74
-
75
- ```bash
76
- cd <workspace>
77
- git add -A && git commit -m "Update dashboard" && git push
78
- ```
79
-
80
- (If the workspace is not a git repo, skip the push.)
81
-
82
- ## Automated Monitoring
83
-
84
- A scheduled job can check the user's inbox, compare against "Waiting On" and "Hot Projects", message the user if anything relevant, and stay silent if nothing important. Manual updates still happen after interactions.
85
-
86
- ## Calendar Integration
87
-
88
- Pull from the calendar tool when relevant. Add meetings to "Today" with times in the user's timezone.
89
-
90
- ## Examples
91
-
92
- **Busy day with meetings:**
93
- ```markdown
94
- ## Right Now
95
- 📅 Team meeting in 25 min (11:00 AM)
96
-
97
- ## Today
98
- - 11:00 AM - Team meeting
99
- - 12:00 PM - Vendor call
100
- - 2:00 PM - Project check-in
101
-
102
- ## Waiting On
103
- - Alex (vendor) - server config fix (sent 1:42 PM)
104
- ```
105
-
106
- **Quiet day, project focus:**
107
- ```markdown
108
- ## Right Now
109
- Nothing urgent. Good day to ship.
110
-
111
- ## Hot Projects
112
- - **Project A** - building the configurator (from this morning's meeting)
113
- - **Project B** - blocked on a vendor fix
114
-
115
- ## Notes
116
- The user should have their password synced by end of day.
117
- ```
118
-
119
- **Personal stuff mixed in:**
120
- ```markdown
121
- ## Today
122
- - 10:00 AM - Dentist
123
- - 2:00 PM - Demo with a client
124
- - Pick up dry cleaning
125
-
126
- ## Waiting On
127
- - Alex (vendor) - server fix
128
- - Online order - package arriving today
129
- ```