feat(moments): a mount that keeps arriving where it does not apply proposes its own removal (milestone 458 step 7b, #4955)
CI & Build / Python lint (push) Successful in 2s
CI & Build / Plugin hooks (push) Successful in 16s
CI & Build / TypeScript typecheck (push) Successful in 54s
CI & Build / integration (push) Successful in 1m19s
CI & Build / Python tests (push) Successful in 2m3s
CI & Build / Build & push image (push) Successful in 18s

The open-after-moment signal proposes a mount; nothing proposed taking one
off, so a wrong mount was noise at every occurrence until someone happened
to notice. rule_misfired(rule_id, moment, why, reached_by) records a report
against a MOUNTED pair, counted per distinct day (the MCP door carries no
session id) on a new rule_moment_judgments.misfire column (migration 0119,
backup v24). At three days the response carries a line asking the agent to
offer the operator the fix - reject takes the rule off, unmap_action stops
the action reaching the moment, confirm keeps the mount and stops the
asking - and Settings > Moments lists it as an unmount proposal with the
reasons and the actions that reached it. A re-mount clears the count.

Taught in moments.md, missed-retrieval.md and the reply hold's wording.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
2026-10-05 18:19:59 -04:00
co-authored by Claude Opus 5.5
parent 8afd6da8af
commit 4e1320120d
19 changed files with 636 additions and 44 deletions
+21 -5
View File
@@ -101,19 +101,32 @@ export function unmapAction(change: MappingChange): Promise<void> {
}
/**
* Proposals that a rule belongs on a moment (milestone 458 step 7) — from a
* pass that read the rule, or from the rule being opened just after the
* moment fired. `GET /api/retrieval/moments/proposals`, the payload the
* Proposals about a rule's moments, waiting on a person. A `mount` proposal
* (milestone 458 step 7) comes from a pass that read the rule, or from the
* rule being opened just after the moment fired. An `unmount` proposal (step
* 7b) comes from sessions reporting that the mount arrived where it did not
* apply. `GET /api/retrieval/moments/proposals`, the payload the
* `rule_moment_proposals` MCP tool returns.
*/
export type ProposalSource = "pass" | "signal" | "edit";
export type ProposalSource = "pass" | "signal" | "edit" | "misfire";
export interface MisfireReason {
why: string;
reached_by: string;
at: string;
}
export interface MomentProposal {
proposal: "mount" | "unmount";
moment: string;
source: ProposalSource;
why: string;
/** For a misfire, `situations` counts distinct days and `co_surfaced` the reports. */
evidence: { situations: number; projects: number; co_surfaced: number };
created_at: string | null;
/** Unmount proposals only: the newest reasons given, and how often each action reached the moment. */
reasons?: MisfireReason[];
reached_by?: Record<string, number>;
}
export interface RuleProposals {
@@ -151,7 +164,10 @@ export function getProposals(ruleId?: number): Promise<ProposalsPayload> {
return apiGet(`/api/retrieval/moments/proposals${ruleId ? `?rule_id=${ruleId}` : ""}`);
}
/** A confirm MOUNTS the rule on the moment; a reject stops it being proposed again. */
/**
* A confirm MOUNTS the rule on the moment (on an unmount proposal, keeps it);
* a reject unmounts it if mounted and stops the pair being proposed again.
*/
export function judgeProposals(judgments: MomentJudgment[]): Promise<JudgeResult> {
return apiPost("/api/retrieval/moments/proposals/judge", { judgments });
}