UNPKG

agentfootprint

Version:

The explainable agent framework — backtrack a wrong answer to the exact context that caused it (evidence, not guesses). Built on footprintjs.

106 lines • 4.55 kB
"use strict"; /** * strategies/lifecycle — who is still using a strategy, and who may stop it. * * A strategy is a plain object a consumer builds and hands to `enable.*`. The * same object can legitimately be handed over more than once: to two runners * (an Agent and the Sequence that contains it), twice to one runner, or to the * same runner again after an unsubscribe — the shipped audit-export example * does exactly that, deliberately, to collect two runs into one hash chain. * * `stop()` is terminal in every adapter we ship: events exported afterwards * are dropped and there is no restart. So "stop it when someone lets go of it" * is wrong — it would silently un-record the second half of that example. This * module holds the one fact that makes stopping safe: * * **how many live subscriptions still point at this strategy.** * * Three laws, and they are the whole module: * * 1. An `Unsubscribe` NEVER stops a strategy. It releases one subscription. * That is the compatibility law: code written before this existed keeps * the behaviour it was written against. * 2. A strategy is stopped only when the last subscription has been released * AND something explicitly asked for teardown (`handle.stop()`, * `agent.shutdown()`). Sharing a strategy therefore cannot produce a * half-stopped pipeline where one runner's shutdown blinds another's. * 3. `stop()` reaches a strategy AT MOST ONCE, ever. Adapters are already * idempotent; this makes the guarantee the framework's rather than the * adapter author's. * * A `WeakMap` keyed on the strategy object holds the count, so nothing here * retains a strategy that the consumer has dropped, and cross-runner sharing * needs no global list. */ Object.defineProperty(exports, "__esModule", { value: true }); exports.stopStrategyIfUnused = exports.isStopped = exports.subscriptionCount = exports.releaseStrategy = exports.retainStrategy = exports.ASYNC_DISPOSE = void 0; /** * `Symbol.asyncDispose` where the runtime has it (Node ≥ 20.4 — every engine * this package supports), and the registered fallback where it does not, so a * handle's disposal method is never keyed on `undefined`. ONE owner, because * two spellings of this key would make `await using` work on a handle and not * on the tracked wrapper around it. */ exports.ASYNC_DISPOSE = Symbol.asyncDispose ?? Symbol.for('Symbol.asyncDispose'); const USAGE = new WeakMap(); function usageOf(strategy) { let usage = USAGE.get(strategy); if (!usage) { usage = { subscriptions: 0, stopped: false }; USAGE.set(strategy, usage); } return usage; } /** One more live subscription. Called by every `attach*Strategy`. */ function retainStrategy(strategy) { usageOf(strategy).subscriptions += 1; } exports.retainStrategy = retainStrategy; /** * One fewer live subscription. Called by every `Unsubscribe`. * * Deliberately does NOT stop the strategy at zero — see law 1. Idempotent * below zero: calling an unsubscribe twice releases one subscription, not two. */ function releaseStrategy(strategy) { const usage = usageOf(strategy); if (usage.subscriptions > 0) usage.subscriptions -= 1; } exports.releaseStrategy = releaseStrategy; /** Live subscriptions pointing at this strategy. Diagnostic + tests. */ function subscriptionCount(strategy) { return usageOf(strategy).subscriptions; } exports.subscriptionCount = subscriptionCount; /** Has this strategy already been stopped? Diagnostic + tests. */ function isStopped(strategy) { return usageOf(strategy).stopped; } exports.isStopped = isStopped; /** * Stop the strategy IF nothing is still subscribed to it and it has not been * stopped before. Returns what actually happened, so a caller can say why it * did nothing rather than guessing. * * Errors from the strategy's own `stop()` are swallowed: teardown runs on the * shutdown path, and one vendor's broken `stop()` must not prevent the next * strategy — or the process — from finishing. */ function stopStrategyIfUnused(strategy) { const usage = usageOf(strategy); if (usage.stopped) return 'already-stopped'; if (usage.subscriptions > 0) return 'still-subscribed'; usage.stopped = true; try { strategy.stop?.(); } catch { // Passive-recorder rule: teardown never throws at the caller. } return 'stopped'; } exports.stopStrategyIfUnused = stopStrategyIfUnused; //# sourceMappingURL=lifecycle.js.map