Say a standing preference once. See it saved. Never say it again.
The bundle gives every Amplifier session on this device a small,
always-loaded file of how you work (~/.amplifier-memory/MEMORY.md), saved
to the moment you correct the assistant, undone with one command, explained
by git log. No database, no daemon, nothing at session end.
Read in this order: docs/VISION.v2.md, then the contracts in
contracts/ — store.v3 (the files), session.v4 (what
happens in a session), cli.v3 (the command), suggestions.v2 (Phase 2,
the daily inbox).
The store is an instance: a directory of plain text, its own git repository.
Which one a session uses is resolved in order — an explicit home from the caller
(the modules' config: home:, or --home), else $AMPLIFIER_MEMORY_HOME, else the
default ~/.amplifier-memory (store.v3 §1). A store made before v3 lives at
~/.amplifier/memory, and while that is the only one on the device it stays the
default, so nothing moves until init offers to move it. Beside the memories the
instance carries two files that are plumbing, not memory — never injected, never
suggested, never cited (§2): config.yaml, its own configuration, and
sessions.jsonl, one line per session seen. Set enabled: false in config.yaml and
the instance goes inert: nothing is injected, no tool is offered, no timer runs,
and every writer refuses in one line (§11).
The portable suggestion job does not require amplifier-app-cli. It declares
Core >=2.0.1, Foundation and its setup installer as dependencies. Use the public
index and require the published Core wheel:
uv tool install --default-index https://pypi.org/simple --no-build-package amplifier-core \
git+https://github.com/microsoft/amplifier-bundle-memory@main
amplifier-memory init --no-timer
amplifier-memory setup
amplifier-memory service install
amplifier-memory doctorinit asks the existing memory-seeding question and preserves an existing store.
setup reads shared and workspace provider settings, prepares the selected source
modules under this memory instance, and installs their dependencies in the standalone
tool environment. It makes no model call. Setup is explicit: suggestion runs never
install or refresh anything. Timer installation/start/restart refuses an unprepared
or changed runtime. After changing provider sources/settings or upgrading runtime
packages, run setup again before re-enabling the timer.
Setup uses Foundation's settings overlay in this order: shared
$AMPLIFIER_HOME/settings.yaml (otherwise ~/.amplifier/settings.yaml), workspace
.amplifier/settings.yaml, then .amplifier/settings.local.yaml. The chosen
workspace is recorded for the timer. Named accounts and their source/configuration
are preserved; source overrides come from sources.modules. Custom providers need
an explicit source. Only providers, hooks-routing, and
basic Core lifecycle modules are mounted. User tools, other hooks, workspace
instructions and recursive memory jobs are not loaded. An explicitly selected
arbitrary bundle or unsupported module override requires host-resolved inference
rather than silently selecting a different account.
Setup supplies routing-matrix automatically, using its balanced matrix by default.
It honors shared routing.matrix, routing.overrides, routing source overrides,
and custom matrices under the shared home's routing/. No routing entry is required
in settings.yaml. Setup previews the account and model for fast without a paid
completion. Interactive setup lets you keep that recommendation or choose from
configured accounts' model lists; use amplifier-memory setup --choose to request
that picker explicitly. Explicit choices are saved only in the memory instance's
config.yaml; existing choices and shared settings are preserved. Fast routing is
a curated recommendation, not a lowest-price guarantee. An unresolved role fails
visibly rather than using a potentially expensive default model.
Use --workspace /path/to/project only for additional workspace settings; it is
not the memory store location. The memory instance is selected with the global
--home option. See configuration and model provenance.
Generated state stays under the existing resolved memory home:
runtime/generations/ contains prepared module copies and source receipts;
runtime/jobs/<id>/ contains internal job metadata, safe usage events and the
private transcript. These files are ignored by Git and not read as memories or
new source conversations. Shared source histories and sessions.jsonl are never
rewritten. Provider-owned OAuth refresh stores remain under each provider's
configured ownership; the job does not relocate, clone or reset credentials.
Hosts such as Unified keep their existing mounted-provider inference path and manage their own component installations. Installing the session memory behavior in another host is a separate, optional host operation. Existing memory homes, including the legacy fallback, keep their current location; no migration is added.
Every verb acts on one instance, and --home <instance> names it — before or
after the verb, whichever reads better:
amplifier-memory status --home ~/work-memory
amplifier-memory --home ~/work-memory status # the same commandWithout it the instance resolves as $AMPLIFIER_MEMORY_HOME, else
~/.amplifier-memory. Each instance is an independent git repository with its own
config.yaml, its own inbox — and its own daily timer, whose unit name carries
the instance, so two instances never collide and neither can uninstall the
other's:
amplifier-memory service status # lists every instance timer on this device
amplifier-memory service uninstall --home ~/work-memorydoctor exits 0 when the store is healthy, and nonzero before step 3 has run.
To see the session plane itself working, start a session and say a standing
preference: it is saved in that turn and announced with its id and its undo.
To stop the daily pass without deleting memories:
amplifier-memory service uninstallAfter setup, amplifier-memory service install enables it again.
Both act on the instance --home resolves to. amplifier-memory service status
lists every installed instance timer, not only that one, so a timer you set up
for another instance is never invisible.
On a device set up before per-instance timers existed there is one un-instanced
amplifier-memory-suggest.timer, which runs whichever instance resolves as the
default. init and service install replace it with that instance's own timer —
disabling and removing the old pair, so exactly one timer serves the instance — and
print what they replaced. For any other instance it is left alone, and named as
serving the default only. Every unit name these commands print is a file in the unit
directory at the moment they print it.
In any session:
/remember never use tabs in YAML; two-space indentation
/memory
/memory forget m-017
/memory help
Where an Amplifier CLI offers slash-command argument discovery, Tab after
/memory can show these first-word choices. This is an optional display hint:
bare /memory remains the overview and typed arguments keep their current behavior.
Or just correct the assistant — it saves and the receipt reads:
saved m-017 — /memory forget m-017 to undo.
never use tabs in YAML; two-space indentation
your words, verbatim
From a shell: amplifier-memory status (what you wrote, kept and forgot,
plus how often a memory was actually cited) · amplifier-memory why m-017
(the commits behind one memory: the creation, each refinement as
was: → now:, and the forget if there was one) · amplifier-memory doctor
(health; never writes — and doctor --repair, the one exception, restores a
damaged MEMORY.md from the last clean commit and prints what it discards
first) · amplifier-memory review (pending suggestions) ·
amplifier-memory init · amplifier-memory update (alias upgrade) ·
amplifier-memory suggest and service (Phase 2, below).
amplifier-memory update upgrades the standalone tool, checks inference readiness,
restarts an installed timer only when ready, and runs doctor. Other hosts manage
their own runtime updates. Legacy CLI/cache maintenance remains an explicitly
requested library interoperability operation; ordinary update never discovers or
invokes amplifier. Existing sessions retain their loaded code until they end.
Reading memory leaves no commit behind: loads and citations are appended to
~/.amplifier-memory/usage.jsonl, which git does not track. Every commit in
the store is a change you made or approved.
Some standing preferences are said in the flow of work and never saved in the
turn. Once a day a timer runs amplifier-memory suggest, which reads
yesterday's recorded sessions, asks the model one question per session, checks
in code that every quote it gets back was really said by you, and proposes
the survivors. It never writes to MEMORY.md.
After explicit setup and service installation, the timer is a systemd user timer on Linux or a launchd agent on macOS. The remaining verbs:
amplifier-memory suggest # run the pass once, now
amplifier-memory review # walk the inbox, one keystroke each
amplifier-memory review --list # or just look
amplifier-memory service status # installed · enabled · last run · last outcome
amplifier-memory service uninstall # stop the daily pass; `service install` puts it back
On Linux, service commands recover a missing user-bus environment only when the
existing private /run/user/<uid> directory and its bus socket are owned by that
user. They do not change your shell, create runtime files, or enable lingering; if
that check fails, re-login and check the systemd user session (containers and WSL may
need host setup).
A proposal lives in ~/.amplifier-memory/inbox.md, two lines, with the words
you actually said and where you said them:
- [s-042] never use tabs in YAML; two-space indentation
quote: "never use tabs in YAML files I ask you to write…" session: bc214bdf 2026-09-05
accept writes the line through the same writer everything else uses — the
commit carries the quote, the session it came from, and writer: suggestion, so
amplifier-memory why m-NNN tells you where a memory came from months later.
decline appends the text to declined.md with the date, and it is never
proposed again (exact match, in code — reversal is deleting the line by hand).
skip leaves it. An item nobody reviews for 30 days is dropped, and counted
in the next run's report.
Nothing is proposed twice: a candidate is dropped when its text matches a
MEMORY.md line, a decline, or something already pending — and when its
verbatim quote matches a pending item or a memory you still have, since a model
that re-proposes something usually rewrites the text while quoting your sentence
word for word. The one case that still gets through: a suggestion you
declined and that comes back paraphrased, because declined.md records the
text and the date only, so there is no quote left to match it on.
Only sessions with you in them, and the test is two things at once:
- The session says it was started by a human. Every session records how it
began in the instance's
sessions.jsonl—human, orworker/recipe/agent/evalfor the ones a launcher started. Onlyhumanis read. A session with no record counts as human, so nothing is dropped for being unclassified; the filter sharpens as launchers setAMPLIFIER_SESSION_ORIGIN. Refusals are counted in the run's log line. - At least two of its turns are you actually typing, in the last 24 hours.
Two shapes are not typing, and both were measured mining the wrong thing on
the first timer night: a lane brief — a turn addressed to an agent, which
opens
Claim <id> from the <project> work-tracker project…— and a continuation turn that is only the harness's own<system-reminder>blocks. Neither reaches the judge, and a quote lifted out of one is rejected.
It still never reads a sub-agent's session, and never one it started itself.
What it costs: at most 30 model calls a day, one run a day, nothing resident —
the unit is Type=oneshot and only the timer starts it. Every run appends one
line to ~/.amplifier-memory/suggest.log — including the runs that proposed
nothing:
2026-09-06T09:00:04+00:00 sessions=3 origin_excluded=4 proposed=1 rejected=2 dropped_stale=0 calls=3 provider=luna model=gpt-5.6-luna status=ok
sessions= is what it read; origin_excluded=, beside it, is what it turned
away for not being a human's session. provider= names the model that was
billed — a provider id, role:<role>, or inherited.
Selection precedence:
- The explicit
provider/model/ host-resolvedbundlein this instance'sconfig.yaml. - Otherwise the configured routing module resolves the requested role (
fastby default). - Only an explicitly empty role can use the unique configured default account. Ambiguous or
unavailable selections fail visibly; an unresolved
fastrole never falls back.
enabled: true
llm:
judge:
role: fast
provider: "" # optional configured named account
model: "" # optional exact model
bundle: "" # requires host-resolved inferenceProvider configuration retains explicit credential placeholders such as ${OPENAI_API_KEY};
process environment takes precedence over shared keys.env when resolving placeholders.
The dedicated amplifier-memory suggest process also loads missing shared keys into
its own environment for providers using implicit environment credentials. Embedding
hosts use run_suggest/complete_once; those APIs never change process environment.
No credential values are written to runtime receipts or usage logs. Requests have no tools,
no automatic retry, a 4096 output-token cap, and no fixed healthy-call completion deadline.
The job records actual provider/model and measured usage; missing cost remains unknown.
Historical evaluation costs below are observations, not current account estimates.
What the measurements say (7 model variants, 210 real calls, evaluations/model-class/):
- The judging task does not need a large model. 6 of 7 variants returned perfect recall and verbatim quotes; the one shape failure and the one false positive in 210 calls were both caught in code before anything reached the inbox.
gpt-5.6-lunaatlowreasoning or above was clean at $0.02/call — $0.60 a day at the 30-call ceiling, against $0.276/call for the large class. Without an OpenAI-backed provider, a mid class (sonnet) is the equivalent.- Reasoning effort belongs to the provider entry, not here. It is
config.providers[].config.reasoning_effortin your amplifier settings, so "luna at low" means an entry named e.g.luna-low, andprovider = "luna-low"above. Turning reasoning off costs precision: atnone, four task instructions in ten were proposed as standing preferences.lowand up returned that to zero. minimalis refused by this endpoint's gpt-5.6 models at request time even though the provider accepts it at mount. A user who sets it sees every sessionrejectedand the run exit 0 — nothing breaks, and nothing is proposed.
A file that cannot be read — a typo, a broken table — never costs you the night's run: the pass says so in its log line, inherits the CLI default, and carries on.
If the session capture is missing, or the model is unavailable, or the reply
comes back malformed, the run records that and exits 0. Nothing is written to
the inbox, and amplifier-memory doctor shows the degraded state on its
suggest timer and substrate rows. The pass depends on the
context-intelligence bundle's local session capture
(~/.amplifier/projects/); Phase 1 does not.
amplifier-memory status after a week of real use shows at least five
memories you kept. If it does not, that is the bug report.
Note
This project is not currently accepting external contributions, but we're actively working toward opening this up. We value community input and look forward to collaborating in the future. For now, feel free to fork and experiment!
Most contributions require you to agree to a Contributor License Agreement (CLA) declaring that you have the right to, and actually do, grant us the rights to use your contribution. For details, visit Contributor License Agreements.
When you submit a pull request, a CLA bot will automatically determine whether you need to provide a CLA and decorate the PR appropriately (e.g., status check, comment). Simply follow the instructions provided by the bot. You will only need to do this once across all repos using our CLA.
This project has adopted the Microsoft Open Source Code of Conduct. For more information see the Code of Conduct FAQ or contact opencode@microsoft.com with any additional questions or comments.
This project may contain trademarks or logos for projects, products, or services. Authorized use of Microsoft trademarks or logos is subject to and must follow Microsoft's Trademark & Brand Guidelines. Use of Microsoft trademarks or logos in modified versions of this project must not cause confusion or imply Microsoft sponsorship. Any use of third-party trademarks or logos are subject to those third-party's policies.