agentfootprint
Version:
The explainable agent framework — backtrack a wrong answer to the exact context that caused it (evidence, not guesses). Built on footprintjs.
59 lines • 3.13 kB
JavaScript
/**
* buildCacheSubflow — the per-turn prompt-cache decision, as ONE subflow.
*
* Collapses the cache machinery into a single `sf-cache` boundary so the
* agent chart reads cleanly (one "Cache" box you can drill into) while the
* execution tree stays honest:
*
* decideCacheMarkers → CacheGate (decider) → ApplyMarkers / SkipCaching
*
* Layering (see src/cache/types.ts):
* - This subflow is the provider-AGNOSTIC DECISION layer — it only computes
* and gates provider-neutral `CacheMarker[]`. It knows nothing about any
* provider.
* - The provider-SPECIFIC MECHANISM (Anthropic `cache_control`, OpenAI
* automatic, …) is the attached provider's `CacheStrategy`, selected by
* the provider's declared `promptCaching` (`cacheStrategyFor`), applied
* later when the request is built.
*
* Why `UpdateSkillHistory` is NOT in here: the `skillHistory` rolling window
* must persist across loop iterations. Keeping `UpdateSkillHistory` in the
* main loop (just before this subflow) lets `skillHistory` live in the parent
* scope without round-tripping through this subflow's in/out mappers — the
* subflow stays pure (reads the turn's state, writes only `cacheMarkers`).
*
* Deps-free: every input (`activeInjections`, `iteration`, `skillHistory`,
* `recentHitRate`, `systemPromptCachePolicy`, …) is supplied by the PARENT's
* `inputMapper` at the mount site; the output is just `cacheMarkers`.
*/
import { flowChart } from 'footprintjs';
import { cacheDecisionStage, } from '../../cache/CacheDecisionSubflow.js';
import { cacheGateDecide } from '../../cache/CacheGateDecider.js';
import { STAGE_IDS } from '../../lib/conventions.js';
/**
* Build the `sf-cache` subflow chart. Called by `buildAgentChart` and
* `buildDynamicAgentChart` and mounted via `addSubFlowChartNext(
* SUBFLOW_IDS.CACHE, buildCacheSubflow(), 'Cache', { inputMapper,
* outputMapper, arrayMerge: Replace })`.
*/
export function buildCacheSubflow(options = {}) {
return flowChart('CacheDecision',
// Root stage = the cache-decision function (a chart cannot start with a
// nested subflow). Exported from CacheDecisionSubflow.ts and reused here —
// the cache decision is the root of sf-cache, no logic duplication.
cacheDecisionStage(options), 'decide-cache-markers', {
description: 'CacheDecision: walk activeInjections, evaluate cache directives, emit CacheMarker[]',
})
.addDeciderFunction('CacheGate', cacheGateDecide, STAGE_IDS.CACHE_GATE, 'Gate cache-marker application: kill switch / hit-rate / skill-churn')
.addFunctionBranch(STAGE_IDS.APPLY_MARKERS, 'ApplyMarkers',
// Pass-through — markers stay in scope; the request build reads them.
() => undefined, 'Proceed with cache markers from CacheDecision')
.addFunctionBranch(STAGE_IDS.SKIP_CACHING, 'SkipCaching',
// Clear markers so the request is built unmodified this iteration.
(scope) => {
scope.cacheMarkers = [];
}, 'Skip caching this iteration')
.end()
.build();
}
//# sourceMappingURL=buildCacheSubflow.js.map