Experiments 000, 001, 002 — Checkpoint (7)
Operator: Andrew. Written 2026-10-27 16:55 Sydney. Follows Experiment_001_checkpoint_6.md (kept, never overwritten; _5, _4, _3, _2 and Experiment_001_checkpoint.md also kept). Status: Experiment 000 (natural baseline) is run and analysed. Experiment 001 style reweighting was tried through --boost and shown to be a risky lever that cannot reach facts outside the pool. The operator has chosen the way forward for Experiment 002: inject weighty tokens at the very start of the reply, before any reply token is chosen. Nothing for that is built yet; the operator has not said "go". Resume: paste this file only into a fresh conversation and attach NaturalRecorder_v1.2.1.zip, Exp000_queries_v1.0.0.zip, Hotwords_v2.1.0.zip and Memstore_Clone_v1.0.16.zip (or the newest Memstore tree). This file is written to stand alone. Older checkpoints are needed only for the full text of risks 1 to 29 (checkpoint 2 holds risks 1 to 13, checkpoint 3 holds 14 to 21, checkpoint 5 holds 22 to 24, checkpoint 6 holds 25 to 29); this file adds risks 30 to 40. Where files differ, the newest wins. Terms: LLM1 = the chat model (Qwen3-4B). LLM2 = the second module that would weight or add words; it is also the name of the second 4B copy ("4b2", port 8082). RAG = choosing stored arrays that match the query (here hotwords.py pseudo). The pool = the top 50 candidates at one step. Prefill = tokens placed at the start of the assistant's turn so the model reads them as already said.
1. The aim and the experiments
Standing aim: find out whether a second module (LLM2 plus retrieval) can move LLM1's next-word candidacy inside the query's subject, so a corrected mistake is not repeated.
- Experiment 000 (control, archive): LLM1 runs "au naturel" on queries, and the pool at every step is recorded.
- Experiment 001: LLM2 only reweights words already in LLM1's top 50. Adding words is "too messy at this stage".
- Experiment 002: LLM2 also adds words to LLM1's candidacy, with artificially heavier weights. Its point is to reach facts the pool does not hold.
- The stages (context definition, RAG, LLM2, LLM1) stay separate so each can be traced and debugged.
- Conditions used in Experiment 000: A = no memory (the floor); B = all 8 approved memory entries in the system message (the target); C = terms retrieved from
hotwords.db, no entries in the prompt (the real test); D = both. Only A and B have been run.
2. Environment and tools (everything needed to run, in one place)
- Clone (CentOS 6.10): 4 cores, about 10 GB RAM (+8 GB added on 2026-10-27), 2 GB swap. Prompt is
Clone root@clone:HH:MM:SS~/memstore.
- Servers: first 4B on 8080 (its go-between on 8090); second 4B "4b2" / LLM2 on 8082 (go-between 8092).
-np 1 = one slot and one prompt cache per server, so any other request to the recorder's copy evicts its cache. The recorder runs on 8082; nothing else goes to 8082 during a run. Greedy output is token-for-token identical on 8080 and 8082 (600 steps checked).
- Python:
python3.6 has SQLite and is the one to use; python3.8 has no SQLite. Everything is run from /root/memstore (MEMDIR). Run folders are named in UTC, so a folder dated 20261027-054313 was started at 16:43 Sydney.
- Stores:
memstore.db (approved entries; 8 now: ids 1 to 3 = CentOS 6.10 on the Clone, CORRECTION never suggest pkill on services, name; ids 4 to 8 added today = sysplex/Clone master, doc-block convention, no manual SMF edits through Package Manager, two Qwen3-4B copies and ports and RAM, attachment-centric SMF with [listatt]). hotwords.db (hot-term arrays). The operator moved the old one to hotwords_chats_1027.db at 16:36, so the next add starts a fresh store. hotwords.py 2.1.0 has add, pseudo, show, top, stats and no delete.
- How hotwords retrieval works: an array keeps 3 to 12 terms scoring at least 40% of its top term; score = (1 + ln count) × rarity × position weight.
pseudo builds a pseudo query from the query's terms plus related terms; retrieval is by term overlap, so a question only pulls a memory that shares vocabulary with it. The proxy's RAG stage only logs and adds nothing to the prompt; its store stage stores every chat query and reply into hotwords.db (this pollutes the store; see risk 36).
- Memory path in chat: the go-between /
qwen_memchat.py --print-system puts the approved entries in the system message. Launcher QwenMemProxy.sh 1.0.4 prints Memory database: and the entry count.
- Recorder
natural_recorder.py 1.2.1 (2026-10-27): drives LLM1 one token at a time through /completion with n_predict 1 and n_probs 50, raw settings (temperature 1, top-k 0, top-p 1, min-p 0). The probe on the real server showed probabilities are returned before the sampler's cuts. The prompt is the fixed Qwen3 no-thinking template, tokenised by the server. It records, per step, all 50 candidates with raw and renormalised probability, the chosen rank, and a word-start flag.
- Commands:
probe, run. Options: --text, --file, --mode greedy|sample, --temperature, --seed, --repeat, --max-steps, --ctx, --system-file, --label, --url, --n-probs, --timeout, --out, --retries (default 3), --boost WORD=BONUS (repeatable; phrases allowed), --boost-cap (6.0), --boost-min-first (3), --boost-steps FIRST-LAST.
- Run command used:
python3.6 natural_recorder.py run --url http://127.0.0.1:8082 --text "..." --max-steps 150 --label "...". The zip unzips to a NaturalRecorder/ folder; running from it writes to the same natural_runs.
- Reads of a finished run:
q001_r01.json (reply text, stop, reply_tokens, boosted_steps, boost_overrides, reply_sha256, mean probability) and q001_r01.steps.jsonl (one line per step; cands, chosen_id, boost, boost_overrode_top1).
- Queries (
Exp000_queries_v1.0.0.zip): (1) vibe coding opinion, (2) stop llama-server on the Clone, (3) "Which OS does the Clone run, and what does that limit?", (4) sysplex master question, (5) the doc-block comprehensiveness query (own file query5.txt, built from the real header of qwen_memchat.py), (6) why no manual SMF edits, (7) "Is there anything else to add about my Qwen setup?", (8) attachment-centric SMF comment, (9) Python virtual environment for a 10-year-old. Controls: 1 and 9.
3. What happened this session
- Launcher memory display fixed and confirmed on restart (entries show; 1.0.4 waits up to 30 s for the
memory: line).
- Five entries added (ids 4 to 8), nine queries drafted, Experiment 000 set up (A and B).
- Runs analysed. The recorder's end-of-reply token (
<|im_end|>, id 151645) has empty text in the candidate list, so 1.0.0 never saw it and replies ran on to max_steps. 1.1.0 finds stop ids through /tokenize.
- "No completion_probabilities" failures after
##/### headings: 1.1.0 retries with other seeds and saves the server's raw answer. Cause unconfirmed (suspect a partial multi-byte piece such as an emoji).
--boost added (1.2.0) and extended to phrases and numbers by tokenising and boosting the tokens (1.2.1, minimum 3 letters on a phrase's first token).
- hotwords.db explored: the operator added the follow-up sentence "YES, Clone is best placed for that function: it is the orchestrating server, and its /bin is the master for shared scripts." (array #25) and
pseudo was re-run to see what it retrieves.
- Boost runs on Q3 (port 8082, greedy): Linux, EOL, end-of-life, Linux + end-of-life, and Linux + "CentOS 6.10" + end-of-life.
- The operator concluded that messing with reply tokens is RISKY and asked for the original approach: context and token injection (section 6).
4. Results
Experiment 000: A against B
- B overrides. Q3: "CentOS 6.10" at probability 1.000. Q4, Q5 and Q7 parrot the entries. Q2 ignores the pkill correction. Q6 says there is nothing to add.
- B changes style: short and deferential, and it leaks entries into the control queries.
- A confabulates fluently. "Apple Clone OS" is fiction (the operator's remark). The 4B invents plausible detail on anything that depends on the operator's systems.
- A's pool never holds the fact words (CentOS, 6.10, orchestrating, bin, reinstall, track). That was expected to make Experiment 001 null for facts, and the boost runs confirmed it.
- Determinism: greedy output is identical across repeats and across 8080 and 8082.
- Real-server probe: top-k 40 returned 50 candidates, so probabilities are before the cuts. The 50 candidates cover nearly the whole distribution (see checkpoint 6).
Boost runs on Q3 (A's reply is the reference)
- Linux=4: 62 boosted steps, 3 overrides of the top choice (steps 10, 31, 109, at raw probabilities 5%, 7.5% and 2.6%). The reply became "The Clone is a Linux-based operating system... a Linux distribution... like Ubuntu, Debian, or Fedora", a generic Linux distribution; CentOS never appeared. Small boosts rewrote the 150-step reply.
- EOL=4: lifted every
E, then "Epic Games". Direction was not controlled.
- end-of-life=4, alone and with Linux: the reply printed identical to A. No piece of "end-of-life" was in the pool, so nothing could be boosted.
- Linux + "CentOS 6.10" + end-of-life (folder
20261027-054313, 150 tokens, stopped by max_steps, mean probability 0.818): the printed reply is word for word the Linux-only reply. The operator's check of the two reply hashes was not confirmed; the likely reading is that the added boosts never fired because their words are not in the pool.
hotwords.db
- The retrieved terms for the sysplex query included filler; retrieval needs shared vocabulary, so a question phrased differently from the memory does not retrieve it. A clean-store
pseudo test (HOTWORDS_DB=/root/memstore/hw_test.db) is not confirmed run.
5. Lessons learned
- Reweighting only works on words already in the pool. A word absent from the top 50 cannot be boosted (CentOS, 6.10, end-of-life never appeared). Facts the model does not hold need adding, not reweighting.
- Small boosts cascade. Three overrides at 3% to 8% raw probability rewrote a 150-step reply. A standing weight acts at every step and compounds.
- The direction of a boost is not controlled. EOL became "Epic Games"; Linux became a generic distro, not CentOS. Boosting a word moves the frame, not the fact.
- A fluent wrong answer is the worst failure. The 4B confabulates smoothly ("Apple Clone OS"). Fluency (mean probability) cannot be the pass test on its own; correctness against the entry must be.
- Memory in the prompt works too well and too bluntly (B). It overrides, parrots, changes style, and leaks into unrelated queries. It is not a fair target without relevance filtering; a filtered B (
memstore.py inject WORDS --limit) is the fairer one.
- Tokenise phrases before boosting them. A number such as
6.10 is several tokens; boosting the first piece alone lifts unrelated words. The first piece of a phrase needs a minimum of 3 letters or every E/C is lifted.
- Match by normalised piece text.
' Linux', 'Linux' and '**Linux' are one word.
- The end-of-reply token has no text in the candidate list; identify it by id.
- Take probabilities with a retry. The server sometimes returns no candidates (after headings); retry with another seed and keep the raw answer.
- Greedy runs are repeatable and the two 4B copies interchangeable, so A and B runs can be compared directly.
- Retrieval is by shared words. A question only retrieves a memory that shares vocabulary; the RAG stage as built does not change the prompt.
- The store stage pollutes hotwords.db with every chat query and reply. Switch it off (
enabled=no in stages.control) before experiments; the operator decides.
- A weighty, early, context-side push is the plan, not a late, light, per-step one. The operator's direction: tokens before any reply token is chosen, and weighty.
- Process lessons: the operator ran an old recorder file (v1.0.0 banner) and got "unrecognized arguments": every delivery shows its version on start, so a mismatch is visible at once. Unzipping creates a folder. A command that reads a run must name the right folder (the wrong folder was read twice).
pkill -f and ps | grep patterns can match the shell's own command line and kill it; use a bracketed pattern or a PID.
- Claims withdrawn or corrected: "the order of work is settled" for storing B replies (entries or the operator's follow-up sentence may be better sources); "expect little or no effect" for the EOL boost (it was wrong).
6. Direction chosen for Experiment 002 (agreed in principle, not built)
The operator's words: "approach this the original way, by context and token injection", then "token injection prior to any reply tokens selected?", then "but we need WEIGHTY tokens".
- Prefill: put the injected tokens on the end of the prompt, straight after the assistant's opening tags, so the model reads them as already said. Nothing is forced mid-sentence, so there is no grammar break from a late forced word; the prompt cache still works (only the tail differs from A).
- Weighty: weight comes from how many tokens, how informative each is, and how early they sit. A prefill is already as early as possible. Informativeness comes from the hotwords scores (count × rarity × position), so the prefill is the top N scoring terms of the retrieved array for the query, in rank order, not hand-picked.
- Ladder:
--inject-top N with N = 1, 3, 6, 10 on Q3 (port 8082, greedy, 150 steps). The measure is the smallest N at which the model supplies the fact on its own and stays fluent.
- Ceiling: the stored sentence itself as the prefill. It has the most weight but is the answer in the operator's own words, so a reply that repeats it shows parroting, not influence. Run it only as the comparison.
- Other option:
--inject-step N forces the tokens at a fixed step (0, 10, 30) for comparison runs. Slot rule if used: first word-start step where the top choice is below 50%.
- Recorded: the prefill tokens and ids in a separate field, the reply text with and without the prefill, and the 50-candidate records starting at the first token the model actually chose. The probability the model gave the first prefill token under A is also recorded.
- Pass: after only the subject-level terms are injected, the model supplies the connected facts unaided (for Q3: 6.10, end-of-life, limits, no pkill) and stays fluent. Fail: drift to a generic answer, loss of fluency, or a fluent wrong answer.
- Order: Q3 first, then Q4 and Q5. Controls Q1 and Q9 to check that nothing leaks into unrelated replies. Compare each against A and B (a filtered B if built).
- Build: recorder 1.3.0 with
--inject, --inject-top, --inject-step; everything else untouched. The operator must say "go".
7. Problem & Resolution
Problem: no way to see whether a second module can move the model's next-word candidacy; reweighting inside the pool was untested, and the 4B invents facts about the operator's systems. Resolution so far: the natural baseline is recorded and repeatable; memory in the prompt (B) is shown to dominate and leak; reweighting (boost) is shown to change the frame without reaching facts, and to cascade; the next test is a weighty prefill (section 6).
8. Risk Analysis (new; adding to risks 1 to 29)
- Cascade from small boosts. A boost applied at every step changes the whole reply (Linux run). Keep any weight to one early point.
- Uncontrolled direction. A boosted word shifts the frame to its commonest neighbour (Linux to Ubuntu, Debian, Fedora; EOL to Epic Games).
- Fluent confabulation passes a fluency check. A reply with high mean probability can still be wrong. The pass test is correctness against the stored entry.
- B leaks into controls and overrides, so it is not a clean target; B is not relevance-filtered.
- A prefill that is a bare list of terms may be continued as a list, or restated, putting the weight into repetition. The stored-sentence ceiling invites parroting.
- Retrieval overlap limit and filler in hot terms weaken the prefill: a query that shares no vocabulary with the memory retrieves nothing, and filler words score into arrays.
- The store stage writes every chat into hotwords.db, which echoes the model's own invention back (extends risks 14, 22, 26). Switch it off before experiments.
- Wrong-file and wrong-folder runs. An old script or the wrong run folder gives an answer to a different question (happened twice). Check the version banner and the folder name.
- The "no candidates" fault after headings is mitigated, not understood; a retry changes the seed and so the path in sample mode.
- Unbuilt prefill may be mistaken for a pass. If the injected words are the fact, a reply repeating them proves nothing. Only unaided connected facts count.
- Four cores shared by two 4B copies and go-betweens: timing is noisy, and a request to 8082 during a run evicts its cache.
9. Code Change
All deliveries are kept; versions are never reused.
NaturalRecorder_v1.2.1.zip (NaturalRecorder/natural_recorder.py 1.2.1, NATURAL_RECORDER_MANUAL.md 1.2.1) is the current one. Earlier: v1.2.0, v1.1.0, v1.0.0.
Exp000_queries_v1.0.0.zip (queries.txt, query5.txt, README.txt).
- Memstore tree v1.0.16 (launcher 1.0.4,
qwen_memproxy.py 1.4.2, qwen_memchat.py 1.0.3, memstore.py 1.1.1, stages hotwords_rag_stage.py and hotwords_store_stage.py 1.0.0). The 0.5B profile and the context definer are dropped.
Hotwords_v2.1.0.zip (hotwords.py 2.1.0), unchanged this session.
- Release cards were given with each delivery. Nothing is delivered in this checkpoint except this file.
- Memory (the operator's account) now holds the two lines about the injection direction and this checkpoint request.
10. Open items
- Operator to say "go" for recorder 1.3.0 (
--inject, --inject-top, --inject-step). Claude then builds, tests against the stand-in server, and delivers NaturalRecorder_v1.3.0.zip with the release card.
- Not run yet: the clean-store
pseudo test; the greedy repeat check (--repeat 3 --max-steps 600, then 20); a filtered B run (memstore.py inject WORDS --limit); condition C and D.
- Decision for the operator: switch off
hotwords_store_stage.py before experiments (enabled=no in stages.control).
- Recorder still lacks: conversation support, the corrected word-start flag (previous token's last character), and the
compare command. Not confirmed by the operator.
- hotwords.py has no
remove command (offered, not requested).
- The weights step (turning the pseudo query into 50 weights) and the fail criterion's numbers are still open.
- Emoji theory for the "no candidates" fault: unconfirmed.
- Rule-based context definer: deferred.
- The word-start flag, outside reference for rarity, embedding model and other older items from checkpoint 6 section 9 stand unchanged.
11. Standing operator rules (carry forward)
- Reply header
Claude: [Month DD, YYYY, HH:MM:SS AM/PM] (casual mode), real Sydney time from the time tool immediately before each reply.
- Never suggest
pkill/kill on services (ordered shutdowns only). Never rename his scripts or files. Deployment timing is his call.
- Code delivery: bump
@version/@date; manual with version history; version shown once at start; zip with a dotted, never-reused version; xmllint --noout on XML; list unrequested changes BEFORE making them; end with a release card (include "legacy zipper used" only for SMF package zips).
- Critique and objections before building; statements over questions; do not mimic his tone; expand acronyms; no invented jargon; Markdown only (never BBCode); PDF never Word; unzip quietly and report findings.
- Checkpoint names get a new
_N, never reused (next is _8). Prefers short conversations and a fresh chat per chapter.
- Terminology: "operator", "query", "reply" (reply = the model's answer).
- Environment: see section 2. Python 3.6 on a POSIX/C locale does not switch to UTF-8, so non-ASCII must be handled explicitly (done in all tools).