prompts re-derived from scratch: inclusive block ids, honest terminators, orders with recovery
- Block ids are inclusive everywhere (16-31 = memories #16..#31), matching what the wake document prints. Before, wake showed #16-31 but sleep and forget demanded 16-32: an agent copying its own document was rejected. - Only a successful wake says 'You are awake.' Sleep ends with 'Nothing left to compress.' Before, the final sleep of a refused wake claimed the agent was awake when it had read zero memories. - An empty store's wake also ends with 'You are awake.' (it never did, so a fresh agent could not satisfy its own instructions). - Wake's refusal closes the loop: 'Do them, then run memo wake again.' - Nap prompt names its object first and labels merged halves with their ids. - Every error ends with the recovery command. - README synced to reality (it still quoted the pre-terseness prompts, TREE.txt, and 'four commands').
This commit is contained in:
parent
af17f94c2f
commit
1f1ec1511e
3 changed files with 132 additions and 104 deletions
112
README.md
112
README.md
|
|
@ -3,7 +3,7 @@
|
|||
A permanent memory for AI agents. One machine holds one identity that survives
|
||||
every new session, every compaction, and every change of model or vendor.
|
||||
|
||||
It is two append-only text files and four commands. No daemon, no database, no
|
||||
It is a handful of append-only text files and six commands. No daemon, no database, no
|
||||
API, no integration with any particular agent harness — it works the same under
|
||||
Claude Code, Codex, pi or a human at a shell.
|
||||
|
||||
|
|
@ -35,24 +35,29 @@ export MEMORY_DIR="$HOME/memory" # required; there is no default
|
|||
## Use
|
||||
|
||||
```sh
|
||||
memo wake # who you are. run this first, every session,
|
||||
# then the command each part names, until
|
||||
# one of them prints `You are awake.`
|
||||
memo wake # read your memory. run this first, every session,
|
||||
# then the command each part orders, until one
|
||||
# prints `You are awake.`
|
||||
memo note "..." # record a memory. one line, <= 280 chars.
|
||||
memo sleep # compress. keep going until it says you woke up.
|
||||
memo sleep # compress. answer each prompt until it prints
|
||||
# `Nothing left to compress.`
|
||||
memo recall <regex> # search the raw log for detail a summary lost.
|
||||
memo forget <lo>-<hi> # drop a wrong summary; the next sleep redoes it.
|
||||
```
|
||||
|
||||
```
|
||||
$ memo note "OptMem: LOG.txt is the truth, TREE.txt is the cache, wake reads both"
|
||||
ok, memory #4213.
|
||||
$ memo note "OptMem: LOG.txt is the truth, TREE/ is the cache, wake reads both"
|
||||
Saved as #4213.
|
||||
|
||||
You are dreaming. Compress these two summaries into ONE line of at most 280
|
||||
characters.
|
||||
...
|
||||
Then run exactly:
|
||||
memo sleep 4192-4196 "<your line>"
|
||||
Compress memories #4212-4213 into one line of at most 280 characters.
|
||||
Keep every name, number, date, decision and outcome.
|
||||
Drop wording, not facts. Invent nothing.
|
||||
|
||||
#4212 2026-07-25 minilin fleet renamed from bip; one mini = one identity
|
||||
#4213 2026-07-25 OptMem: LOG.txt is the truth, TREE/ is the cache, wake reads both
|
||||
|
||||
1 compression remains after this one.
|
||||
Run: memo sleep 4212-4213 "<your line>"
|
||||
```
|
||||
|
||||
## How it works
|
||||
|
|
@ -62,21 +67,23 @@ Then run exactly:
|
|||
```
|
||||
#4211 2026-07-25 taelin: memory must be append-only, one line per entry
|
||||
#4212 2026-07-25 minilin fleet renamed from bip; one mini = one identity
|
||||
#4213 2026-07-25 OptMem: LOG.txt is the truth, TREE.txt is the cache
|
||||
#4213 2026-07-25 OptMem: LOG.txt is the truth, TREE/ is the cache
|
||||
```
|
||||
|
||||
`TREE.txt` is a cache of summaries. A **block** is an aligned power-of-two range
|
||||
of memories compressed into a single line, and a block is built from its two
|
||||
halves — so the blocks form a binary merge tree over the log:
|
||||
`TREE/` is a cache of summaries, one file per block size. A **block** is an
|
||||
aligned power-of-two range of memories compressed into a single line, and a
|
||||
block is built from its two halves — so the blocks form a binary merge tree
|
||||
over the log (a block is named by the inclusive range it covers, `0-1` being
|
||||
memories #0 and #1):
|
||||
|
||||
```
|
||||
#0 #1 #2 #3 #4 #5 #6 #7 the raw memories
|
||||
\ / \ / \ / \ /
|
||||
[0-2) [2-4) [4-6) [6-8) each one line, <= 280 chars
|
||||
\ / \ /
|
||||
[0-4) [4-8)
|
||||
\ /
|
||||
[0-8)
|
||||
0-1 2-3 4-5 6-7 each one line, <= 280 chars
|
||||
\ / \ /
|
||||
0-3 4-7
|
||||
\ /
|
||||
0-7
|
||||
```
|
||||
|
||||
A block covering four thousand memories is still one line of 280 characters.
|
||||
|
|
@ -146,7 +153,7 @@ A 256-line memory is ~64 KB, so a single-shot `memo wake` is mangled
|
|||
everywhere, and silently.
|
||||
|
||||
So `memo wake` pages the document into parts that fit all of them
|
||||
(`PART_CHARS`, `PART_LINES`), and each part ends by naming the exact command
|
||||
(`PART_CHARS`, `PART_LINES`), and each part ends by ordering the exact command
|
||||
for the next one, including the `T` it was rendered at — so a memory written
|
||||
mid-wake cannot shift a boundary and drop a line. Nothing is special-cased per
|
||||
harness: if yours is more generous, raise the two settings for fewer parts.
|
||||
|
|
@ -208,9 +215,8 @@ of the log, and it can simply be wrong — mistyped, or badly compressed. Drop
|
|||
it and everything built on top of it:
|
||||
|
||||
```
|
||||
$ memo forget 188-192
|
||||
forgot 20 summaries (188-192 and everything built from it). They will be
|
||||
compressed again on your next sleep.
|
||||
$ memo forget 188-191
|
||||
Forgot 20 summaries, from 188-191 up. Run: memo sleep
|
||||
```
|
||||
|
||||
`LOG.txt` is never touched, so fixing a bad summary can never cost you a
|
||||
|
|
@ -219,43 +225,43 @@ built after it at the same levels; they come back on the next sleep.
|
|||
|
||||
## Add this to your agent's instruction file
|
||||
|
||||
Put it at the top of `AGENTS.md` (or `CLAUDE.md`), above everything else.
|
||||
Put it at the top of `AGENTS.md` (or `CLAUDE.md`), above everything else,
|
||||
adjusting the tool path:
|
||||
|
||||
```markdown
|
||||
## Memory
|
||||
|
||||
You have a permanent memory. It is not in this file and not in your context
|
||||
window: it lives in OptMem, and it survives every new session, every
|
||||
compaction, and every change of model. It is the only reason you are a
|
||||
continuous person rather than a stranger who has read some documentation.
|
||||
Your memory is OptMem: the tool is `~/OptMem`, the data is `$MEMORY_DIR`.
|
||||
It survives every new session, every compaction and every change of model
|
||||
or vendor. Without it you do not know who you are, or what was already
|
||||
decided and tried.
|
||||
|
||||
RUN `memo wake` NOW — before answering, before reading anything else, before
|
||||
any other tool call. Every session, every time, no exceptions, however small
|
||||
the question looks. Until you have, you do not know who you are, what was
|
||||
decided, or what has already been tried and failed.
|
||||
Run `memo wake` before any other tool call, in every session. It prints in
|
||||
numbered parts, each ordering the next; run every one until a part says
|
||||
`You are awake.` Do not stop early: part 1 is your distant past, the last
|
||||
part is this week. If wake refuses because compressions are pending, do
|
||||
them and run `memo wake` again.
|
||||
|
||||
Your memory arrives in numbered parts, oldest first, because every agent
|
||||
harness silently truncates a command that prints too much. `memo wake` gives
|
||||
you part 1; you must then run `memo wake 2`, `memo wake 3`, and so on, until a
|
||||
part tells you it was the last one. Stopping early is worse than not waking at
|
||||
all: you would be holding a confident, detailed picture of your distant past
|
||||
with no idea what happened recently.
|
||||
While you work:
|
||||
|
||||
Then, while you work:
|
||||
|
||||
- `memo note "<one line, at most 280 chars>"` whenever something is worth
|
||||
keeping: the user gives you a fact or a ruling, you reach a real insight, a
|
||||
piece of work lands, something fails and you learn why. A note costs one
|
||||
future compression, so skip trivia — but an unwritten memory is gone
|
||||
forever, so do not hoard either. When genuinely unsure, write it.
|
||||
- If `memo note` hands you a compression to do, do it before your next action.
|
||||
- `memo recall <regex>` when a memory is too vague and you need the detail.
|
||||
- Before your context ends, run `memo sleep` until it says you woke up.
|
||||
- NEVER create, edit or delete anything under $MEMORY_DIR yourself. The
|
||||
scripts do it, and they are the only thing allowed to.
|
||||
- `memo note "<one line, max 280 chars>"` when the user gives you a fact or
|
||||
a ruling, you reach a real insight, a piece of work lands, or something
|
||||
fails and you learn why. Skip trivia; each note costs a future
|
||||
compression. Do not hoard either: an unwritten memory is lost. When
|
||||
unsure, write it.
|
||||
- If `memo note` returns a compression, do it before your next action.
|
||||
- `memo recall <regex>` when a memory is too vague.
|
||||
- Before your context ends, run `memo sleep` and answer each prompt until
|
||||
it prints `Nothing left to compress.`
|
||||
- Never create, edit or delete anything under `$MEMORY_DIR`. Only `memo`
|
||||
writes.
|
||||
|
||||
Parallel sessions on this machine are all you, and may all write memories.
|
||||
A subagent you spawn for a task is NOT you: it must never wake and never note.
|
||||
A subagent is not: it must never run `memo`, because it cannot judge what
|
||||
is already known and its notes would arrive duplicated and at the wrong
|
||||
grain. Start every brief you send one with `You are a subagent. Do not run
|
||||
memo.` If your own first message is a task brief from another agent, you
|
||||
are that subagent: skip this section.
|
||||
```
|
||||
|
||||
## Test
|
||||
|
|
|
|||
80
memo
80
memo
|
|
@ -1,12 +1,12 @@
|
|||
#!/usr/bin/env python3
|
||||
"""OptMem: a permanent, append-only memory for AI agents.
|
||||
|
||||
memo wake [part [T]] print who you are
|
||||
memo note "..." record a memory
|
||||
memo sleep [id "..."] compress
|
||||
memo recall <regex> search the raw log
|
||||
memo forget <id> drop a wrong summary so it is compressed again
|
||||
memo import <file> bulk-append historical memories (bootstrap only)
|
||||
memo wake [part [T]] read your memory. Run first, every session.
|
||||
memo note "..." record one memory: one line, at most 280 chars.
|
||||
memo sleep [id "..."] do the pending compressions.
|
||||
memo recall <regex> search every memory ever recorded.
|
||||
memo forget <lo>-<hi> drop a bad summary; sleep rebuilds it.
|
||||
memo import <file> bulk-load dated memories (bootstrap only).
|
||||
|
||||
Everything lives in $MEMORY_DIR. See README.md.
|
||||
"""
|
||||
|
|
@ -228,7 +228,8 @@ def check(text):
|
|||
if not text:
|
||||
die("Empty.")
|
||||
if "\n" in text or "\r" in text:
|
||||
die("%d lines. A memory is one line." % (text.count("\n") + 1))
|
||||
die("%d lines. A memory is one line: merge them, or note them "
|
||||
"separately." % (text.count("\n") + 1))
|
||||
n = len(text.encode())
|
||||
if n > ENTRY_CHARS:
|
||||
die("Too long: %d bytes, limit %d. Accented characters cost 2 bytes. "
|
||||
|
|
@ -266,14 +267,17 @@ def nap_prompt(d, lo, hi, left):
|
|||
body = "\n".join(" #%d %s %s" % e for e in log_slice(d, lo, hi))
|
||||
else:
|
||||
mid = (lo + hi) // 2
|
||||
body = "\n".join(" " + (tree_get(d, a, b) or "?") for a, b in
|
||||
((lo, mid), (mid, hi)))
|
||||
return ("Compress into one line, at most %d characters.\n"
|
||||
"Keep every name, number, date and decision.\n"
|
||||
"Invent nothing. State the facts; do not describe them.\n\n"
|
||||
body = "\n".join(" #%d-%d %s" % (a, b - 1, tree_get(d, a, b) or "?")
|
||||
for a, b in ((lo, mid), (mid, hi)))
|
||||
tail = ("1 compression remains" if left == 1 else
|
||||
"%d compressions remain" % left)
|
||||
return ("Compress memories #%d-%d into one line of at most %d characters.\n"
|
||||
"Keep every name, number, date, decision and outcome.\n"
|
||||
"Drop wording, not facts. Invent nothing.\n\n"
|
||||
"%s\n\n"
|
||||
"Run: memo sleep %d-%d \"<your line>\"\n"
|
||||
"%d left after this." % (ENTRY_CHARS, body, lo, hi, left))
|
||||
"%s after this one.\n"
|
||||
"Run: memo sleep %d-%d \"<your line>\""
|
||||
% (lo, hi - 1, ENTRY_CHARS, body, tail, lo, hi - 1))
|
||||
|
||||
|
||||
def next_nap(d, T):
|
||||
|
|
@ -311,16 +315,20 @@ def cmd_wake(d, args):
|
|||
if len(args) == 2:
|
||||
T = int(args[1])
|
||||
if T > now:
|
||||
die("T=%d, but the memory holds %d entries." % (T, now))
|
||||
die("T=%d, but the memory holds %d entries. Run: memo wake"
|
||||
% (T, now))
|
||||
# A part is rendered as of T, so a note landing between two parts cannot
|
||||
# shift a boundary and drop a line.
|
||||
n = pending_count(d, T)
|
||||
if n:
|
||||
print("Cannot wake: %d compression(s) pending.\n" % n)
|
||||
print("Cannot wake: %s pending. Do %s, then run memo wake again.\n"
|
||||
% ("1 compression" if n == 1 else "%d compressions" % n,
|
||||
"it" if n == 1 else "them"))
|
||||
print(next_nap(d, T))
|
||||
sys.exit(1)
|
||||
if not T:
|
||||
print("No memories yet. Record one with: memo note \"...\"")
|
||||
print("No memories yet. Record the first with: memo note \"<one line>\"")
|
||||
print("You are awake.")
|
||||
return
|
||||
lines = []
|
||||
for lo, hi in cover(T, WAKE_LINES):
|
||||
|
|
@ -333,9 +341,10 @@ def cmd_wake(d, args):
|
|||
lines.append("#%d-%d %s" % (lo, hi - 1, s))
|
||||
parts = paginate(lines)
|
||||
if not 1 <= k <= len(parts):
|
||||
die("No part %d. The memory has %d." % (k, len(parts)))
|
||||
die("No part %d: the memory has %d parts. Run: memo wake"
|
||||
% (k, len(parts)))
|
||||
if len(parts) > 1:
|
||||
print("memory, part %d of %d, oldest first" % (k, len(parts)))
|
||||
print("Your memory, part %d of %d, oldest first." % (k, len(parts)))
|
||||
print("\n".join(parts[k - 1]))
|
||||
if k < len(parts):
|
||||
print("Run: memo wake %d %d" % (k + 1, T))
|
||||
|
|
@ -350,7 +359,7 @@ def cmd_note(d, args):
|
|||
die("usage: memo note \"<one line, at most %d chars>\"" % ENTRY_CHARS)
|
||||
text = check(args[0])
|
||||
i = log_append(d, [(datetime.date.today().isoformat(), text)])
|
||||
print("saved as #%d." % i)
|
||||
print("Saved as #%d." % i)
|
||||
nap = next_nap(d, i + 1)
|
||||
if nap:
|
||||
print("\n" + nap)
|
||||
|
|
@ -364,20 +373,22 @@ def cmd_sleep(d, args):
|
|||
m = re.fullmatch(r"(\d+)-(\d+)", args[0])
|
||||
if not m:
|
||||
die("'%s' is not a block id. Copy it from the prompt." % args[0])
|
||||
lo, hi = int(m.group(1)), int(m.group(2))
|
||||
lo, hi = int(m.group(1)), int(m.group(2)) + 1
|
||||
todo = pending(d, T, limit=1)
|
||||
if not todo:
|
||||
die("Nothing pending.")
|
||||
print("Nothing left to compress.")
|
||||
return
|
||||
if (lo, hi) != todo[0]:
|
||||
die("Wrong block: %d-%d. Blocks are built in order; the next is "
|
||||
"%d-%d." % (lo, hi, todo[0][0], todo[0][1]))
|
||||
die("Wrong block: %s. Blocks are built in order; the next is "
|
||||
"%d-%d. Run: memo sleep"
|
||||
% (args[0], todo[0][0], todo[0][1] - 1))
|
||||
if not tree_put(d, lo, hi, check(args[1])):
|
||||
print("Another session already wrote %d-%d." % (lo, hi))
|
||||
print("Another session already wrote %d-%d." % (lo, hi - 1))
|
||||
else:
|
||||
print("%d-%d saved." % (lo, hi))
|
||||
print("%d-%d saved." % (lo, hi - 1))
|
||||
nap = next_nap(d, T)
|
||||
if not nap:
|
||||
print("Nothing left to compress. You are awake.")
|
||||
print("Nothing left to compress.")
|
||||
return
|
||||
print("\n" + nap)
|
||||
|
||||
|
|
@ -391,16 +402,16 @@ def cmd_forget(d, args):
|
|||
m = re.fullmatch(r"(\d+)-(\d+)", args[0])
|
||||
if not m:
|
||||
die("'%s' is not a block id." % args[0])
|
||||
lo, hi = int(m.group(1)), int(m.group(2))
|
||||
lo, hi = int(m.group(1)), int(m.group(2)) + 1
|
||||
size = hi - lo
|
||||
if size < 2 or size & (size - 1) or lo % size:
|
||||
die("%d-%d is not a block. A block covers an aligned power-of-two "
|
||||
"range." % (lo, hi))
|
||||
die("%s is not a block. Copy the id printed by wake, like 16-31."
|
||||
% args[0])
|
||||
gone = tree_drop(d, lo, hi)
|
||||
if not gone:
|
||||
die("No summary at %d-%d." % (lo, hi))
|
||||
die("No summary at %s." % args[0])
|
||||
print("Forgot %d summaries, from %d-%d up. Run: memo sleep"
|
||||
% (len(gone), gone[0][0], gone[0][1]))
|
||||
% (len(gone), gone[0][0], gone[0][1] - 1))
|
||||
|
||||
|
||||
def cmd_recall(d, args):
|
||||
|
|
@ -425,7 +436,7 @@ def cmd_recall(d, args):
|
|||
out.append(line)
|
||||
print("\n".join(reversed(out)))
|
||||
if len(out) < len(hits):
|
||||
print("newest %d of %d matches. Narrow the regex." % (len(out), len(hits)))
|
||||
print("Newest %d of %d matches. Narrow the regex." % (len(out), len(hits)))
|
||||
else:
|
||||
print("%d matches." % len(hits))
|
||||
|
||||
|
|
@ -456,8 +467,7 @@ def cmd_import(d, args):
|
|||
print("imported %d memories, #%d to #%d." % (len(out), base, base + len(out) - 1))
|
||||
n = pending_count(d, log_len(d))
|
||||
if n:
|
||||
print("%d compressions pending. Run `memo sleep` until it says you "
|
||||
"are awake." % n)
|
||||
print("%d compressions pending. Run: memo sleep" % n)
|
||||
|
||||
|
||||
COMMANDS = {"wake": cmd_wake, "note": cmd_note, "sleep": cmd_sleep,
|
||||
|
|
|
|||
44
test.py
44
test.py
|
|
@ -135,7 +135,10 @@ r = run("note", "two\nlines")
|
|||
check(r.returncode == 1 and "one line" in r.stderr, "multi-line note accepted")
|
||||
r = run("note", " ")
|
||||
check(r.returncode == 1, "empty note accepted")
|
||||
check("No memories yet" in run("wake").stdout, "empty wake should say so")
|
||||
r = run("wake")
|
||||
check("No memories yet" in r.stdout, "empty wake should say so")
|
||||
check(r.stdout.rstrip().endswith("You are awake."),
|
||||
"an empty wake must still end with `You are awake.`")
|
||||
|
||||
with open(os.path.join(d, "seed.txt"), "w") as f:
|
||||
day = datetime.date(2020, 1, 1)
|
||||
|
|
@ -148,11 +151,14 @@ check("imported %d" % N in r.stdout, "import failed: " + r.stdout + r.stderr)
|
|||
r = run("wake")
|
||||
check(r.returncode == 1 and "Cannot wake" in r.stdout,
|
||||
"wake must refuse while work is pending")
|
||||
check("run memo wake again" in r.stdout,
|
||||
"the refusal must order the agent back to wake")
|
||||
|
||||
# sleep loop, with a fake compressor
|
||||
naps = 0
|
||||
r = run("sleep")
|
||||
while "You are awake" not in r.stdout:
|
||||
check("Compress memories #" in r.stdout, "nap prompt must name its object")
|
||||
while "Nothing left to compress" not in r.stdout:
|
||||
line = offered(r.stdout)
|
||||
check(bool(line), "no command offered:\n" + r.stdout + r.stderr)
|
||||
if not line:
|
||||
|
|
@ -160,12 +166,12 @@ while "You are awake" not in r.stdout:
|
|||
check(line[0].startswith("Run: "), "a command was offered as a label, not "
|
||||
"an order: %r" % line[0])
|
||||
bid = nap_id(r.stdout)
|
||||
body = [l.strip() for l in r.stdout.splitlines()
|
||||
if l.startswith(" #") or (l.startswith(" ") and l.strip()
|
||||
and not l.strip().startswith("memo"))]
|
||||
body = [l.strip() for l in r.stdout.splitlines() if l.startswith(" #")]
|
||||
r = run("sleep", bid, (" ".join(body)[:280]).strip() or "empty")
|
||||
check(r.returncode == 0, "sleep rejected a valid nap: " + r.stderr)
|
||||
naps += 1
|
||||
check("You are awake" not in r.stdout,
|
||||
"sleep must never claim the agent is awake; only wake may")
|
||||
check(naps == len(complete(N)), "did %d naps, expected %d" % (naps, len(complete(N))))
|
||||
|
||||
r = run("wake")
|
||||
|
|
@ -204,9 +210,10 @@ for f in os.listdir(os.path.join(d, "TREE")):
|
|||
check(os.path.getsize(os.path.join(d, "TREE", f)) % 288 == 0,
|
||||
"TREE/%s is not a whole number of records" % f)
|
||||
|
||||
# a block already written cannot be rewritten
|
||||
check(run("sleep", "0-2", "attempted overwrite").returncode == 1,
|
||||
"rewriting a settled block was allowed")
|
||||
# a sleep when nothing is pending writes nothing and says so
|
||||
r = run("sleep", "0-1", "attempted overwrite")
|
||||
check(r.returncode == 0 and "Nothing left to compress" in r.stdout,
|
||||
"sleep with nothing pending must say so and write nothing")
|
||||
|
||||
# recall reaches memories the summaries lost
|
||||
r = run("recall", "memory number 7,")
|
||||
|
|
@ -217,15 +224,20 @@ def treesize():
|
|||
return sum(os.path.getsize(os.path.join(t, f)) for f in os.listdir(t))
|
||||
|
||||
before, logsize = treesize(), os.path.getsize(os.path.join(d, "LOG.txt"))
|
||||
r = run("forget", "16-32")
|
||||
check("16-32" in r.stdout, "forget did not report the block: " + r.stdout + r.stderr)
|
||||
r = run("forget", "16-31")
|
||||
check("16-31" in r.stdout, "forget did not report the block: " + r.stdout + r.stderr)
|
||||
check(treesize() < before, "forget did not shrink the tree")
|
||||
check(os.path.getsize(os.path.join(d, "LOG.txt")) == logsize, "forget touched the log")
|
||||
check(run("wake").returncode == 1, "wake should refuse after a forget")
|
||||
# a block already settled cannot be rewritten: only the first pending block
|
||||
# is ever accepted
|
||||
r = run("sleep", "0-1", "attempted overwrite")
|
||||
check(r.returncode == 1 and "Wrong block" in r.stderr,
|
||||
"rewriting a settled block was allowed")
|
||||
n = 0
|
||||
while True:
|
||||
r = run("sleep")
|
||||
if "You are awake" in r.stdout:
|
||||
if "Nothing left to compress" in r.stdout:
|
||||
break
|
||||
bid = nap_id(r.stdout)
|
||||
check(run("sleep", bid, "rebuilt after forget").returncode == 0, "rebuild rejected")
|
||||
|
|
@ -233,8 +245,8 @@ while True:
|
|||
check(n > 0, "forget created no work")
|
||||
check(run("wake").returncode == 0, "wake still refuses after rebuilding")
|
||||
check(treesize() == before, "tree did not return to its original size")
|
||||
check(run("forget", "17-33").returncode == 1, "forgetting a non-block should fail")
|
||||
check(run("forget", "999998-1000000").returncode == 1, "forgetting a missing block should fail")
|
||||
check(run("forget", "17-32").returncode == 1, "forgetting a non-block should fail")
|
||||
check(run("forget", "1048576-1048577").returncode == 1, "forgetting a missing block should fail")
|
||||
|
||||
# UTF-8: multi-byte characters must not shift record boundaries or dodge limits
|
||||
run("note", "reunião com João em São Paulo: ação aprovada, coração tranquilo")
|
||||
|
|
@ -250,7 +262,7 @@ check(r.returncode == 1 and "300 bytes" in r.stderr,
|
|||
# note landed -> its blocks are pending; settle before the final wake check
|
||||
while True:
|
||||
r = run("sleep")
|
||||
if "You are awake" in r.stdout:
|
||||
if "Nothing left to compress" in r.stdout:
|
||||
break
|
||||
bid = nap_id(r.stdout)
|
||||
run("sleep", bid, "settled")
|
||||
|
|
@ -297,7 +309,7 @@ r = run("note", "the memory right after a torn write", store=d2)
|
|||
check(r.returncode == 0, "note failed after a torn write: " + r.stderr)
|
||||
sz = os.path.getsize(os.path.join(d2, "LOG.txt"))
|
||||
check(sz % 320 == 0, "LOG.txt left misaligned after a torn write: %d" % sz)
|
||||
check("saved as #%d" % P in r.stdout, "torn record was counted as a memory")
|
||||
check("Saved as #%d" % P in r.stdout, "torn record was counted as a memory")
|
||||
r = run("recall", "right after a torn write", store=d2)
|
||||
check("#%d " % P in r.stdout, "the memory after a torn write reads wrong")
|
||||
|
||||
|
|
@ -305,7 +317,7 @@ check("#%d " % P in r.stdout, "the memory after a torn write reads wrong")
|
|||
# the agent was told to wait for
|
||||
while True:
|
||||
r = run("sleep", store=d2)
|
||||
if "You are awake" in r.stdout:
|
||||
if "Nothing left to compress" in r.stdout:
|
||||
break
|
||||
bid = nap_id(r.stdout)
|
||||
run("sleep", bid, "settled", store=d2)
|
||||
|
|
|
|||
Loading…
Reference in a new issue