every command handed to an agent is an order, not a label

'next: memo wake 2 246' is an annotation, not an instruction. ASD-STE100:
an instruction is an imperative, verb first. Same defect in the nap prompt,
where the command sat on a bare line with nothing telling the agent to run it.

The wake terminator is 'You are awake.', the same words memo sleep already
used, instead of a bare 'awake.' -- one term, one meaning.

test.py now asserts that every offered command starts with 'Run: '.
This commit is contained in:
victortaelin 2026-07-25 17:20:01 -03:00
parent dc5339c55b
commit af17f94c2f
3 changed files with 30 additions and 14 deletions

View file

@ -37,7 +37,7 @@ export MEMORY_DIR="$HOME/memory" # required; there is no default
```sh ```sh
memo wake # who you are. run this first, every session, memo wake # who you are. run this first, every session,
# then the command each part names, until # then the command each part names, until
# one of them prints `awake.` # one of them prints `You are awake.`
memo note "..." # record a memory. one line, <= 280 chars. memo note "..." # record a memory. one line, <= 280 chars.
memo sleep # compress. keep going until it says you woke up. memo sleep # compress. keep going until it says you woke up.
memo recall <regex> # search the raw log for detail a summary lost. memo recall <regex> # search the raw log for detail a summary lost.

6
memo
View file

@ -272,7 +272,7 @@ def nap_prompt(d, lo, hi, left):
"Keep every name, number, date and decision.\n" "Keep every name, number, date and decision.\n"
"Invent nothing. State the facts; do not describe them.\n\n" "Invent nothing. State the facts; do not describe them.\n\n"
"%s\n\n" "%s\n\n"
"memo sleep %d-%d \"<your line>\"\n" "Run: memo sleep %d-%d \"<your line>\"\n"
"%d left after this." % (ENTRY_CHARS, body, lo, hi, left)) "%d left after this." % (ENTRY_CHARS, body, lo, hi, left))
@ -338,11 +338,11 @@ def cmd_wake(d, args):
print("memory, part %d of %d, oldest first" % (k, len(parts))) print("memory, part %d of %d, oldest first" % (k, len(parts)))
print("\n".join(parts[k - 1])) print("\n".join(parts[k - 1]))
if k < len(parts): if k < len(parts):
print("next: memo wake %d %d" % (k + 1, T)) print("Run: memo wake %d %d" % (k + 1, T))
else: else:
# always, even for a one-part memory: the contract an agent is given # always, even for a one-part memory: the contract an agent is given
# is "run parts until one says awake", so it must always arrive # is "run parts until one says awake", so it must always arrive
print("awake.") print("You are awake.")
def cmd_note(d, args): def cmd_note(d, args):

36
test.py
View file

