agentfootprint
Version:
The explainable agent framework — backtrack a wrong answer to the exact context that caused it (evidence, not guesses). Built on footprintjs.
81 lines • 3.1 kB
JavaScript
;
Object.defineProperty(exports, "__esModule", { value: true });
exports.normalizeHostTurn = exports.resolveTurnNumber = exports.maxStoredTurn = void 0;
/**
* How many pages the scan will read before it stops asking. 100 pages of
* 1000 is 100k entries in ONE conversation's namespace — far past any real
* transcript, and a bound rather than a hang if an adapter's cursor never
* terminates. Stopping early can only UNDER-count, which the `+1` and the
* host's own floor then correct upward.
*/
const MAX_SCAN_PAGES = 100;
/** Page size requested per `list()` call. */
const SCAN_PAGE_SIZE = 1000;
/** A turn is a positive integer. Anything else is not a turn. */
function turnOf(entry) {
const raw = entry.source?.turn;
if (typeof raw !== 'number' || !Number.isFinite(raw))
return 0;
const turn = Math.floor(raw);
return turn > 0 ? turn : 0;
}
/**
* The highest turn already stored in this identity's namespace — 0 when the
* conversation has never been written to.
*
* Only LIVE entries are seen: `store.list` drops TTL-expired ones, the same
* as every read path, so an expired turn 4 does not hold the numbering open.
*/
async function maxStoredTurn(store, identity, options = {}) {
const read = options.turnFor ?? turnOf;
let max = 0;
let cursor;
let seen;
for (let page = 0; page < MAX_SCAN_PAGES; page++) {
const result = await store.list(identity, {
limit: SCAN_PAGE_SIZE,
...(cursor !== undefined && { cursor }),
});
for (const entry of result.entries) {
const turn = read(entry);
if (turn > max)
max = turn;
}
if (result.cursor === undefined)
break;
// An adapter that hands back the cursor it was given would page forever.
if (result.cursor === seen)
break;
seen = result.cursor;
cursor = result.cursor;
}
return max;
}
exports.maxStoredTurn = maxStoredTurn;
/**
* Resolve the turn number this run should stamp on everything it writes.
*
* Returns at least 1, always. A store that throws is NOT swallowed: a memory
* whose store is unreachable is going to fail on the next read anyway, and a
* turn number quietly guessed here is how entries overwrite each other.
*/
async function resolveTurnNumber(options) {
const hostTurn = normalizeHostTurn(options.hostTurn);
const stores = Array.from(new Set(options.stores));
let stored = 0;
for (const store of stores) {
const max = await maxStoredTurn(store, options.identity);
if (max > stored)
stored = max;
}
return Math.max(hostTurn, stored + 1);
}
exports.resolveTurnNumber = resolveTurnNumber;
/** A host turn below 1 (or not a number at all) means "I do not know" → 1. */
function normalizeHostTurn(hostTurn) {
if (typeof hostTurn !== 'number' || !Number.isFinite(hostTurn))
return 1;
return Math.max(1, Math.floor(hostTurn));
}
exports.normalizeHostTurn = normalizeHostTurn;
//# sourceMappingURL=resolveTurnNumber.js.map