agentfootprint
Version:
The explainable agent framework — backtrack a wrong answer to the exact context that caused it (evidence, not guesses). Built on footprintjs.
94 lines • 5.05 kB
TypeScript
/**
* startRuleClaim — WHO CLAIMS A PHRASE. The one place that answers it.
*
* Two check-ups ask the same question of a graph's start rules and want
* opposite answers:
*
* • `skillExamples.ts` declares a phrase and asserts THIS rule claims it;
* • `skillNeverRoutes.ts` declares a phrase and asserts NO rule claims it.
*
* The question underneath is identical — run the compiled conditions over one
* concrete phrase, on the context a turn's first iteration really hands a start
* rule, and see who says yes. Two copies of that would drift the moment one of
* them learned something about the router the other did not, and the two would
* then disagree about the same graph. So it lives here, once, and the modules
* that report keep only their own sentences.
*
* Nothing here decides SEVERITY or writes a message — that is the reporting
* module's job, because the same "no" means different things to a positive and
* a negative assertion. This module only runs predicates and names conditions.
*
* Zone: PURE CORE (no engine, no loop). Pinned by
* `test/injection-engine/skill-graph-fence.test.ts`.
*/
import type { InjectionContext } from './types.js';
import { type SkillMatchData } from './skillMatch.js';
/**
* The slice of an entry declaration a claim question reads — structural, so
* `skillGraph.ts`'s module-private `EntryDecl` satisfies it unchanged (the same
* trick `skillIntent.ts` uses).
*/
export interface StartRuleDecl {
readonly id: string;
/** The compiled condition (from `match:`) or the author's `when`. Absent on
* an unconditional entry and on an intent rule (no sync predicate). */
readonly when?: (ctx: InjectionContext) => boolean;
/** The serializable matcher behind `when`, when declared as data — quoted
* back in the messages, and the one way to spot the intent arm. */
readonly match?: SkillMatchData;
}
/**
* Run one entry's condition over one phrase, on a COLD-START context — the
* context a turn's first iteration really hands a start rule: iteration 1, the
* phrase as `userMessage`, no history, no cursor, no tool results. A throw is
* reported as `'threw'` and treated as a no-match by every caller, which is
* exactly what the cursor resolver does with it at run time (so a claim made
* here stays true of the run).
*
* An entry with NO condition matches everything: that is the cold walk's law
* (`if (!e.when) return { to: e.id }`), not a shortcut.
*/
export declare function runsOn(entry: StartRuleDecl, phrase: string): 'match' | 'no-match' | 'threw';
/** The one context every predicate in a phrase check is judged on. */
export declare function coldContext(userMessage: string): InjectionContext;
/** The one context every predicate here is judged on, spelled out for the
* reader of a message — a check that names its own inputs can be argued with;
* one that hides them can only be guessed at. Kept as ONE string so every
* message describes the SAME context {@link coldContext} really builds. */
export declare const COLD_CONTEXT_PHRASE: string;
/** One claimant of a phrase: the entry, and its position in declaration order.
* (Named Claimant since 9.58.0 — `Claim<T>` on the `/maps` door is the
* honesty primitive, a different thing; one name, one meaning.) */
export interface Claimant {
readonly entry: StartRuleDecl;
readonly index: number;
}
/** How a rule's condition is named in a message: the data matcher quoted back,
* the honest word for opaque code — or, for an entry that declares neither,
* what having no condition MEANS. Naming a `when` predicate an unconditional
* entry does not have is how a report describes a graph nobody wrote. */
export declare function describeCondition(entry: StartRuleDecl): string;
/**
* The two claimant sets, mirroring the TWO start laws (see `skillExamples.ts`'s
* header for the whole statement) rather than re-inventing either:
*
* • COLD WALK (`makeResolveCursor`, the default mount): every non-intent
* entry, unconditional ones included — they claim everything from their
* position onward.
* • CASCADE tier 1 (`firstRuleMatch`, mounted by `.classify()` OR by
* `continuity: 'conversation'`): the CONDITIONAL non-intent entries only —
* an unconditional entry is a default, not a rule.
*
* Intent entries appear in NEITHER: they compile to no sync predicate at all,
* and the classifier judges them at run time with a scorer this pass does not
* run.
*/
export interface ClaimLaws {
/** First claimant under the declaration-order cold walk, or undefined. */
readonly underColdWalk: (phrase: string) => Claimant | undefined;
/** First claimant under the turn-start cascade's tier 1, or undefined. */
readonly underCascade: (phrase: string) => Claimant | undefined;
}
/** Build both laws' claim finders over one entry list, in declaration order. */
export declare function claimLaws(entries: readonly StartRuleDecl[]): ClaimLaws;
//# sourceMappingURL=startRuleClaim.d.ts.map