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
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:
@@ -147,10 +147,13 @@ async def propose_rule_moments(proposals: list[dict]) -> dict:
|
||||
async def rule_moment_proposals(rule_id: int = 0) -> dict:
|
||||
"""The moment proposals waiting on the operator, grouped by rule.
|
||||
|
||||
Each rule carries what it is mounted on now and its proposals: the moment,
|
||||
where the proposal came from (`pass` — read from the rule; `signal` — the
|
||||
rule kept being opened just after that moment fired), the reason, and the
|
||||
evidence counts. `rule_id` narrows to one rule.
|
||||
Each rule carries what it is mounted on now and its proposals. Each
|
||||
proposal is a `mount` or an `unmount`, with the moment, where it came
|
||||
from (`pass` — read from the rule; `signal` — the rule kept being opened
|
||||
just after that moment fired; `misfire` — sessions reported the mount
|
||||
arriving where it did not apply, with their `reasons` and the actions
|
||||
that `reached_by` the moment), the reason, and the evidence counts.
|
||||
`rule_id` narrows to one rule.
|
||||
"""
|
||||
return await judgments_svc.pending(current_user_id(), rule_id=rule_id or None)
|
||||
|
||||
@@ -160,14 +163,54 @@ async def judge_rule_moments(judgments: list[dict]) -> dict:
|
||||
|
||||
Each item is `{"rule_id": N, "moment": "work.finish", "verdict":
|
||||
"confirm" | "reject", "note": "why"}`. Confirm mounts the rule on that
|
||||
moment beside what it already has; reject records that it does not belong
|
||||
there (and unmounts it if it was mounted), so neither the pass nor the
|
||||
signal proposes the pair again. Moment `""` judges a "no moment fits"
|
||||
moment beside what it already has — on an `unmount` proposal, it keeps
|
||||
the mount and stops the misfire question; reject records that it does not
|
||||
belong there (and unmounts it if it was mounted), so neither the pass nor
|
||||
the signal proposes the pair again. Moment `""` judges a "no moment fits"
|
||||
answer. Put the operator's reason in `note`.
|
||||
"""
|
||||
return await judgments_svc.judge(current_user_id(), judgments)
|
||||
|
||||
|
||||
async def rule_misfired(rule_id: int, moment: str, why: str,
|
||||
reached_by: str = "", project_id: int = 0) -> dict:
|
||||
"""Report that a rule MOUNTED on a moment arrived there and did not apply.
|
||||
|
||||
A mount delivers its rule every time the moment fires, whatever the work
|
||||
is about — so a mount that is wrong is noise at every occurrence, and
|
||||
nothing else notices. Call this when a line said a rule arrived *at* a
|
||||
moment ("at work.verify, reached by `actions_run_read`"), you read it,
|
||||
and it does not govern what you were doing there. It costs one call and
|
||||
asks nothing of the operator; reports gather, counted once per day, and
|
||||
once a pair has them on three distinct days the response carries a line
|
||||
asking you to offer the operator the fix — take the rule off the moment,
|
||||
or unmap the action when it is the action that is wrong here.
|
||||
|
||||
A rule that applied, even one you were already following, is not a
|
||||
misfire. A rule that arrived by resemblance rather than a mount is
|
||||
refused with the fix for that (its trigger).
|
||||
|
||||
Args:
|
||||
rule_id: the rule the line named.
|
||||
moment: the moment it arrived at, as the line names it.
|
||||
why: what you were doing and why the rule did not bear on it. Required
|
||||
— it is what tells the operator whether the rule or the action
|
||||
is wrong.
|
||||
reached_by: the action the line says reached the moment (`git push`,
|
||||
`status=done`). Counted, so the operator can see which action
|
||||
keeps bringing it.
|
||||
project_id: the project you are working in (0 = none).
|
||||
|
||||
Returns `recorded`, `days` so far against the `bar`, and `context`: a line
|
||||
to act on once the bar is crossed, else "". A mount the operator chose to
|
||||
keep says so under `kept`.
|
||||
"""
|
||||
return await judgments_svc.misfired(
|
||||
current_user_id(), rule_id, moment, why=why, reached_by=reached_by,
|
||||
project_id=project_id or None,
|
||||
)
|
||||
|
||||
|
||||
def register(mcp) -> None:
|
||||
mcp.tool(name="list_moments")(list_moments)
|
||||
mcp.tool(name="map_action")(map_action)
|
||||
@@ -176,3 +219,4 @@ def register(mcp) -> None:
|
||||
mcp.tool(name="propose_rule_moments")(propose_rule_moments)
|
||||
mcp.tool(name="rule_moment_proposals")(rule_moment_proposals)
|
||||
mcp.tool(name="judge_rule_moments")(judge_rule_moments)
|
||||
mcp.tool(name="rule_misfired")(rule_misfired)
|
||||
|
||||
Reference in New Issue
Block a user