agentfootprint
Version:
The explainable agent framework — backtrack a wrong answer to the exact context that caused it (evidence, not guesses). Built on footprintjs.
276 lines • 14 kB
JavaScript
/**
* The lease machine — engagement advances one pass at a time, by evidence.
*
* Pattern: pure reducer. `advanceEngagement(plan, prior, pass)` returns the
* next state, the injection ids to suppress this pass, and the
* standing changes for the record. No I/O, no clock, no model.
* Role: the kernel's law. A TENTATIVE engagement (a regex or classifier
* guess) is renewed only by concrete evidence — the map's own tool
* called, the cursor routed by the system, the model asking by
* name. Without corroboration for `renewalGrace` consecutive
* passes it is PARKED: its cursor stays exactly where the map put
* it, and its prompt and tools simply stop riding the next calls.
* An engagement backed by explicit or structural evidence stands.
*
* Recovery is evidence too: any explicit or structural evidence while parked
* re-engages the map on the spot — the model's accepted `read_skill` pick is
* the recovery door, so parking is never a trap. That door is real as of
* 9.59.0: the reachability gate used to refuse a pick of the node the cursor
* already occupies, which for a single-member map (or any map parked where
* the model wants to return) made parking permanent for the rest of the turn.
* A pick is now routed by INTENT — of a PARKED map's member it is a
* RE-ENGAGEMENT, which changes engagement and not position.
*
* Measured grounding: in the recorded 30-call stuck turn (an entry regex
* matched a noun), the map's four tools were never called and 29 of 30 moves
* were "stay". Under grace 3 this reducer parks it on call four, saving the
* remaining ~26 calls from re-serving ~7k characters of the wrong map.
*/
import { isTentative, renewalEvidenceOf, strengthOfMove, strongerOf, strongestOf, } from './evidence.js';
// FOLD · the one owner of which mounted maps are PARKED this pass — and so which injection ids and tool names come off the wire
// consumers read this and never re-derive it: the engaged/parked events, parkCard, scope.parkedToolNames,
// ctx.parkedIds, and toolCalls.ts · parkedNow via parkedMemberIds
// detached: yes — a freshly built record array each pass, never a mutated prior.
/**
* Advance every mounted map's engagement by one pass.
*
* Laws, in order:
* 1. A map first earns a record when its cursor lands on a member or a member
* activates; the founding strength is the winning clause's strength.
* 2. Renewal evidence resets `idle` and may UPGRADE the standing's strength
* (never downgrade — a guess later confirmed by the system stops decaying).
* An upgrade is a CHANGE: it emits, and `foundedBy`/`foundedAt` keep the
* cause the engagement actually started on.
* 3. A tentative engagement with no evidence counts one idle pass when ALL
* THREE clauses hold: its contribution was served last pass, none of its
* tools was called, and the model called something else instead. At
* `renewalGrace` it parks. A pass with no tool calls at all counts nothing
* (the model was thinking, not ignoring), and neither does a pass where
* the map's contribution never reached the wire (it cannot be ignored if
* it was not there).
* 4. `nonParkable` maps never park, whatever the count.
* 5. Explicit or structural evidence re-engages a parked map immediately.
*/
export function advanceEngagement(plan, prior, pass) {
const nextRecords = [];
const changes = [];
const parkedInjectionIds = [];
const parkedToolNames = [];
const priorByMap = new Map((prior ?? []).map((r) => [r.mapId, r]));
for (const map of plan.maps) {
const record = advanceOne(map, priorByMap.get(map.id), pass, plan.renewalGrace, changes);
if (record !== undefined) {
nextRecords.push(record);
if (record.standing === 'parked') {
parkedInjectionIds.push(...map.memberIds);
// A parked map's tools come off the wire whatever the tool posture.
parkedToolNames.push(...map.toolNames);
}
}
}
return { next: nextRecords, parkedInjectionIds, parkedToolNames, changes };
}
function advanceOne(map, prior, pass, renewalGrace, changes) {
const evidence = renewalEvidenceOf(map, pass);
const strongest = strongestOf(evidence);
// ── No record yet: does this pass found one? ──────────────────────────
if (prior === undefined) {
const cursorInMap = pass.currentNode !== undefined && map.memberIds.includes(pass.currentNode);
if (!cursorInMap && strongest === undefined)
return undefined; // nothing to manage yet
// Founding strength: the strongest evidence, else the clause that placed
// the cursor (an entry regex founds a LEXICAL engagement — a guess).
//
// An ABSENT clause founds `assumed`, not `structural` (9.59.0). Three
// reachable ways in, all of them previously written down as the
// strongest, non-decaying category:
// • the graph has no explain-next-skill hook, so no cause ever arrives
// (now also refused at mount — a kernel that can never park anything
// is decoration);
// • the cause was `'stay'` or `'none'`, which are not moves;
// • this is a later TURN: engagement state is per-run but the cursor is
// not, so turn one's mistaken regex entry arrived on turn two with no
// cause attached and was laundered into a permanent warrant.
// `assumed` is tentative, so all three now decay and park honestly
// instead of standing forever on a cause nobody stated.
const by = strongest ?? (pass.moveBy !== undefined ? strengthOfMove(pass.moveBy) : 'assumed');
// POSITION is recorded separately from ENGAGEMENT from the very first
// pass, so the two can never be read off one another later.
const positionBy = pass.moveBy !== undefined ? strengthOfMove(pass.moveBy) : 'assumed';
const record = {
mapId: map.id,
standing: 'engaged',
by,
since: pass.iteration,
foundedBy: by,
foundedAt: pass.iteration,
...(cursorInMap && pass.currentNode !== undefined && { foundedOn: pass.currentNode }),
...(cursorInMap &&
pass.currentNode !== undefined && {
at: pass.currentNode,
atBy: positionBy,
atSince: pass.iteration,
}),
idle: 0,
...(pass.witness !== undefined && { witness: pass.witness }),
};
changes.push({
kind: 'engaged',
mapId: map.id,
iteration: pass.iteration,
by,
...(pass.witness !== undefined && { witness: pass.witness }),
});
return record;
}
// ── Parked: only re-engaging evidence changes anything. ───────────────
if (prior.standing === 'parked') {
if (strongest === 'explicit' || strongest === 'structural') {
const record = {
mapId: map.id,
standing: 'engaged',
by: strongest,
since: pass.iteration,
// The founding pair survives a park and a recovery: the question
// "was this founded on a guess?" has ONE answer for the life of the
// record, whatever the standing has done since.
foundedBy: prior.foundedBy,
foundedAt: prior.foundedAt,
...(prior.foundedOn !== undefined && { foundedOn: prior.foundedOn }),
...positionOf(map, prior, pass),
idle: 0,
...(pass.witness !== undefined && { witness: pass.witness }),
};
changes.push({
kind: 'engaged',
mapId: map.id,
iteration: pass.iteration,
by: strongest,
reengaged: true,
...(pass.witness !== undefined && { witness: pass.witness }),
});
return record;
}
return prior; // stays parked; tentative evidence does not recover a park
}
// ── Engaged: renew, upgrade, or count idle. ───────────────────────────
const position = positionOf(map, prior, pass);
const moved = position.at !== undefined && position.at !== prior.at;
// ── TENANT CHANGE (9.59.0) — the cursor moved to a DIFFERENT member ────
// Eligibility is re-derived from the new position's OWN evidence rather
// than inherited from the old tenant's. The map is still engaged and its
// founding cause is untouched; what changed is the SUBJECT of the claim.
//
// The case the ruling names: the user explicitly asked for A, a declared
// edge then moved the cursor to B. Under the old single-`by` model the
// record read `explicit` on B — a user request for a skill the person had
// never mentioned, and `explicit` never decays, so the map rode every
// remaining call of the turn on a warrant B never had. Now B rests on the
// strength of the clause that actually produced it (a declared route is
// `structural`; an entry regex is `lexical` and can therefore park), while
// `foundedBy`/`foundedAt`/`foundedOn` keep saying, truthfully and forever,
// that this engagement began explicitly on A at iteration N.
//
// Downgrading here does NOT violate "never downgrade because time passed":
// no time passed, the subject changed.
if (moved) {
const by = strongerOf(position.atBy ?? 'assumed', strongest ?? 'assumed');
changes.push({
kind: 'engaged',
mapId: map.id,
iteration: pass.iteration,
by,
tenantChanged: true,
at: position.at,
foundedBy: prior.foundedBy,
...(pass.witness !== undefined && { witness: pass.witness }),
});
return { ...prior, ...position, by, idle: 0 };
}
if (strongest !== undefined) {
const by = strongerOf(prior.by, strongest);
if (by === prior.by)
return prior.idle === 0 ? prior : { ...prior, ...position, idle: 0 };
// An UPGRADE is a standing change, so it goes on the record and it
// emits. `by` moves; `foundedBy`/`foundedAt` never do — overwriting the
// founding cause in place (and silently) is how a record founded on a
// regex came to read `structural since iteration 1`.
changes.push({
kind: 'engaged',
mapId: map.id,
iteration: pass.iteration,
by,
upgraded: true,
foundedBy: prior.foundedBy,
...(prior.witness !== undefined && { witness: prior.witness }),
});
return { ...prior, ...position, by, idle: 0 };
}
// No evidence. Idle counts ONLY when all three documented clauses hold.
//
// (1) SERVED — this map's contribution actually reached the wire on the
// pass being judged. Implemented as of 9.59.0 from the previous pass's
// per-slot active set; before that it was asserted and never checked,
// including in the skip reason written onto the record. A contribution
// that never rode cannot have been ignored, so it earns no idle.
// (2) NONE OF ITS TOOLS CALLED — by control flow: an owned-tool call is
// evidence, and evidence returned above.
// (3) THE TURN WENT ELSEWHERE — at least one tool call landed. A pass with
// no tool calls at all counts nothing: the model was thinking.
const served = pass.servedLastPass.some((id) => map.memberIds.includes(id));
const calledElsewhere = pass.toolResults.length > 0;
if (!served || !calledElsewhere)
return prior;
const idle = prior.idle + 1;
if (isTentative(prior.by) && idle >= renewalGrace && map.nonParkable !== true) {
const record = {
...prior,
standing: 'parked',
since: pass.iteration,
idle,
};
changes.push({
kind: 'parked',
mapId: map.id,
iteration: pass.iteration,
by: prior.by,
idleCalls: idle,
...(prior.witness !== undefined && { witness: prior.witness }),
});
return record;
}
return { ...prior, idle };
}
/**
* FACT 2 — where the cursor is, and why it is there.
*
* Observed from the pass, never set: the kernel reads a position it has no
* power to move. When the cursor is outside this map (or unknown) the prior
* position is kept rather than blanked — "I cannot see it right now" is not
* "it moved", and blanking would fabricate a tenant change on the next pass.
*/
function positionOf(map, prior, pass) {
const node = pass.currentNode;
if (node === undefined || !map.memberIds.includes(node)) {
return {
...(prior.at !== undefined && { at: prior.at }),
...(prior.atBy !== undefined && { atBy: prior.atBy }),
...(prior.atSince !== undefined && { atSince: prior.atSince }),
};
}
if (node === prior.at) {
// Same tenant: the arrival facts stand. Restating them each pass would
// reset `atSince` and erase how long this member has held the cursor.
return {
at: node,
...(prior.atBy !== undefined && { atBy: prior.atBy }),
...(prior.atSince !== undefined && { atSince: prior.atSince }),
};
}
return {
at: node,
atBy: pass.moveBy !== undefined ? strengthOfMove(pass.moveBy) : 'assumed',
atSince: pass.iteration,
};
}
//# sourceMappingURL=lease.js.map