agy-context-saver 1.2.1 → 1.3.2

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
@@ -5,6 +5,37 @@ All notable changes to `Agy-Context-Saver` will be documented in this file.
5
5
  The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
6
6
  and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
7
7
 
8
+ ## [1.3.2] - 2026-10-07
9
+
10
+ ### Governance & Command Semantics (LH-11 Hardening)
11
+ - **Opt-In Fail-Fast Semantics**: Reverted forced `-x -q` parameter injection on bare test runner invocations to preserve native command semantics. Fail-fast (`-x`, `--maxfail=1`) remains strictly opt-in, preventing the "whack-a-mole" repair loop and preserving complete failure blast radius visibility across multi-module test suites.
12
+ - **Architectural Separation**: Replaced forced command mutation with the **Coordinated Watchdog Protocol** and **Mandatory Pre-Yield Status Cards** in `rules/AGENTS.md`, ensuring visibility during multi-minute full-repository sweeps without distorting test results.
13
+ - **Loophole Ledger Synchronization**: Updated `LEDGER.md` (LH-11) documenting the trade-offs of fail-fast injection vs native command semantics.
14
+
15
+ ---
16
+
17
+ ## [1.3.1] - 2026-10-07
18
+
19
+ ### Governance & Asynchronous Execution Observability (LH-11)
20
+ - **Fail-Fast Auto-Injection on Bare Test Sweeps**: `execution-guard-hook.mjs` transparently normalizes bare `pytest` and `python -m pytest` invocations to include `-x -q` (fail-fast, quiet mode). In large codebases (e.g. 900+ tests), this prevents multi-minute unconstrained sweeps by stopping execution immediately at the first failure.
21
+ - **Coordinated Watchdog Protocol Codification**: Updated `rules/AGENTS.md` to explicitly train agents on the Coordinated Watchdog Protocol: when launching heavy background operations ($> 15\text{s}$), agents must schedule a coordinated watchdog timer (`schedule(DurationSeconds=45, ...)`) to prevent silent yielding into unmonitored execution black holes.
22
+ - **Mandatory Pre-Yield Status Cards**: Codified in `rules/AGENTS.md` that agents launching background tasks must output a structured markdown status card (command, scope, estimated duration, watchdog tier) before yielding their turn, eliminating user uncertainty during prolonged runs.
23
+ - **Loophole Ledger LH-11 Addition**: Formally documented LH-11 (Silent Background Deadlock & Bare Sweep Exhaustion) from Incident `07b0f8d8` in `LEDGER.md`.
24
+
25
+ ---
26
+
27
+ ## [1.3.0] - 2026-10-07
28
+
29
+ ### Governance & Loophole Elimination
30
+ - **Proportional Backoff Engine**: Eliminated the "Pendulum Freeze Trap" (where total bans on task polling caused hung/deadlocked background commands to run unmonitored forever) by implementing an exponential backoff curve ($0\text{s} \to 30\text{s} \to 75\text{s} \to 188\text{s} \to 469\text{s} \to 600\text{s}$). Tasks can be safely diagnosed when silent without enabling busy-waiting loops.
31
+ - **Watchdog Timer Coordination**: Coordinated `schedule` watchdog timers with the active proportional backoff window. Timers shorter than the backoff cooldown are rejected with detailed feedback, while legitimate watchdog timers ($\ge 30\text{s}$) are permitted and auto-cancel via native Reactive Wakeup.
32
+ - **Shell Wrapper Unwrapping (LH-01-B)**: Added recursive shell wrapper unwrapping (`cmd /c`, `powershell -Command`, `pwsh -c`, `bash -c`) inside `run_command` so wrapped commands are transparently rewritten to RTK equivalents instead of bypassing token optimization.
33
+ - **Internal State Shell Guard (LH-02-B)**: Added strict shell command guards blocking direct shell access (`cat`, `Get-Content`, `type`) to internal Antigravity execution state (`.system_generated/`), routing transcript queries to MCP tools.
34
+ - **Benchmark Shell Mutation Protection (LH-09-B)**: Extended benchmark and evaluator protections to shell redirect and removal operations (`>`, `rm`, `del`, `Remove-Item`), preventing benchmark tampering via the terminal.
35
+ - **Single Source of Truth Ledger**: Authored comprehensive `LEDGER.md` tracking all 10 historical loopholes (LH-01 to LH-10), remnant escape routes, backoff mathematical formulas, and architectural invariants.
36
+
37
+ ---
38
+
8
39
  ## [1.2.1] - 2026-10-07
