Skip to content
Merged
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
20 changes: 20 additions & 0 deletions docs/architecture/rfcs/goal-scoped-capability-portfolio-v0.md
Original file line number Diff line number Diff line change
Expand Up @@ -526,6 +526,26 @@ settings editor already owns these configurations and their readback. Live
memory/usage/correction controls and Lark evidence returns remain delivery work,
not acceptance supplied by CLI tests.

The explicit Agent preference slice now implements a narrow part of the
correction journey: the existing `semantic-preference` capability owns a
TS lifecycle backed by the shared transactional storage implementation in an
isolated private context namespace. Current explicit preferences have stable
subjects, source references, revisions and retirement/expiry; CLI quota and
native Turn entry points disclose an exact current read. This is not a universal
memory database, an authorization grant or proof of learned-experience utility.
See [operation and boundaries](../../../loopx/capabilities/semantic_preference/README.md#explicit-agent-preferences).
Remaining acceptance includes authenticated external-message ingestion,
Dashboard/Lark inspection, cross-host migration, memory-service provider
qualification (including OpenViking, beyond interchangeable storage drivers),
and measured decision utility
for episodic recall. Reuse the existing semantic-preference extension lifecycle:
project-scoped recall is already available, while explicit Goal/Agent correction,
retirement and provider cutover need their own real-service readback. A top-k
recall response does not qualify as a complete current preference view; service
outages or delayed indexing must not resurrect revoked advice. The
[memory-service acceptance boundary](../../../loopx/capabilities/semantic_preference/README.md#memory-service-providers-and-the-next-integration-boundary)
defines the next independent slice. Do not count this slice as completion of all M0–M5.

Next, qualify one **correction → fresh-session decision** journey before adding
a composition planner: persist an authorized correction, show the affected
source and superseded assertion, resume a different session, retrieve the
Expand Down
195 changes: 193 additions & 2 deletions loopx/capabilities/semantic_preference/README.md
Original file line number Diff line number Diff line change
@@ -1,15 +1,19 @@
# Semantic preference hook
# Semantic preferences

For the built-in OpenViking project-scoped adapter, see
[OpenViking project peer provider](docs/openviking-project-peer.md).

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 hook is disabled unless a caller supplies an enabled local-private JSON
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.
Expand Down Expand Up @@ -208,3 +212,190 @@ receipt = application_receipt(
)
# 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.

### 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](docs/openviking-project-peer.md) 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](https://docs.openviking.ai/en/api/16-memory) 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](https://docs.openviking.ai/en/api/05-sessions) and
[file-system API](https://docs.openviking.ai/en/api/03-filesystem) 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

```bash
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 |

```bash
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

After any preference has been committed, CLI quota admission and native Turn
planning/execution disclose a required owner-local read through the existing
turn-start hook. Every required read and its exact executable command survive
Turn compaction, including long quoted paths and more than five hooks; size
excess remains a diagnostic rather than permission to drop obligations.
Counts and the read command enter the turn envelope; private
statements and source quotes do not enter generic quota/status or public sinks.
Discovery uses a content-free TS observation of the exact Goal/Agent scope.
A missing store leaves the entire hook projection unchanged even when another
Goal or Agent has preferences in the same runtime. Retired/expired records still
require a fresh read; an unreadable store is unavailable, never absence.

The command reads all current scoped entries, including retired/expired markers,
instead of relying on embedding or keyword ranking to find a prohibition.

The managed `/loopx` instructions 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. Native hosts must execute the disclosed read, and 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](https://docs.langchain.com/oss/python/concepts/memory)
distinguishes procedural, semantic and episodic memory and namespaced long-term
state. [Letta blocks](https://docs.letta.com/v1-sdk/memory/memory-blocks) illustrate
bounded always-visible working context, separate from archival search.
[Zep temporal search](https://help.getzep.com/searching-the-graph) distinguishes
when facts are valid from when the system learned or invalidated them.
[Mem0 Dream](https://docs.mem0.ai/platform/features/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.
Loading
Loading