agentfootprint
Version:
The explainable agent framework — backtrack a wrong answer to the exact context that caused it (evidence, not guesses). Built on footprintjs.
110 lines • 6.18 kB
JavaScript
;
/**
* The park card — what the MODEL is told when a map is parked.
*
* Pattern: pure function over plain data. No I/O, no scope, no engine.
* Role: the kernel's model-visible surface. Everything else the kernel
* produces lands on the RECORD (skip reasons, `map.parked` events),
* which a human reads afterwards. The model reads none of it.
*
* ── The defect this exists for (9.59.0) ──────────────────────────────────
* Parking was invisible on the wire, and on the default posture it was worse
* than invisible — it was CONTRADICTED. `.maps()` documents that parking stops
* "the prompt fragment AND tools"; with `scopeTools` false (the default for
* flat graphs) only the fragment stopped, and every one of the
* parked map's tool schemas kept riding. So the model saw four tools it was
* being invited to call, belonging to a skill whose instructions had silently
* vanished, with nothing anywhere saying why or how to get them back.
*
* A door the model cannot see is not a door. Re-engagement was reachable in
* principle and unreachable in practice, because nothing told the model that
* re-picking a skill it appears to already be in means anything at all.
*
* ── What the card says, and why each part is there ───────────────────────
* CURSOR and ENGAGEMENT are printed as SEPARATE fields. That is the whole
* teaching move: the model is shown the distinction rather than left to infer
* a contradiction from a skill that is both "where I am" and "not talking".
* Then the REASON, so the state is not arbitrary. Then the WAY BACK, named as
* a concrete call, with the one sentence that stops it being read as a cursor
* move: it changes engagement, not position.
*
* Kept to a few lines on purpose. This rides every call while a map is parked,
* and a verbose status block would spend the tokens parking just saved.
*/
Object.defineProperty(exports, "__esModule", { value: true });
exports.PARK_CARD_ID = exports.parkCard = void 0;
/** One line per parked map, plus the header that names what these lines are. */
const HEADER = 'Map status (the framework is telling you this; it is not part of the task):';
/**
* Why this map is parked, in the model's own terms.
*
* Deliberately does NOT restate the idle arithmetic — the model cannot act on
* "idle 3 of 3" and the record already carries it. It states the one thing the
* model can act on: nothing currently justifies serving this map.
*/
function reasonOf(record) {
return record.by === 'assumed'
? 'nothing on this turn explains why it is loaded'
: 'no recent evidence that it is the right map for this turn';
}
// LENS · system-text · request-ephemeral
// reads: the parked standing, cursor and by ← advance.next, the value lease.ts · advanceEngagement just
// returned on THIS pass, so the card and the tool suppression are two views of one decision
// the map's id and memberIds ← the EngagementPlan, resolved at build time
// law: may omit, never deny; every clause anchored to the call it was composed on.
/**
* Render the model-visible card for a set of parked maps.
*
* Returns `undefined` when nothing is parked — the caller then adds nothing to
* the prompt at all, which keeps the unparked path byte-identical.
*
* `mayName` is the caller's role filter (`isHiddenSkill`, negated): a member
* skill the role may not read is never named, and a parked map with no member
* left to name gets no card line at all (may omit, never deny).
*/
function parkCard(plan, engagement, mayName = () => true) {
if (engagement === undefined || engagement.length === 0)
return undefined;
const byId = new Map(plan.maps.map((m) => [m.id, m]));
const lines = [];
for (const record of engagement) {
if (record.standing !== 'parked')
continue;
const map = byId.get(record.mapId);
if (map === undefined)
continue;
// The cursor is named, and named as UNCHANGED. A model that reads
// "engagement: parked" beside a skill id needs to be told in the same
// breath that its position did not move, or the obvious inference is that
// it was thrown out of the skill.
const visible = map.memberIds.filter(mayName);
if (visible.length === 0)
continue;
const cursor = record.at !== undefined && mayName(record.at) ? record.at : '(unknown)';
const members = visible.join(', ');
lines.push(` - ${map.id} — cursor: ${cursor} (unchanged) · engagement: PARKED · ` +
`reason: ${reasonOf(record)} · re-engage: available`);
// SAYS WHAT PARKING DOES, NOT WHAT THIS WIRE HOLDS. "…are not being sent
// right now" was a claim about a request that did not exist yet: this card
// is composed in the injection-engine pass, and the tools slot that acts on
// the park (`parkedToolNames`) runs after it. The card cannot see the wire
// it is describing, so it describes the STATE it is reporting instead —
// which is the same lesson for the model and is true whenever it is read.
lines.push(` Parking stops its instructions and its tools from being sent. To bring them ` +
`back, call read_skill for any of: ${members}. That is allowed even for the ` +
`skill the cursor is already on — it changes ENGAGEMENT (whether this map is ` +
`sent to you), not POSITION (where the cursor is). If this map is not what ` +
`this turn needs, ignore this and carry on.`);
}
return lines.length === 0 ? undefined : `${HEADER}\n${lines.join('\n')}`;
}
exports.parkCard = parkCard;
/**
* The injection id the card is served under.
*
* A stable, reserved id so the card is one identifiable row on the record
* (composition, budget, the context ledger) rather than an anonymous string
* appended to somebody else's fragment.
*/
exports.PARK_CARD_ID = 'agentfootprint:map-status';
//# sourceMappingURL=parkCard.js.map