@ -8,6 +8,7 @@ import contextlib
import datetime import datetime
import io import io
import os import os
import re
import shutil import shutil
import subprocess import subprocess
import sys import sys
@ -109,6 +110,18 @@ def run(*args, store=None):
return Result(code, out.getvalue(), err.getvalue()) return Result(code, out.getvalue(), err.getvalue())
def nap_id(out):
"""The block id from the command a nap prompt offers."""
m = re.search(r"memo sleep (\d+)-(\d+)", out)
return "%s-%s" % m.groups() if m else None
def offered(out):
"""The line offering a command. Every command handed to an agent must be
an order, not a label: `Run: memo ...`, never `next: memo ...`."""
return [l for l in out.splitlines() if "memo sleep " in l or "memo wake " in l]
# the real entry point still has to work: shebang, argv parsing, exit code # the real entry point still has to work: shebang, argv parsing, exit code
smoke = subprocess.run(memo + ["wake"], env=dict(os.environ, MEMORY_DIR=d), smoke = subprocess.run(memo + ["wake"], env=dict(os.environ, MEMORY_DIR=d),
capture_output=True, text=True) capture_output=True, text=True)
@ -140,11 +153,13 @@ check(r.returncode == 1 and "Cannot wake" in r.stdout,
naps = 0 naps = 0
r = run("sleep") r = run("sleep")
while "You are awake" not in r.stdout: while "You are awake" not in r.stdout:
line = [l for l in r.stdout.splitlines() if l.strip().startswith("memo sleep ")] line = offered(r.stdout)
check(bool(line), "no command offered:\n" + r.stdout + r.stderr) check(bool(line), "no command offered:\n" + r.stdout + r.stderr)
if not line: if not line:
break break
bid = line[0].split()[2] 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() body = [l.strip() for l in r.stdout.splitlines()
if l.startswith(" #") or (l.startswith(" ") and l.strip() if l.startswith(" #") or (l.startswith(" ") and l.strip()
and not l.strip().startswith("memo"))] and not l.strip().startswith("memo"))]
@ -174,8 +189,10 @@ lines = [l for p in parts for l in p]
check(len(lines) == WAKE_LINES, "woke with %d lines, want %d" % (len(lines), WAKE_LINES)) check(len(lines) == WAKE_LINES, "woke with %d lines, want %d" % (len(lines), WAKE_LINES))
check(lines[-1].startswith("#%d " % (N - 1)), "newest memory not last / not raw") check(lines[-1].startswith("#%d " % (N - 1)), "newest memory not last / not raw")
check(lines[0].startswith("#0-"), "oldest line should be a summary block") check(lines[0].startswith("#0-"), "oldest line should be a summary block")
check("memo wake 2" in run("wake").stdout, "part 1 must name the next command") check("Run: memo wake 2" in run("wake").stdout,
check("awake." in run("wake", str(len(parts))).stdout, "last part must say it is last") "part 1 must ORDER the next command, not label it")
check("You are awake." in run("wake", str(len(parts))).stdout,
"last part must say it is last")
check(run("wake", str(len(parts) + 1)).returncode == 1, "a nonexistent part should fail") check(run("wake", str(len(parts) + 1)).returncode == 1, "a nonexistent part should fail")
# append-only: nothing was ever rewritten # append-only: nothing was ever rewritten
@ -210,7 +227,7 @@ while True:
r = run("sleep") r = run("sleep")
if "You are awake" in r.stdout: if "You are awake" in r.stdout:
break break
bid = [l for l in r.stdout.splitlines() if l.strip().startswith("memo sleep ")][0].split()[2] bid = nap_id(r.stdout)
check(run("sleep", bid, "rebuilt after forget").returncode == 0, "rebuild rejected") check(run("sleep", bid, "rebuilt after forget").returncode == 0, "rebuild rejected")
n += 1 n += 1
check(n > 0, "forget created no work") check(n > 0, "forget created no work")
@ -235,7 +252,7 @@ while True:
r = run("sleep") r = run("sleep")
if "You are awake" in r.stdout: if "You are awake" in r.stdout:
break break
bid = [l for l in r.stdout.splitlines() if l.strip().startswith("memo sleep ")][0].split()[2] bid = nap_id(r.stdout)
run("sleep", bid, "settled") run("sleep", bid, "settled")
check(run("wake").returncode == 0, "wake refuses at the very end") check(run("wake").returncode == 0, "wake refuses at the very end")
@ -290,12 +307,11 @@ while True:
r = run("sleep", store=d2) r = run("sleep", store=d2)
if "You are awake" in r.stdout: if "You are awake" in r.stdout:
break break
bid = [l for l in r.stdout.splitlines() bid = nap_id(r.stdout)
if l.strip().startswith("memo sleep ")][0].split()[2]
run("sleep", bid, "settled", store=d2) run("sleep", bid, "settled", store=d2)
r = run("wake", store=d2) r = run("wake", store=d2)
check(r.stdout.rstrip().endswith("awake."), check(r.stdout.rstrip().endswith("You are awake."),
"a one-part wake never says `awake.`:\n" + r.stdout) "a one-part wake never says `You are awake.`:\n" + r.stdout)
shutil.rmtree(d2) shutil.rmtree(d2)
shutil.rmtree(d) shutil.rmtree(d)