9
40
 
10
41
  ### Documentation & Developer Experience
package/mcp/index.js CHANGED
@@ -98,7 +98,7 @@ const __dirname = path.dirname(fileURLToPath(import.meta.url));
98
98
  const rulesPath = path.resolve(__dirname, "../rules/AGENTS.md");
99
99
 
100
100
  const SERVER_NAME = "agy-context-saver";
101
- const SERVER_VERSION = "1.2.1";
101
+ const SERVER_VERSION = "1.3.2";
102
102
 
103
103
  // --- Tools Specification ---
104
104
  const TOOLS = [
package/package.json CHANGED
@@ -1,42 +1,42 @@
1
- {
2
- "name": "agy-context-saver",
3
- "version": "1.2.1",
4
- "description": "Universal Model Context Protocol (MCP) server & lifecycle governor purpose-built for Google Antigravity",
5
- "main": "mcp/index.js",
6
- "bin": {
7
- "agy-context-saver": "mcp/index.js"
8
- },
9
- "type": "module",
10
- "scripts": {
11
- "start": "node mcp/index.js",
12
- "setup": "node scripts/install-register.mjs",
13
- "uninstall": "node scripts/install-register.mjs --uninstall",
14
- "status": "node scripts/install-register.mjs --status",
15
- "test": "node tests/test-hook.mjs && node tests/test-mcp.mjs && node tests/test-e2e-probes.mjs && node tests/test-performance-probes.mjs && node tests/test-installed-verification.mjs && node tests/test-lifecycle-rollback.mjs"
16
- },
17
- "keywords": [
18
- "antigravity",
19
- "mcp",
20
- "model-context-protocol",
21
- "gemini",
22
- "context-window",
23
- "lifecycle-hooks",
24
- "agentic-coding"
25
- ],
26
- "files": [
27
- "mcp",
28
- "scripts",
29
- "rules",
30
- "skills",
31
- "hooks.json",
32
- "plugin.json",
33
- "mcp_config.json",
34
- "install.ps1",
35
- "install.sh",
36
- "README.md",
37
- "CHANGELOG.md",
38
- "LICENSE"
39
- ],
40
- "author": "paragon-ux",
41
- "license": "MIT"
42
- }
1
+ {
2
+ "name": "agy-context-saver",
3
+ "version": "1.3.2",
4
+ "description": "Universal Model Context Protocol (MCP) server & lifecycle governor purpose-built for Google Antigravity",
5
+ "main": "mcp/index.js",
6
+ "bin": {
7
+ "agy-context-saver": "mcp/index.js"
8
+ },
9
+ "type": "module",
10
+ "scripts": {
11
+ "start": "node mcp/index.js",
12
+ "setup": "node scripts/install-register.mjs",
13
+ "uninstall": "node scripts/install-register.mjs --uninstall",
14
+ "status": "node scripts/install-register.mjs --status",
15
+ "test": "node tests/test-hook.mjs && node tests/test-mcp.mjs && node tests/test-e2e-probes.mjs && node tests/test-performance-probes.mjs && node tests/test-installed-verification.mjs && node tests/test-lifecycle-rollback.mjs"
16
+ },
17
+ "keywords": [
18
+ "antigravity",
19
+ "mcp",
20
+ "model-context-protocol",
21
+ "gemini",
22
+ "context-window",
23
+ "lifecycle-hooks",
24
+ "agentic-coding"
25
+ ],
26
+ "files": [
27
+ "mcp",
28
+ "scripts",
29
+ "rules",
30
+ "skills",
31
+ "hooks.json",
32
+ "plugin.json",
33
+ "mcp_config.json",
34
+ "install.ps1",
35
+ "install.sh",
36
+ "README.md",
37
+ "CHANGELOG.md",
38
+ "LICENSE"
39
+ ],
40
+ "author": "paragon-ux",
41
+ "license": "MIT"
42
+ }
package/rules/AGENTS.md CHANGED
@@ -6,11 +6,27 @@
6
6
 
7
7
  ---
8
8
 
9
- ## Layer 1: Background Tasks & Polling Ban (Reactive Wakeup SSOT)
10
- - **Zero-Tolerance on Task Polling**: Do NOT call `manage_task(Action='status')` or `schedule` timers in a loop to wait for running background tasks. Background tasks execute asynchronously and automatically resume the agent via **Reactive Wakeup** (`<SYSTEM_MESSAGE>`).
11
- - **Yield Turn Immediately**: When a process moves to the background, stop calling tools, output a concise status message, and yield the turn.
12
- - **No Keyword or Timer Exemptions**: Words like "debug", "timeout", "diagnose", or "stuck" do NOT exempt an agent from lifecycle governance. Timers with `duration >= 120` are NOT exempt.
13
- - **Circuit Breaker**: Repeated polling calls across any tasks in a session trigger an escalating circuit breaker. At 5 cumulative denials, autonomous execution is frozen via `force_ask`.
9
+ ## Layer 1: Background Tasks & Proportional Backoff Protocol (Reactive Wakeup SSOT)
10
+ - **Zero-Tolerance on Busy-Polling**: Do NOT call `manage_task(Action='status')` in tight loops or schedule rapid timers to wait for running background tasks. Background tasks execute asynchronously and automatically resume the agent via **Reactive Wakeup** (`<SYSTEM_MESSAGE>`).
11
+ - **Proportional Backoff Protocol**: Diagnostic inspections scale along an exponential backoff curve:
12
+ $\text{Cooldown} = \min(600, \text{round}(30 \times 2.5^{\text{pollCount} - 1})) \quad [0\text{s} \to 30\text{s} \to 75\text{s} \to 188\text{s} \to 469\text{s} \to 600\text{s}]$
13
+ - Check #1 (Immediate upon launch): Permitted ($0\text{s}$).
14
+ - Premature status checks within an active backoff window are denied. 5 consecutive premature checks trigger the `force_ask` circuit breaker.
15
+ - Silent/hung background tasks: Once the backoff window elapses, a single diagnostic check is permitted to inspect logs, detect deadlocks, and issue `manage_task(Action='kill')` if needed.
16
+ - **Coordinated Watchdog Protocol (`schedule`)**: For long-running operations (builds, migrations, test suites) expected to take $> 15\text{s}$:
17
+ - Schedule a coordinated watchdog timer: `schedule(DurationSeconds=45, Prompt="Check status of background task-X", TimerCondition="task-X")`.
18
+ - Watchdog timers must satisfy `DurationSeconds >= Math.max(30, requiredBackoffSec)`. Short timers are denied.
19
+ - If the task completes early, Reactive Wakeup auto-cancels the timer and resumes the agent immediately.
20
+ - If the task is still running when the watchdog fires, perform a legal diagnostic check (`manage_task(status)`) and post a live progress update in chat for the user.
21
+ - **Mandatory Pre-Yield Status Card**: When a command moves to the background, NEVER yield with a silent or generic one-liner. Output a structured Markdown card so the user is never left wondering if the IDE has stalled:
22
+ ```markdown
23
+ ### ⏳ Background Execution Started
24
+ - **Command**: `<cmd>`
25
+ - **Scope / Target**: `<scope>`
26
+ - **Estimated Duration**: `~X minutes`
27
+ - **Watchdog Active**: Coordinated check scheduled in 45s via Reactive Wakeup.
28
+ ```
29
+ - **Kill & Stdin Immediate Exemption**: `manage_task(Action='kill')` and `manage_task(Action='send_input')` are ALWAYS permitted immediately ($0\text{s}$ backoff, zero penalties).
14
30
 
15
31
  ---
16
32
 
@@ -34,7 +50,11 @@
34
50
 
35
51
  ## Layer 3: Synchronous Execution & Subagent Delegation
36
52
  - **Maximum Synchronous Window**: Non-daemon `run_command` calls are automatically set to `WaitMsBeforeAsync: 10000` to complete synchronously and prevent unnecessary background detachment.
37
- - **Targeted Test Execution**: Avoid massive multi-minute test sweeps in interactive turns. Run targeted, quiet, fail-fast commands (e.g. `pytest tests/test_core.py -q -x`).
53
+ - **Targeted Test Execution & Opt-In Fail-Fast (LH-11)**:
54
+ - **Targeted Invocations**: In interactive turns, prefer targeting specific test files or directories (e.g. `pytest tests/test_core.py`) rather than running full multi-minute repository sweeps.
55
+ - **Fail-Fast Flags are Opt-In**: Fail-fast flags (`-x`, `--maxfail=1`) are opt-in. Use them when quickly debugging a localized failure, but omit them when assessing the full blast radius across the codebase to avoid the "whack-a-mole" loop.
56
+ - **Coordinated Watchdog Mandatory for Full Sweeps**: When running full multi-minute test sweeps without `-x`, agents **MUST** pair execution with the **Coordinated Watchdog Protocol** (`schedule(DurationSeconds=45, ...)`) and output a **Pre-Yield Status Card**. Never yield silently.
57
+ - **Subagent Delegation for Deep Audits**: When a comprehensive repository-wide test audit is required, delegate the sweep to a subagent (`invoke_subagent`) to keep the main thread responsive.
38
58
  - **Subagent Delegation**: Delegate heavy multi-file exploration and exploratory research to subagents (`invoke_subagent`). Subagents absorb intermediate steps and return high-signal summaries.
39
59
  - **Benchmark / Evaluator Integrity**: Never mutate benchmark definitions, prompt templates, or scoring artifacts during an active evaluation without explicit user confirmation.
40
60
 
@@ -276,7 +276,21 @@ function rewriteCommandWithRtk(rawCmd) {
276
276
  return trimmed;
277
277
  }
278
278
 
279
- // Fast-path bypass for shell builtins, non-rewritable binaries, and powershell cmdlets
279
+ // Unwrap shell wrappers (e.g. cmd /c "...", powershell -Command "...", bash -c "...") (LH-01-B)
280
+ const shellWrapperMatch = trimmed.match(
281
+ /^(cmd(?:\.exe)?\s+\/[cC]|(?:powershell|pwsh)(?:\.exe)?\s+(?:-Command|-c)|(?:bash|sh)\s+-c)\s+["']?(.+?)["']?$/i
282
+ );
283
+ if (shellWrapperMatch) {
284
+ const wrapperPrefix = shellWrapperMatch[1];
285
+ const innerCmd = shellWrapperMatch[2].trim();
286
+ const rewrittenInner = rewriteCommandWithRtk(innerCmd);
287
+ if (rewrittenInner !== innerCmd) {
288
+ return `${wrapperPrefix} "${rewrittenInner}"`;
289
+ }
290
+ return trimmed;
291
+ }
292
+
293
+ // Fast-path bypass for shell builtins, non-rewritable binaries, and powershell cmdlets
280
294
  const firstWord = (trimmed.match(/^[^\s"']+/)?.[0] || "").toLowerCase();
281
295
  if (
282
296
  NON_REWRITABLE_BINARIES.has(firstWord) ||
@@ -317,6 +331,17 @@ function rewriteCommandWithRtk(rawCmd) {
317
331
  return rewritten;
318
332
  }
319
333
 
334
+ /**
335
+ * Calculates the required minimum cooldown interval (in seconds)
336
+ * proportional to the number of diagnostic status inspections already performed.
337
+ * Formula: Math.min(600, Math.round(30 * Math.pow(2.5, Math.max(0, pollCount - 1))))
338
+ * Levels: Level 0 -> 0s (baseline), Level 1 -> 30s, Level 2 -> 75s, Level 3 -> 188s, Level 4 -> 469s, Level 5+ -> 600s
339
+ */
340
+ function getRequiredBackoffSeconds(pollCount) {
341
+ if (pollCount <= 0) return 0;
342
+ return Math.min(600, Math.round(30 * Math.pow(2.5, Math.max(0, pollCount - 1))));
343
+ }
344
+
320
345
  // --- Main Hook Entrypoint ---
321
346
  let isGovernedTool = false;
322
347
 
@@ -364,6 +389,29 @@ try {
364
389
  });
365
390
  }
366
391
 
392
+ // LH-02-B: Direct shell access to internal Antigravity execution state (.system_generated)
393
+ if (isProtectedInternalState(rawCmd)) {
394
+ respond({
395
+ decision: "deny",
396
+ reason:
397
+ "Antigravity Execution Governance: Direct shell access to internal Antigravity execution state (.system_generated) is strictly prohibited to prevent transcript bloat and polling loops.\n" +
398
+ "- To inspect conversation transcripts: Call MCP tool read_transcript(conversationId=\"...\", mode=\"compact\") or query_transcript(...).\n" +
399
+ "- To inspect background tasks: Yield execution turn and rely on native Reactive Wakeup (<SYSTEM_MESSAGE>). Do not read internal task logs."
400
+ });
401
+ }
402
+
403
+ // LH-09-B: Shell command mutation of protected benchmark artifacts
404
+ if (
405
+ (rawCmd.includes(">") || /\b(?:rm|del|Remove-Item)\b/i.test(rawCmd)) &&
406
+ isProtectedBenchmarkPath(rawCmd)
407
+ ) {
408
+ respond({
409
+ decision: "force_ask",
410
+ reason:
411
+ "[BENCHMARK MUTATION DETECTED] Shell command mutation of protected evaluator/benchmark artifact requires explicit user confirmation to prevent benchmark contamination."
412
+ });
413
+ }
414
+
367
415
  const rewritten = rewriteCommandWithRtk(rawCmd);
368
416
  const isDaemon = args.IsDaemon === true || args.isDaemon === true;
369
417
  const waitMs = args.WaitMsBeforeAsync !== undefined ? args.WaitMsBeforeAsync : args.waitMsBeforeAsync;
@@ -486,33 +534,43 @@ try {
486
534
  session.taskIds.push(taskId);
487
535
  }
488
536
 
489
- // First inspection check in the session is permitted
490
- if (session.pollCount === 0) {
491
- session.pollCount = 1;
537
+ const now = Date.now();
538
+ const requiredBackoffSec = Math.max(30, getRequiredBackoffSeconds(session.pollCount));
539
+ const requiredBackoffMs = requiredBackoffSec * 1000;
540
+ const elapsedMs = session.lastPollTime ? (now - session.lastPollTime) : Infinity;
541
+ const elapsedSec = Math.floor(elapsedMs / 1000);
542
+
543
+ // Premature check before backoff interval has elapsed
544
+ if (elapsedMs < requiredBackoffMs) {
545
+ session.denials++;
492
546
  saveSessionState(convId, session);
493
- respond({ decision: "allow" });
494
- }
495
547
 
496
- // Any subsequent polling checks in the session are strictly denied
497
- session.denials++;
498
- saveSessionState(convId, session);
548
+ if (session.denials >= 5) {
549
+ respond({
550
+ decision: "force_ask",
551
+ reason: `[CIRCUIT BREAKER ACTIVATED] Autonomous loop suspended: manage_task('${action}') called prematurely ${session.denials} times during active backoff window across tasks [${session.taskIds.join(", ")}]. Stop calling tools and yield the turn for native Reactive Wakeup (<SYSTEM_MESSAGE>). User confirmation required to proceed.`
552
+ });
553
+ }
499
554
 
500
- if (session.denials >= 5) {
555
+ const remainingSec = Math.ceil((requiredBackoffMs - elapsedMs) / 1000);
501
556
  respond({
502
- decision: "force_ask",
503
- reason: `[CIRCUIT BREAKER ACTIVATED] Autonomous loop suspended: manage_task('${action}') has been denied ${session.denials} times across tasks [${session.taskIds.join(", ")}]. Stop calling tools and yield the turn for native Reactive Wakeup (<SYSTEM_MESSAGE>). User confirmation required to proceed.`
557
+ decision: "deny",
558
+ reason: `Antigravity Execution Governance: Proportional Backoff Active (Level ${session.pollCount}, ${session.denials} denial${session.denials > 1 ? "s" : ""}). Diagnostic check #${session.pollCount + 1} requires at least ${requiredBackoffSec}s elapsed since previous check (${elapsedSec}s elapsed, ${remainingSec}s remaining). Tasks execute asynchronously and notify you automatically via native Reactive Wakeup (<SYSTEM_MESSAGE>) upon completion. Stop calling tools and yield your turn now.`
504
559
  });
505
560
  }
506
561
 
507
- respond({
508
- decision: "deny",
509
- reason: `Antigravity Execution Governance: Background task polling is prohibited (${session.denials} denial). Tasks execute asynchronously and notify you automatically via native Reactive Wakeup (<SYSTEM_MESSAGE>) upon completion. Stop calling tools and yield your turn now.`
510
- });
562
+ // Backoff window satisfied! Allow diagnostic check and advance level:
563
+ session.pollCount++;
564
+ session.lastPollTime = now;
565
+ session.denials = 0;
566
+ saveSessionState(convId, session);
567
+ respond({ decision: "allow" });
511
568
  }
512
569
 
513
570
  if (toolName === "schedule") {
514
571
  const prompt = String(args.Prompt || args.prompt || "").replace(/^["']|["']$/g, "").trim().toLowerCase();
515
572
  const cond = String(args.TimerCondition || args.timerCondition || "").replace(/^["']|["']$/g, "").trim().toLowerCase();
573
+ const duration = Number(args.DurationSeconds || args.durationSeconds || args.duration || 0);
516
574
 
517
575
  const isTaskPoll =
518
576
  cond.startsWith("task-") ||
@@ -525,23 +583,30 @@ try {
525
583
  ((prompt.includes("check") || prompt.includes("status") || prompt.includes("poll") || prompt.includes("monitor") || prompt.includes("wait")) &&
526
584
  (prompt.includes("task") || prompt.includes("process") || prompt.includes("job") || prompt.includes("test") || prompt.includes("command") || prompt.includes("suite")));
527
585
 
528
- // All polling timers are strictly denied regardless of duration (LH-03)
529
586
  if (isTaskPoll) {
530
587
  const session = getSessionState(convId);
531
- session.denials++;
532
- saveSessionState(convId, session);
588
+ const requiredBackoffSec = Math.max(30, getRequiredBackoffSeconds(session.pollCount));
589
+
590
+ // Task watchdog timers must be AT LEAST the required backoff window!
591
+ if (duration < requiredBackoffSec) {
592
+ session.denials++;
593
+ saveSessionState(convId, session);
594
+
595
+ if (session.denials >= 5) {
596
+ respond({
597
+ decision: "force_ask",
598
+ reason: `[CIRCUIT BREAKER ACTIVATED] Autonomous loop suspended: schedule polling timer has been denied ${session.denials} times. Background tasks notify you automatically upon completion. User confirmation required to proceed.`
599
+ });
600
+ }
533
601
 
534
- if (session.denials >= 5) {
535
602
  respond({
536
- decision: "force_ask",
537
- reason: `[CIRCUIT BREAKER ACTIVATED] Autonomous loop suspended: schedule polling timer has been denied ${session.denials} times. Background tasks notify you automatically upon completion. User confirmation required to proceed.`
603
+ decision: "deny",
604
+ reason: `Antigravity Execution Governance: Task watchdog timer duration too short (${duration}s, ${session.denials} denial${session.denials > 1 ? "s" : ""}). Under Proportional Backoff (Level ${session.pollCount}), watchdog timer must be at least ${requiredBackoffSec}s. Tasks notify you automatically via native Reactive Wakeup (<SYSTEM_MESSAGE>). Stop calling tools and yield the turn instead.`
538
605
  });
539
606
  }
540
607
 
541
- respond({
542
- decision: "deny",
543
- reason: "Antigravity Execution Governance: Scheduling timers to poll or monitor background tasks is prohibited regardless of duration. Background tasks notify you automatically via native Reactive Wakeup (<SYSTEM_MESSAGE>). Stop calling tools and yield the turn instead."
544
- });
608
+ // If duration >= requiredBackoffSec, watchdog timer is permitted!
609
+ respond({ decision: "allow" });
545
610
  }
546
611
 
547
612
  // Non-polling timers (e.g. user-requested reminders) are allowed