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
2 changes: 1 addition & 1 deletion .claude-plugin/plugin.json
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
{
"name": "vta-agent-memory",
"description": "Durable agent memory stored in your own Verifiable Trust Agent, not in the tool. Save and recall facts across sessions, scoped to a VTA trust context you control and can revoke.",
"version": "0.4.0",
"version": "0.5.0",
"keywords": [
"memory",
"vta",
Expand Down
5 changes: 5 additions & 0 deletions CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ src/
store.rs typed ops over vta/memory/* — the only VTA-facing code
record.rs the record shape, key encoding, and recall ranking
authorship.rs signing records on save, verifying author + location on read
provenance.rs basis / confidence / evidence: vocabulary, checks, rank weight
server.rs the six MCP tools
tests/
memory_roundtrip.rs Store end-to-end against an in-process fake VTA
Expand Down Expand Up @@ -172,6 +173,10 @@ longer sends, returned the whole context with no prefix at all.) Everything in
(`MemoryKey::raw_key_has_type` / `Store::list_of_type`).
- Recall returns `summary()` (key, name, type, description), never `full()`.
Bodies come from `memory_get` alone.
- `rank()` multiplies relevance by `provenance::weight(basis)`. Never rank on
`confidence`: the rooms schema it comes from says a reader MUST NOT compare it
across authors. Keep `basis`/`evidence` as strings/raw values in the record —
a typed field would make a newer writer's record undecodable.
- `rank()` is weighted token containment. Deliberately crude; see its doc
comment before replacing it.

Expand Down
2 changes: 1 addition & 1 deletion Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[package]
name = "vta-agent-memory"
version = "0.4.0"
version = "0.5.0"
edition = "2024"
rust-version = "1.95.0"
description = "Agentic memory for Claude Code, stored in a Verifiable Trust Agent"
Expand Down
8 changes: 8 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -211,6 +211,14 @@ context in key order and narrows by key prefix, but does not search. So:
- Ranking is weighted token containment (name 4, description 2, body 1). Crude
and predictable, chosen over embeddings because the VTA offers no vector index
and a scoring function nobody can predict is worse than one everybody can.
- Relevance is then weighted by **basis** — what kind of claim the memory is:
`declared` (the user said it) and evidenced `verified` count fully, `observed`
0.9, `unstated` (pre-provenance records) 0.8, `inferred` and evidence-less
`verified` 0.6. A nudge, not an override: a strong match on an inference still
beats a passing mention of a statement. `confidence` is shown but never ranked
on — two authors' estimates are not on one scale. The vocabulary (`basis`,
`confidence`, `evidence`) is the one Data Rooms publish, so a record moved
between memory and a room keeps its meaning.

Cost is a full read of the context per recall. Fine at the hundreds of memories a
person accumulates. If that stops being true, the fix is upstream — a
Expand Down
3 changes: 3 additions & 0 deletions commands/remember.md
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,9 @@ Follow the `agent-memory` skill. In particular:
of a session?" — recall ranks on it.
4. For `feedback` and `project`, put the **why** in the body.
5. Convert relative dates to absolute ones.
6. State the `basis`. `/remember` is usually the user telling you something, so
it is usually `declared` — but if what you are saving is your own reading of
what they said, it is `inferred`.

If the argument is empty, look at what has happened in this session and propose
what is worth remembering — then ask before saving. If the thing to remember is
Expand Down
24 changes: 24 additions & 0 deletions skills/agent-memory/SKILL.md
Original file line number Diff line number Diff line change
Expand Up @@ -149,6 +149,30 @@ Keep the essentials in the body and save a pointer to where the rest lives.
If the user asks you to remember something in one of those categories, say so in
a sentence and offer the alternative rather than saving it anyway.

## Say what kind of claim it is

Every save states a `basis`, and it is signed with the memory, so it cannot be
quietly upgraded later. Get it right the first time:

| Basis | When |
|---|---|
| `declared` | The user said it — "I prefer tabs", "we deploy on Thursdays". |
| `observed` | You saw it happen — a command failed, a file exists, CI is red. |
| `verified` | Checked against something citable. **Must** cite it in `evidence`: a credential id, another memory's key, or a data-room record. |
| `inferred` | Your own conclusion — "seems to prefer short answers". **The default when the user did not say it.** |

`confidence` (0–1) is optional and only meaningful for `observed` and
`inferred`. Be honest rather than generous: an inference saved as `declared`
becomes, in every later session and for every other agent, something the user
supposedly said.

Recall shows the basis of each result, ranks `inferred` below an equally
relevant `declared` memory, and reads a `verified` memory that cites nothing as
an inference (`basisNote` says so). When a stated memory and an inferred one
disagree, the stated one wins; when an inferred memory is all you have, treat it
as a hypothesis and confirm it before relying on it. `unstated` means the memory
predates this field.

## Before saving

Recall first. If a memory already covers it, update that one — `memory_save`
Expand Down
1 change: 1 addition & 0 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ pub mod enrol;
pub mod fence;
pub mod lazy;
pub mod pnm;
pub mod provenance;
pub mod record;
pub mod server;
pub mod setup;
Expand Down
27 changes: 24 additions & 3 deletions src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -541,8 +541,12 @@ fn render_memories(
"\n- **{}** (`{}`) — {}",
e.record.name, e.key, e.record.description
);
if let Some(note) = e.authorship.note() {
item.push_str(&format!(" _[{note}]_"));
let notes: Vec<String> = [e.record.basis_note(), e.authorship.note()]
.into_iter()
.flatten()
.collect();
if !notes.is_empty() {
item.push_str(&format!(" _[{}]_", notes.join("; ")));
}
if full && !e.record.body.is_empty() {
item.push_str(&format!("\n\n {}", e.record.body.replace('\n', "\n ")));
Expand Down Expand Up @@ -657,7 +661,11 @@ mod tests {
fn entry(kind: MemoryType, name: &str, desc: &str, body: &str) -> Entry {
Entry {
key: MemoryKey::new(kind, name).unwrap(),
record: MemoryRecord::new(kind, name, desc, body, vec![]),
record: MemoryRecord::new(kind, name, desc, body, vec![]).with_provenance(
vta_agent_memory::provenance::Basis::Declared,
None,
vec![],
),
authorship: vta_agent_memory::authorship::Authorship::Unattributed,
}
}
Expand Down Expand Up @@ -720,6 +728,19 @@ mod tests {
"{out}"
);
assert!(line("Old").contains("_[unattributed]_"));
let guessed = Entry {
record: MemoryRecord::new(MemoryType::User, "Guess", "d", "", vec![]).with_provenance(
vta_agent_memory::provenance::Basis::Inferred,
Some(0.6),
vec![],
),
..mine.clone()
};
let inferred = render_memories("proj", vec![&guessed], &[], false, None);
assert!(
inferred.contains("_[inferred, confidence 0.60]_"),
"{inferred}"
);
assert!(out.contains("## refused"));
assert!(out.contains("`feedback/deploys` — its signature does not verify"));

Expand Down
Loading
Loading