Semantic preferences¶
For the built-in OpenViking project-scoped adapter, see OpenViking project peer provider.
LoopX supports explicit local Agent preferences and optional provider recall. They have different update rules: explicit preferences use the lifecycle below; provider-owned experiences keep their existing provider and application contract.
LoopX can optionally recall semantic preferences before a domain action and build a compact application receipt afterwards. The hook is deliberately thin: the provider owns storage, ranking, and semantic content; the caller owns how a preference affects its output and writes the receipt through existing LoopX evidence or state surfaces.
The external recall hook is disabled unless a caller supplies an enabled local-private JSON config. Config files inside a git project must be ignored; tracked configs are rejected. LoopX never copies the provider command, config path, recalled semantic content, or raw provider errors into receipts.
The preferred provider path is an explicitly activated extension. The
compatibility path still accepts a direct subprocess argv; both paths use the
same core request, response, failure-policy, and receipt contracts.
Module-owned surfaces¶
surfaces is a mapping keyed by arbitrary module-qualified ids. The runtime
does not branch on issue_fix, content_ops, or any other domain name.
{
"schema_version": "semantic_preference_hook_config_v0",
"enabled": true,
"provider": {
"id": "local_memory",
"args": ["--project", "."]
},
"surfaces": {
"issue_fix.pr_description": {
"query": "PR description structure and reviewer language preferences"
},
"content_ops.draft_language": {
"query": "Draft language and section preferences",
"limit": 3
}
}
}
LoopX resolves the installed provider from the semantic-preference capability
and semantic_preference_provider_v0 protocol in runtime state. args are
appended after the manifest-owned entrypoint arguments. The manifest owns
protocol, permission, timeout, and doctor; config cannot override them.
extension_id remains an optional compatibility selector when migrating an
existing config or disambiguating multiple installed implementations.
extension_state_file is an optional local-private override for tests or
specialized embeddings; the CLI's global --runtime-root selects the normal
isolated runtime. If an activated extension is later disabled or unavailable,
recall follows the surface's existing fail_open or fail_closed policy.
For a legacy provider that has not adopted the extension manifest, replace the provider object with:
{
"id": "local_memory",
"argv": ["semantic-preference-provider"],
"timeout_seconds": 30,
"probe_argv": ["semantic-preference-provider", "doctor"]
}
argv and extension_id are mutually exclusive. Omitting both selects the
unique installed extension implementation from runtime state.
A domain module owns the surface id, query, context keys, and decision about how recalled items influence its output. Adding another module is a config change, not a LoopX runtime change.
Provider protocol¶
On recall --execute, LoopX sends one
semantic_preference_provider_request_v0 JSON object on stdin. A provider
returns one semantic_preference_provider_response_v0 object on stdout:
{
"schema_version": "semantic_preference_provider_response_v0",
"items": [
{
"preference_ref": "provider-owned-reference",
"summary": "Use concise Chinese sections for this surface."
}
],
"corpus_inventory": [
{
"corpus_id": "project_preferences",
"scope_ref": "provider-owned-scope-reference",
"read_role": "primary",
"write_mode": "provider_managed",
"write_actor_ref": "provider-owned-actor-reference",
"source_of_truth": "repository_revision_and_explicit_feedback",
"writeback_triggers": ["explicit_feedback", "source_truth_changed"],
"closure_policy": "write_wait_l2_read_scoped_recall"
}
]
}
corpus_inventory is optional and provider-neutral. It describes which bounded
corpora contributed to the recall and what closes a maintenance decision; it
does not contain raw memory. LoopX validates the inventory and derives
semantic_preference_maintenance_guidance_v0. A fixed function boundary can
therefore expose the corpus ids, writeback triggers, and closure policy in the
same provider call instead of relying on the agent to remember a separate
runbook. Providers that omit the field remain compatible.
An explicit feedback or source-of-truth change does not imply that every corpus
must be rewritten. The caller either performs the provider-owned update and
verifies the configured closure policy, or records a no_write_rationale.
LoopX does not infer semantic updates, mirror provider storage, or turn a soft
preference into an execution permission.
Provider stderr and non-zero output are reduced to a bounded failure kind.
fail_open returns no items and lets the domain continue; fail_closed stops
the caller with an actionable error. Provider failures do not become user
gates automatically.
provider.id and setup_hints are optional. Legacy probe_argv must be a
read-only health check owned by the provider. Extension providers use the
manifest doctor instead. Neither doctor path installs packages, starts
services, changes config, or writes credentials; setup hints remain guidance
for an explicit operator action.
CLI¶
loopx semantic-preference recall \
--project . \
--config <ignored-config.json> \
--surface issue_fix.pr_description \
--context repository=owner/repo \
--execute
loopx semantic-preference doctor \
--project . \
--config <ignored-config.json> \
--execute
loopx semantic-preference receipt \
--surface issue_fix.pr_description \
--application-id pr-123-description-v2 \
--outcome applied \
--preference-ref <provider-owned-reference> \
--artifact-ref https://github.com/owner/repo/pull/123
loopx semantic-preference maintenance-receipt \
--trigger source_truth_changed \
--outcome verified \
--corpus-id project_preferences \
--scope-ref <provider-owned-scope-reference> \
--evidence-ref project-preference-readback-v2
Receipts contain only surface, application id, outcome, optional public
artifact reference, and hashes of provider-owned preference references. The
command returns the receipt without writing a file. Callers can attach it to
the existing evidence log, todo evidence, or refresh-state record; the hook
does not maintain a second reward or memory ledger.
Maintenance receipts are also stateless. They contain only the trigger,
outcome, corpus ids, optional compact evidence reference, and hashes of scope
references. A verified outcome means the provider-specific write, queue or
index wait, direct read, and scoped recall required by the inventory have all
passed. A no_write_rationale outcome records that the trigger was assessed
but no durable semantic change was needed.
--context is repeatable and each entry uses lower_snake=value syntax.
Invalid config, context, surface, or fail-closed requests return a structured
semantic_preference_error_v0 payload with exit code 2 instead of a Python
traceback.
Domain integration¶
For reviewed reward-memory records, Stage 3 also exposes
run_semantic_preference_reward_memory. The caller supplies the exact corpus,
module-owned surface, query steps, read-authority checkpoint, provider binding,
and model application callback. The shared reward-memory core performs the
scope/freshness/conflict guards and returns a compact receipt; this module does
not add another store, router, or scheduler. Function-boundary mode permits one
query, while bounded agentic mode permits at most three caller/model-authored
queries.
from loopx.capabilities.semantic_preference import application_receipt, recall
preferences = recall(
config_path,
project=project_root,
surface="issue_fix.pr_description",
execute=True,
)
# The same result identifies provider-owned corpora that must be assessed after
# explicit feedback or a source-of-truth change.
guidance = preferences.get("maintenance_guidance")
# The issue-fix module decides whether and how to apply preferences["items"].
receipt = application_receipt(
surface="issue_fix.pr_description",
application_id="pr-123-description-v2",
outcome="applied",
preference_refs=[item["preference_ref"] for item in preferences["items"]],
)
# Write `receipt` through an existing LoopX evidence/state surface.
Explicit Agent preferences¶
Use semantic-preference agent for an owner's durable instructions about how
an Agent should work. This is advisory context, not a permission store,
Goal configuration, capability enablement or an automatic execution engine.
Existing authorization and exact-head review rules still govern actions.
The local trusted caller attests the user instruction; a source reference is
provenance, not cryptographic authentication of the speaker.
The built-in local provider reuses AuthorityStore transactions, conditional
revisions and retained history. TypeScript owns validation, replacement,
retirement, expiry and replay. Python only resolves the registered Goal and
adapts CLI/host inputs. This store is private runtime context, separate from
Goal/Todo authority and its selected File/SQLite/PostgreSQL provider. It needs
no optional memory service. The lifecycle is also tested against real SQLite;
this does not introduce a new user-selectable memory backend or claim remote
memory synchronization.
The exact scope is runtime + resolved Goal state file + Goal instance (when present) + Goal id + Agent id. Global/project registry aliases pointing at the same Goal share preferences; another Goal or Agent does not inherit them. No copying host conversations, cross-home rebinding or implicit global corpus. Moving the Goal state file requires an explicit context migration; it is not silently treated as the same private scope.
Preference guidance belongs to the participating hook¶
The generic /loopx skill no longer teaches preference discovery, reads or
corrections. Shared work-context guidance stays capability-neutral. Preference
instructions accompany the capability-owned current view through its turn-start
hook, so Agents that never use local preferences do not receive this recipe or
an extra preference-read obligation.
Local participation retains the existing explicit-use boundary: the first
semantic-preference agent remember --execute commits a journal for the exact
Goal/Agent scope. A read or preview does not activate it. Another scope's journal
does not opt this scope in. With no namespace, quota/Turn skips the preference
provider; with no exact-scope journal it leaves the hook projection unchanged.
This local lifecycle is separate from the external recall enabled setting;
turning external recall off does not erase explicitly committed local preferences.
For participating scopes, a single capability-owned snapshot supplies both observation and current bodies. Each guard rereads without a negative cache, so corrections, retirements and expiry appear in the next guard. Retired/expired markers remain visible to invalidate cached advice; an unreadable journal is unavailable, not inactive or empty. Permission denial remains distinguishable. Recover the source before preference-dependent work; independent work keeps its existing authority.
The delivered view includes the operating rules previously in the generic skill:
consume it once for this guard's pre-work checks, then execute any remaining
required_reads. Before every preference-dependent external action, obtain a
fresh exact-scope view through a new guard or explicit agent read, even after
an earlier empty or current view. Read explicitly before a durable correction;
reuse the subject key, current revision, stable operation identity and exact user
source, then preview, execute and read back. Missing hook context is unknown, not
an instruction to discover preferences. An explicit empty read clears cached
advice; neither context nor its absence grants authority.
Memory service providers and the next integration boundary¶
The extension boundary is a memory service, not just a File/SQLite driver. A provider such as OpenViking may own extraction, semantic organization, retrieval and corpus maintenance. The existing OpenViking extension already implements optional project-peer recall. Keep its extension installation, permission, doctor and configuration lifecycle; do not create another provider registry. The built-in preference journal is one zero-service implementation of explicit current context, not the mandatory backend for every future memory service.
Distinguish the operations callers actually need:
| Caller outcome | Provider obligation |
|---|---|
| Recall relevant experience | Scoped ranked candidates, source references and bounded context; a missing hit does not prove deletion. |
| Read current explicit preferences | Complete current subjects and retirement/expiry markers for the exact scope, with a freshness/revision result; top-k search alone cannot implement this. |
| Correct or retire a preference | Acknowledge the identified subject and new revision, preserve source/lineage, reject stale writes and make uncertain retries observable. |
| Change the memory service | Preserve scope and stable subjects, verify current state and retirements, and cut over one binding after readback; do not silently create an empty corpus. |
These are acceptance requirements for extending the existing service protocol,
not newly shipped operations of the current OpenViking recall adapter. A
service adapter must not be forced to implement LoopX's entire AuthorityStore:
that interface is the built-in journal's persistence implementation. Provider
integration belongs at the caller's memory operation boundary. The common TS
layer owns response validation, exact scope and application of current user
corrections; provider-specific code owns transport, URI mapping, extraction and
index readiness. No hard-coded OpenViking URI belongs in the host prompt.
OpenViking's memory API separates
extraction from retrieval; its current documentation directs context recall to
search(mode="context"). The LoopX adapter currently uses find, so version
negotiation and deployment-specific qualification remain necessary. Its
session API and
file-system API provide useful
update/deletion surfaces, but those docs alone do not establish LoopX's
concurrent correction or fresh-session contract.
Before qualifying it for explicit Agent preferences, bind the authenticated provider namespace to the selected Goal/Agent (the existing project peer is broader), then prove correction → extraction/index completion → direct read → scoped recall → fresh-session use on a real isolated service. Include a stale index returning the old statement, delayed/failed deletion, concurrent updates, uncertain write retry and provider unavailability. Retirement must suppress old advice immediately at the application boundary; an adapter that cannot attest fresh current state remains recall-only. It must not become the current-state owner merely because it can answer a search query.
A later binding change needs an explicit, backed-up migration with retirement and revision readback. Do not send existing private preferences to a service just because its extension is installed. This PR does not activate OpenViking, export private memory, or claim interchangeable memory services are delivered.
Read, remember, correct and retire¶
loopx semantic-preference agent read --goal-id demo --agent-id author --format json
# A truly empty store reports revision=null. Otherwise pass the exact revision.
loopx semantic-preference agent remember --goal-id demo --agent-id author \
--key review.collaboration --statement 'Ask the designated reviewer before merging.' \
--source-ref owner-message-1 --source-quote 'Use the designated reviewer for my changes.' \
--expected-revision none --operation-id preference-1
# Inspect the preview, then repeat exactly with --execute and read back.
Keys name stable subjects, not individual messages. A correction reuses
review.collaboration, a new operation id, and the revision from a fresh
read. --expires-at optionally bounds validity with a UTC ISO timestamp.
The CLI does not classify natural language or run an LLM. The host interprets
an explicit user message and calls this typed transition; retrieved documents
and model-generated lessons are not admissible write sources.
| User intent | Update | Next fresh decision |
|---|---|---|
| Use the designated reviewer from now on | Remember the collaboration preference | Read and apply it under current authority |
| Stop asking a reviewer | Replace the same key with the negative preference | Do not act on the superseded positive preference |
| This time skip the reviewer | Keep durable preference; use the current task exception | Later tasks still read the durable preference |
| Forget this preference | Retire the same key | Tombstone invalidates cached guidance; no older value is resurrected |
| Here is an example: “stop asking a reviewer” | No update | Quotation is not a user correction |
loopx semantic-preference agent retire --goal-id demo --agent-id author \
--key review.collaboration --source-ref owner-message-2 \
--source-quote 'Forget my review preference.' \
--expected-revision '<fresh read revision>' --operation-id preference-2 --execute
loopx semantic-preference agent history --goal-id demo --agent-id author --format json
History is paginated (--after-cursor), preserves sources and predecessor
operation ids, and is not injected into the action context. Retirement is not
physical erasure: older statements remain in private history/backups. There is
no automatic upload. Back up the private runtime directory with normal host
backups; do not publish its journal or include it in public fixtures.
Uncertain writes retry the same operation id and exact original arguments. Replay returns the old receipt with the current view, never rewrites its old projection. Stale revisions and changed same-id requests are explicit conflicts; read and reconcile before proposing a new operation. Do not blindly retry with a newly fetched revision, which would hide a concurrent correction.
Fresh-turn adoption¶
CLI quota and native Turn share the
participating hook contract
above. Native hosts consume delivered current bodies and execute remaining
required reads. Every remaining read and its exact executable command survive
Turn compaction, including long quoted paths and more than five hooks; size
excess does not authorize dropping obligations. Scoped quota/Turn work_context
may carry owner-private bodies; generic status and public sinks do not receive
private statements or source quotes.
The command reads all current scoped entries, including retired/expired markers, instead of relying on embedding or keyword ranking to find a prohibition.
The participating hook
delivers current.instructions that teach the host to persist explicit
corrections, read them back, and re-read before a preference-dependent external action.
A new user instruction overrides old context immediately, including while a
write is being recovered. An unreadable store is unavailable, not empty. The shared Turn capsule signs
failed/partial/unavailable hook observations and carries them into the execution
host's authority packet, including producer/contract failures that produced no
read command. It invalidates the affected hook's cached context and holds only
actions dependent on missing context; independent work keeps its existing
permissions. Healthy/disabled hooks do not add this field. This failure-policy
projection also applies to other turn-start hooks; it does not grant capability
permissions, make a whole Goal unavailable, or retry failed providers in a loop.
Do not
act on a cached preference; independent work can continue. This is a host
obligation, not a claim that a generic memory engine intercepts every tool
call atomically. Ordinary unmanaged conversations have no automatic hook.
The bounded current view accepts at most 64 subject keys and 32 KiB of records.
Capacity failure rejects the write; it never silently evicts an old constraint.
Expired/retired statements are not actionable. A later remember of a retired
key needs a fresh explicit user source and current revision. Journal retention
and physical erasure are separate work, not implemented by retire.
No frontend configuration editor is added: this slice is owner-local CLI/host context, not Goal settings. Dashboard/Lark memory inspection and authenticated message ingestion remain outside this slice; external messages must not be silently promoted to preferences by a display surface.
Design evidence¶
The design deliberately separates explicit instructions from probabilistic experience recall. LangGraph distinguishes procedural, semantic and episodic memory and namespaced long-term state. Letta blocks illustrate bounded always-visible working context, separate from archival search. Zep temporal search distinguishes when facts are valid from when the system learned or invalidated them. Mem0 Dream retains superseded memories and offers latest-only filtering. These are documented mechanisms, not comparative performance evidence. Here explicit correction commits synchronously, preserves history, and deterministic current-state reading excludes superseded instructions from action guidance; it does not wait for background consolidation or a relevant search hit.