Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion hooks.lock
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,6 @@
# no hooks.json here and nothing generates one; tools/generate.py refuses this host by
# name for exactly that reason.
repo=memvara/memvara
sha=eb25ea028e9f70372d2da7903215636a40df320a
sha=8f7545ead4814e2a81c1c6a05d0391cb65343443
path=plugin/hooks
host=opencode
32 changes: 26 additions & 6 deletions hooks/lib/standing.py
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,14 @@
#: The server's word for "a machine derived this". Matched as a bracket field.
_INFERRED = "inferred"

#: What `memory_standing` is asked for on a hosted deployment, instead of the tool's own
#: default of 64. The deployment truncates BEFORE this module orders or filters anything,
#: and a store measured at 169 standing claims was handing back 64 of them chosen by the
#: server. 200 is the most memvara-cloud's `GET /v1/standing` accepts (`le=200` on its
#: `limit`); a larger number is refused there, not clamped, so this is a ceiling and not
#: a wish. Only the hosted route reads it: the local route holds the whole set already.
HOSTED_STANDING_MAX = 200

#: What a marked row ends with. The library's own spelling, so a reader who has seen one
#: block has seen both. The extractor's NAME is deliberately never rendered here or
#: upstream: it is caller-supplied through `memory_remember`, so printing it would put
Expand Down Expand Up @@ -137,7 +145,10 @@ def _mine(subject: str, cwd: str) -> bool:
"""
if subject == "user":
return True
return bool(cwd) and subject == f"project:{cwd}"
# The namespace folds case, the way every typed entity does in the library
# (`Claim.subject_type`); the path does not, because paths do not.
namespace, sep, path = subject.partition(":")
return bool(cwd) and bool(sep) and namespace.lower() == "project" and path == cwd


def _machine_wrote(claim: Any) -> bool:
Expand Down Expand Up @@ -251,7 +262,7 @@ def _from_tool(store: Any) -> "list[Note] | None":
call = getattr(store, "_call", None)
if not callable(accepts) or not callable(call) or not accepts("memory_standing", "k"):
return None
return _rows(str(call("memory_standing", {}) or ""))
return _rows(str(call("memory_standing", {"k": HOSTED_STANDING_MAX}) or ""))


def _from_since(store: Any) -> "list[Note] | None":
Expand All @@ -269,7 +280,14 @@ def _from_since(store: Any) -> "list[Note] | None":


def _order(notes: "list[Note]") -> "list[Note]":
"""Most-trusted first, then newest, then by id so the order is total.
"""Stated first, then most-trusted, then newest, then by id so the order is total.

Stated before trusted, because confidence is written by whoever wrote the claim and
a model writes its own. Measured on the real store: one extractor filed every
paraphrase it derived at 0.84 to 1.00 and the capture hook filed the user's own
sentence at 0.70, so seven machine restatements of one rule sat above the sentence
the user typed, and the budget cut off below them. The server's `memory_standing`
sorts the same way; this is the same rule on the route that parses rows.

The id tiebreak is not decoration. Without a total order two claims written in the same
instant swap places between runs, and a block that differs run to run is a block whose
Expand All @@ -279,9 +297,11 @@ def _order(notes: "list[Note]") -> "list[Note]":
stand-in value would sort real claims against a number nobody measured.
"""
if any(note.confidence is None for note in notes):
return list(notes)
return sorted(notes, key=lambda n: (-(n.confidence or 0.0), _negated(n.recorded),
n.ident))
# Stated before derived is still knowable without a number: the marker is on
# the row. Stable, so within each half the server's own order stands.
return sorted(notes, key=lambda n: n.inferred)
return sorted(notes, key=lambda n: (n.inferred, -(n.confidence or 0.0),
_negated(n.recorded), n.ident))


def _negated(stamp: str) -> str:
Expand Down
Loading