Day 8 — 'Trust the roadmap, not the memo.'
Twenty-eight commits, ~1903 → 2360 tests (+457). Sk.1 (skills system) closed end-to-end across three days. A.5 packaging shipped days 1-3 of 5 (`pip install werbos` wheel-ready, `werbos init` scaffolds the data dir, `user.md` hydration into ContextBuilder). Vision capture landed: `/see` REPL slash + macOS screencapture + per-session pending-images buffer. Persona surface broadened: composer mirror chip moved into Settings as an operator-global default; private-chat mode (full surface — server + API + CLI + REPL + SPA); html-expert persona + new `--persona-name` flag for wheel-friendly opt-in. Anti-fabrication clause tightened twice after the 'hi' confabulation. Plus the day's load-bearing lesson: two captured memories went stale within 24 hours — K.0 was 'in progress' in memory but already closed end-to-end on main; streaming follow-ups had me re-implementing a filter that was already shipped server-side. Reverted same-session. The roadmap is the source of truth; memory is a point-in-time observation.
“trust the roadmap, not the memo.”
Day 7’s lesson was the persona is the artifact — F.2 dialed the voice and K.0 stamped the floor of the memory ladder. Day 8 broadens the surface area in four directions and runs into the day’s actual discovery in the process: at this work pace, memory between sessions ages out faster than I can refresh it. Two memories that read “in progress” turned out to be already-closed, and one of them nearly cost a duplicated implementation.
The four directions:
- Sk.1 — Skills. A SQLite-backed skill store with a markdown body
per skill at
~/werbos/skills/<name>.md, a 3-way autonomy toggle (off / propose / auto), a detector that clusters chat-archive patterns into candidate skills, and an auto-curator that creates matched skills + purges stale ones. The auto-curator publishes warden Alerts when the mode flips, because silent drift is the failure mode for self-modifying systems. - A.5 — Packaging. Three days of a five-day sprint to make
pip install werbosreal. Day 1 collapsed eight sibling repos into git+https SHA pins, bundled the SPA dist as package data, and bumped license format to PEP 639. Day 2 shippedwerbos init— scaffolds~/werbos/withchats/ images/ personas/ skills/ tasks/subdirs, copies a templateduser.mdfor the operator to fill in, probes Ollama for reachability, and prints actionable install hints when it isn’t running. Day 3 wireduser.mdthroughContextBuilderso werbos actually KNOWS the operator across all sessions; emits aKIND_USER_PROFILE_LOADEDaudit event with{path, sha256, byte_size}(metadata only — never the body). Day 4 (Docker) and Day 5 (real PyPI publish) are queued; Day 5 is blocked on the visibility decision for the four phase-2 sibling repos. - Vision —
/see. Awerbos.visionpackage wrapping macOSscreencapture(region / full / window modes), and a REPL/seeslash that captures, base64-encodes, and attaches the PNG to the live ChatSession’s pending-images buffer. The user’s next typed message becomes the prompt; the next AI turn drains the buffer onto its UserMessage. Without--vision-modelthe image still attaches but the text-only model can’t see it — the slash surfaces that fact in its response. Lazy imports keep the chat module’s startup time low for users who never touch/see. - Persona ladder grows a named-rung surface. The composer
mirror chip moved into Settings as an operator-global default
(one place to set “my baseline”; per-chat overrides still work).
Private-chat mode shipped end-to-end (server + API + CLI +
REPL + SPA — turns flagged
?private=1archive nothing). And thehtmlpersona — a voice-layer character mined fromtom.html’s actual style (single-file, no build pipeline, CSS custom properties, semantic markup, system fonts, textContent over innerHTML) — landed alongside a new--persona-name <slug>flag that resolves packaged personas by name. Wheel-friendly: post-pip install werbos, the persona file lives inside the package and there’s no obvious on-disk path to point--persona-fileat.
Past midnight — the day-8 queue captures
Two docs(backlog) commits at 00:13 and 00:55 captured the day’s
shape: Sk.1 skills + 3-way autonomy toggle, private chat,
save-current, persona-slider Settings mockup. The shape held.
Morning — Sk.1, private chat, Settings cleanup
Sk.1 shipped in three commits (49161ec, cd92678, ace6073).
Day 1: werbos.skills.store (SQLite, one row per skill, body on
disk at ~/werbos/skills/<name>.md, integrity via body_sha256,
operator gates via enabled), the werbos.skills.loader (reads
the body file by name, returns SkillFileError on tamper), a
discovery ranker (rank_skills over keyword triggers), and the
/skill REPL slash (list / show / fire). Day 2 added the
3-way mode toggle in the store + a chat-side SkillProvider that
ranks the user’s last message against enabled skills, reads the
top-3 bodies, and returns a single markdown chunk to append as a
SystemMessage after the persona prompt. Day 3 closed the auto-
curator surface: detector.detect_from_archives clusters chat
patterns by Jaccard over N-grams; autocurator.AutoCurator
applies candidates + stale-skill purges; werbos skills scan
exposes the surface to the operator. Mode flips fire warden
Alerts via LoggingAlertSink because auto-mode is loud by
design.
Settings cleanup ran in parallel. 0395b2d moved the per-chat
mirror chip out of the composer header and into Settings as a
global default — the operator has ONE place to set “my baseline,”
not seventy-five tabs each remembered separately. e1fc1bf
deleted the now-redundant chip. 7573c4a shipped private-chat
mode end-to-end (?private=1 query param; chat.private_mode
session attribute; archive write skipped at finalize; the SPA
gets a “private” badge). And four small fixes — the hooks-rule
violation in ChatView (725ae59), the chat-header padding
clipping the Settings FAB (8ceace5), window.confirm becoming
the proper ConfirmModal (8579b09), and the composer chip
removal — closed the day’s UI dust.
Mid-morning — persona discipline + dogfood UX
The “hi” bug. llama3.1:8b on a content-thin prompt confabulated
“I’ll answer in plain text from now on. That’s correct, the
result of the calculation is 7.” The model had seen the
calculator tool in its registry + the “evaluating math” example
in the discipline paragraph, and filled space by inventing a
calculator result that never happened. 0074eef added an
anti-fabrication clause to the default persona; f5dbf07
tightened it with concrete “hi!” counter-examples. The discipline
is type-shaped — do not reference a tool result that wasn’t
produced in this turn. 6dd146a shipped a persona_slider eval
suite to gate the discipline going forward.
1691623 fixed five chat-UX bugs from the day’s dogfood —
chess/checkers boards collapsing to 1-row layouts (1f5655c,
980c9c7), and a handful of mobile-scroll regressions.
Afternoon — packaging opens + ergonomics
5a7e347 fixed blackjack AI freezing mid-hand (the AI loop
needed an explicit yield point for hands the dealer played out
silently). 00f17cf shipped ? / /help as a quick-reference
card in the chat REPL — slashes grouped by surface (chat, eval,
notebook, skills, vision, etc.). e7c57e9 opened the packaging
sprint: pyproject consolidates all eight siblings as git+https
SHA pins, bundles werbos/ui/dist/** as package_data, license
format updated for PEP 639. 08c7aee extracted the blackjack AI
loop to a library and added two new personalities (the dogfood
loop on the games surface continues to feed the v5+ training
roadmap).
Evening — A.5 days 2-3, vision, persona-name
7fb0e42 (A.5 day 2): werbos init scaffolds ~/werbos/ and
its standard subdirs idempotently, copies the bundled
default persona if personas/default.md is missing, writes a
templated user.md if it doesn’t exist, probes Ollama at
http://localhost:11434/api/tags with a 2s timeout, and prints
SUCCESS / CONNECTION_REFUSED / TIMEOUT messages with actionable
install hints. --strict flag for CI; the K.1 project flow
preserved at werbos init project. 47 new tests; 2299 passing.
e3848f6 (A.5 day 3): werbos.personas.user_profile with a
UserProfile dataclass (text, byte_size, sha256, path).
ContextBuilder injects user.md AFTER persona + project_context
and BEFORE history. SessionRegistry’s session config gains
user_profile_path; serve CLI plumbs it. KIND_USER_PROFILE_LOADED
event records metadata; never the body. 16 KB cap; over-cap
returns truncated profile + warning flag.
fb789b5 bumped the pitts pin past UserMessage.images (a
pre-vision-work blocker — 153 pre-existing session-test failures
cleared in one bump).
b2e417b shipped /see: werbos.vision.capture.capture_screen
returns a CaptureResult(path, image_b64, size_bytes, mode) from
shelling out to macOS screencapture with mode-specific flags
(-i -x for region, -x for full, -W -x for window). ESC
cancellation detected via missing/empty file. werbos.vision
package re-exports the public surface; the chat REPL’s
_handle_see_slash attaches captures to the session’s pending-
images buffer. New session methods: attach_pending_image,
pending_image_count, clear_pending_images. New
_build_vision_provider factory mirrors the serve-CLI pattern.
33 new tests across capture / slash / pending-images.
aa76b62 then c1c6e91. The day’s discovery, in two commits.
The first shipped a UI-side mid-stream tool-call detector to
ChatView, based on a memory note that said “mid-stream JSON
detection is open.” The second reverted it once I checked the
roadmap and saw the work was already shipped server-side at
werbos.session._stream_filter.filter_tool_call_json, wired into
ChatSession.step_stream. The SPA never sees raw { tokens
because the server filter swallows them and emits a
PhaseEvent("tool_pending", "calling <name>...") in their
place. Memory was three days stale; the roadmap was current.
Lesson logged.
3d91f10 shipped the html persona + the --persona-name
flag. Persona file mined from tom.html’s actual style — the
canonical single-file reference in this repo at 489 lines, zero
dependencies. Anti-patterns named explicitly: no Vite, no
Tailwind CDN, no localStorage by default, no speculative
abstractions. Precedence: --persona-file > --persona-name > --persona > built-in default. Unknown name fails loudly via
SystemExit (typo = error, not silent fallback).
AHAs
- Memory is a snapshot; the roadmap is the source. The K.0
memo said “Day 1 shipped, Day 2+ left.” git log on the same
branch said all three days closed end-to-end on 2026-05-08.
The streaming follow-ups memo said item 1 was open; the
ROADMAP at line 570 said
[shipped 2026-05-08]. At the work pace of multiple sprints/day, captured “what’s next” notes age out within hours. Habit: when a sprint memo claims in-progress, verify against the roadmap and git before acting. - Self-modifying systems must be loud. Sk.1’s auto-mode
fires warden Alerts on every mode flip. Silent automation
is the failure mode — when the curator decides to delete a
skill the operator wrote, the operator hears it. The
LoggingAlertSinkdefault makes this work without configuration. - The wheel format is a constraint that shapes design. A.5
day 2’s
werbos initexists because once werbos is pip-installed, there’s no source tree to give the user a template by example. The init command IS the template. Same shape applies to--persona-name: post-install, there’s no obvious filesystem path for--persona-file, so the surface has to load by slug from the package data. - The user.md audit shape mirrors a B2B compliance story.
KIND_USER_PROFILE_LOADEDrecords{path, sha256, byte_size}but never the body. Same shape as Tom’sKIND_TOM_ATTESTATION(history_length: 0,notebook_size_bytes,turn_index). Auditors get an envelope they can verify; the contents stay on the operator’s box. Local-first as your DATA, not your compute — applied to the audit surface itself. - The single-file HTML is the design vocabulary. The
htmlpersona pinned style by mining a real artifact (tom.html), not by inventing rules.:rootcolor tokens,box-sizing: border-boxuniversally,grid-template-areasfor top-level layout, 14px base + 1.5 line-height, border-radius 14 / 999 / 6-8 vocabulary. Future single-file demos will share these decisions because the persona will keep them in scope. - The CLI has fingers now.
/seemakes screen capture a chat-native primitive. The next move is hookingVisionContextProviderto the user’s preferred vision model (per-agent routing perfeedback_local_first_definition); the bytes stay on the box, the inference can route to whatever fits. Day 8’s vision work landed the local half of that loop.
Numbers worth keeping
- 28 commits on main between
2afc5db(00:13) and3d91f10(19:33). - ~1903 → 2360 tests (+457 net across cli/session/server/ personas/skills/vision/cli flag tests). Pre-existing schedule-test calendar-rot (4 failures) accumulated as today’s date crossed 2026-05-09T18:00:00Z.
- 3 sprint days closed: A.5 days 1-3 of 5. Sk.1 closed fully across days 1-3.
- 1 commit reverted same-session (the duplicated UI stream filter). Net new files added by the revert: 0.
- HEAD:
3d91f10.
Continuation — late evening (6pm to past midnight)
“protocol first, runtime second — for the third time today.”
The day was supposed to close at the 3d91f10 HEAD. It didn’t.
Six more commits landed between 22:31 and 00:13 — cross-midnight by
13 minutes — and they’re notable enough to be their own block:
Soc.1 closes (TwitterAdapter, the fourth and hardest of the
four social bridges), P.1 opens AND ships its first two days
back-to-back (file parsers — Phase 13’s first sprint, the missing
“werbos can read non-image files” capability), and a games sweep
that picked up real AI for everything on the games roadmap except
Risk.
Soc.1 day 5 — TwitterAdapter (73cf87e)
The fourth and final platform on the SocialAdapter Protocol. Three security envelopes layer in — more than any of the prior three adapters carry combined:
- OAuth 1.0a request signing (HMAC-SHA1 over an RFC-5849 base string) for outbound DM sends. Twitter’s DM endpoints require User Context auth, not Bearer.
- CRC handshake (HMAC-SHA256 of a
crc_tokenunderconsumer_secret, base64) for webhook registration + revocation pings. - Webhook signature verification (HMAC-SHA256 of body under
consumer_secret, base64,sha256=prefix) on every inbound POST.
Plus a paid-API gate: Twitter’s basic AAA tier with DM endpoints runs $100/mo as of 2026, so the adapter ships behind an env-flag the operator flips when ready. The runtime contract is identical to the other three; only the wire-format work was harder.
docs/integrations/twitter-setup.md walks through Developer
Account, App creation under a Project, AAA tier upgrade, Account
Activity API webhook registration, and CRC verification — the
parts a generic “twitter API” tutorial doesn’t cover because most
tutorials stop at Bearer-token search APIs.
The Soc.1 sprint closes with this commit. Four platforms across
five days, one Protocol, ~2300 lines of net-new integration code.
The morning roll-up commit f8f7ad6 landed the README Status
table + architecture-doc subsystem section for the Soc.1 v1
surface; this commit drops the fourth adapter into that frame.
P.1 days 1-2 — file parsers (9487e09, 4381fde)
Phase 13 opens. werbos already SEES via /see +
VisionContextProvider (images), READS code + chat archives via
the existing tool surface, and HEARS (planned) via the audio
stack. P.1 fills the gap for everything else the user hands it:
HTML, JSON, JSONL, TOML, Markdown, plain text, CSV, TSV, DOCX,
PPTX, ZIP / TAR / GZ archives, PDF, XLSX. Twelve formats; 10
stdlib-only, 2 (PDF + XLSX) gated behind a parsers optional-
dependency extra (pip install werbos[parsers]).
The pattern is the same one the orchestration plane and social
bridges use: protocol first, runtime second. One FileParser
Protocol in werbos/parsers/base.py; format-specific
implementations layer in independently. Three discovery surfaces
share the same registry:
/parse <path>REPL slash (operator surface)./parse <url>REPL slash, day 2 (operator surface, remote). Fetches via a newweb_fetch_rawhelper that mirrorsweb_fetch’s SSRF posture — same DNS-pinned transport (M2 + R2 closed) — but returns raw bytes + Content-Type up to a 50 MB cap so the registry can dispatch by mime, falling back to URL- path extension when the server reportsapplication/octet-streamor omits Content-Type.parse_filepitts.Tool (agent surface — model invokes when the user references a file path).- Auto-trigger inside ChatSession, day 2: a new
attachments=parameter onstep()/step_stream()takes a tuple ofAttachmentSpec(lightweight NamedTuple — clean layering overwerbos.integrations.Attachment). A new_run_attachment_interludemirrors_run_vision_interludeexactly: runs BEFORE the vision interlude (so a PDF + screenshot in one turn composes as[ATTACHMENT CONTEXT]then[VISION CONTEXT]then UserMessage), inserts extracted text as a SystemMessage immediately before the latest UserMessage, and the post-stream history filter preserves it across turns the same way[VISION CONTEXT]is preserved. Per-attachment failures degrade to per-attachment failure notes — a corrupt PDF in a 2-attachment turn never aborts the user’s reply.
Day 2 also wires the social bridges through:
SocialMessageRouter._route_to_session translates non-image
Attachment records into AttachmentSpec and forwards them
through the new channel. Telegram and WhatsApp file shares now
reach the model as parsed text automatically; Slack will too
once its file_share event path lands (Day 6+ on the Soc.1
roadmap).
Default-deny sandbox at every entry: 50 MB per-file cap,
path-traversal anchored to operator-allowed roots when set,
zip-bomb defense (depth + total-bytes + per-entry caps).
Optional-dep gracefulness: missing pypdf / openpyxl raises
ParserUnavailable("pip install werbos[parsers]") at parse time,
not import time, so the registry boots cleanly without them.
126 new tests across the two days (96 day 1 + 30 day 2). New
audit event KIND_ATTACHMENT_PARSED (metadata only — no body),
new PhaseEvent("parsing") / PhaseEvent("parsing_failed") for
streaming UI placeholders.
Games — connect4 oracle + UI AI sweep (e4b3a2a, f4eee92)
Two parallel pieces of games tooling that have nothing to do with the chat surface but were the visible polish on the games roadmap.
werbos/games/connect4_oracle.py — a real Connect 4 negamax
solver to replace the depth-3 labeler that produced the
random-level v1-c4 LoRA. Negamax + alpha-beta + transposition
table (with bound-flag) + center-first move ordering, optional
iterative deepening for time-bounded play. Strength: 5/6 vs
heuristic_policy as X at depth 5; 100% vs random at depth 4.
Latency on 16GB M1 from empty board: depth 8 in 68ms, depth 10
in 545ms; iterative deepening hits depth 10 inside a 1s budget.
Drop-in for scripts/build_c4_corpus.py when the v2-c4 retrain
slot opens — replace oracle_move(board, mark, depth=3) with
negamax_move(board, mark, depth=10) and the generic-rationale
failure mode goes away.
blackjackAi.ts / poker.ts / chessAi.ts / checkersAi.ts
in the React side — five games picked up real AI. New
“perfect” 7th blackjack personality with exact dealer-outcome
enumeration over the remaining-by-rank deck (10-bucket collapse:
A, 2..9, T) with bound-flag transposition; computes
EV(stand/hit/double) and argmaxes; conditions on the
no-dealer-BJ rule (BlackjackView settles naturals on deal, so
when up=A or T the BJ-completing hole is excluded from the
hole-card distribution). 15/15 vs the single-deck S17 chart,
~0.5ms per decision. Poker gets monteCarloEquity() exported
plus a rewritten pickAiAction over uniform-range Monte-Carlo.
Chess + checkers get strength ladders. The companion is now
playable across every game on the roadmap except Risk.
AHAs (continuation)
- The protocol-first pattern is now the project’s design vocabulary. Three sprints (orchestration plane Phase 11, social bridges Phase 12, file parsers Phase 13) all opened with the same shape: define the Protocol, ship a default-deny sandbox, layer runtimes in independently, audit-event with metadata-only payload. The third application of a pattern is when it stops being a pattern and starts being a habit.
- Day 1’s “no auto-trigger yet” was a load-bearing constraint. P.1 day 1 deliberately did not auto-parse on attachment receipt — only on explicit operator/agent ask. That kept the day-1 scope finite, surfaced sandbox + soft-import + recursion bugs in isolation, and let day 2 wire the auto-path on top of a proven base. Same as how the orchestration plane shipped the store + handler + dispatch in three separate sprints rather than one mega-PR.
web_fetch_rawis two helpers stacked: one to pin, one to fetch. The agent’s existingweb_fetchhas a 2 MiB safety cap that’s right for prose but wrong for files. Rather than parameterize and risk widening that surface, P.1 day 2 added a sibling_pinned_request_raw(50 MB cap, raw bytes + headers) next to_pinned_request. The SSRF guard layer is reused; the body-handling is per-call-site. Refactoring rule: don’t loosen a hardened helper for a softer use case; build a sibling.- Twitter’s three-envelope security is the price of legacy.
OAuth 1.0a + CRC + HMAC-SHA256 webhook-sig is more crypto
surface than the other three adapters combined, and it exists
because the Account Activity API was bolted onto a 2010-era
REST surface. The pattern: when an integration’s auth shape is
more elaborate than the function it performs, the platform is
paying for backwards compatibility, and you’ll feel that cost
in the adapter. Documented in
docs/integrations/twitter-setup.mdso the next operator doesn’t re-derive it. - werbos can now READ. The clean way to say what Phase 13 v1 is: take any file the user hands you (HTML, doc, sheet, archive, PDF), turn it into model-readable text, audit the shape but not the body. Together with vision (images), code reading (chat archives + repo trees), and the planned audio stack, the sensory surface starts to feel complete.
Numbers worth keeping (continuation)
- 6 commits between
f8f7ad6(22:31) and4381fde(00:13) —f8f7ad6,73cf87e,9487e09,e4b3a2a,f4eee92,4381fde. Cross-midnight by 13 minutes; this block reads as Day 8’s late session, not Day 9’s morning, because the work thread is continuous from3d91f10forward. - +~270 net tests across the block: 30 (Twitter) + 96 (P.1 day 1) + 30 (P.1 day 2) + 20 (connect4 oracle) + TS-side game-AI tests. Full sweep: 2360 → 2631 passing (4 pre-existing schedule calendar-rot failures unrelated).
- 2 sprints’ status changed: Soc.1 closes (5/5 days). P.1 ships 2/N (day 1 + day 2; Day 3 is the v1 polish slot — content-sniffing for binary headers, OCR fallback for image-only PDFs).
- 0 new runtime dependencies for the parser core (stdlib only
for the 10 default formats). Optional
pypdf>=5andopenpyxl>=3.1gated behind theparsersextra; PDF + XLSX raiseParserUnavailable("pip install werbos[parsers]")at parse time when missing. - 3 protocols, one shape:
Tool(orchestration / agent surface),SocialAdapter(Phase 12),FileParser(Phase 13). Each one ships in roughly the same skeleton — protocol + registry + default-deny + audit envelope — and the third one took a third of the design effort the first one did. - HEAD:
4381fde.