The daily work log, with a trigger this time (PENDING-155 filed)

The steward asked for a daily note in Obsidian explaining in plain terms what
we did, decisions taken, and commit references — plus significant insights and
exchanges between the parties. This is Obsidian tracker item 5, the actual goal,
unstarted since 2026-08-19 while four items of frontmatter hygiene stood in
front of it.

Two prior attempts at this exact genre were read before anything was designed,
honouring the tracker's own flag: daily-log/ (4 entries, Mar-Apr 2026) and
sessions/ (3 long-form narratives). Both died inside four weeks. Neither had a
trigger — they were written when someone remembered. So the design question was
never what the note should say. It was what makes it survive.

Answer, steward-chosen at each fork: the note lives at 01. Daily/YYYY-MM-DD.md
with project work under its own H2 inside it rather than in project folders; a
SessionStart hook creates it so existence stops depending on memory; the
executor writes during the session because a wrap-only design inherits the
wrap's failure mode (2026-08-19 died unwrapped); and the wrap finalises rather
than begins.

daybook-ensure.py creates only. It will not touch an existing note — that is the
control the design turns on, and it is tested, along with a positive control
proving the no-dir report distinguishes a missing directory from a broken
function. It also reports failure loudly, because the job it sits beside spent
five months failing behind a > /dev/null 2>&1.

Not revived: the 2025 analogue template — sleep, supplements, homeopathy. Pen
and paper beat it and should keep it. That it failed is a finding, not a gap,
and this note is a different genre rather than its replacement.

PENDING-155 files the steward's idea for the jurist's side: an append-only MCP
surface writing a scratch file the executor folds in. Deliberately NOT a tool on
governance-mcp.py, whose read-only guarantee is a live AST control with a
positive control rather than a comment. A separate one-tool server leaves that
intact, and the vault note stays single-writer — the same lesson the
thinking-mirror taught this morning.

Correction carried into the record: the executor told the steward mid-session
that Claude.app has no filesystem access. False since 2026-08-08. Third instance
today of a constraint asserted from recall where the substrate was one file away.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01JQKeKY9T9d95KpvHwwok8T
This commit is contained in:
David F Glidden
2026-08-23 10:52:35 +02:00
co-authored by Claude Opus 5
parent 3cddc8cf29
commit 073ef8a482
4 changed files with 237 additions and 7 deletions
+26
View File
@@ -4582,3 +4582,29 @@ If the visible work today came from **positional difference between two same-for
**Files affected:** none. Cross-filed to PENDING-89; ladder entry queued to PENDING-141.
**Awaiting:** PENDING-89 to record which of (a)'s two claims the data supports.
## PENDING-155 — A daybook append surface for the jurist: one tool, one file, and not on the governance server
**Date:** 2026-08-23
**Tag:** [PROPOSAL] — a new write interface between two governing parties. Not built. Installing it edits the steward's desktop-app config.
**Summary:** The steward asked whether an MCP surface could let Claude.app append its side of an exchange to a temporary file the executor then folds into the daily work log. It can. It must not be a tool on `governance-mcp.py`.
**Origin.** The daily work log (`01. Daily/YYYY-MM-DD.md`, built 2026-08-23) records what we did, decisions taken, commit references, and — the steward's distinctive ask — *"any significant insights or exchanges that happened between us."* The executor's side is covered: `/wrap-up` §7.5 records it, and jurist material relayed through the session is recorded there too. What is not covered is a jurist-only exchange the executor never sees.
**The refusal this runs into, stated before the workaround.** `governance-mcp.py` (PENDING-82, installed 2026-08-08) is read-only, and its first designed-in refusal reads: *"No tool writes. Audited by AST, not by text search... A write path would collapse three parties into one."* Verified live this session, not taken from the document: the selftest prints `no filesystem-mutating call in this file` **and** a positive control proving the checker detects writes when present. Adding a write tool there would break a control that currently passes, and that control is what makes "read-only" checkable rather than merely claimed.
⚠ **The executor also told the steward, earlier in the same session, that Claude.app "has no filesystem access."** That was wrong — it has mediated read access and has since 2026-08-08. Corrected in place. The error is the one this register keeps finding: a constraint asserted from recall where the substrate was checkable.
**Proposed shape — a separate server, so the invariant above survives untouched:**
- New `~/dotfiles/scripts/daybook-mcp.py`. **One tool:** `daybook_append(text)`.
- **No path argument.** The target derives from today's date, as `governance-mcp.py`'s refusal 4 does: there is no traversal to defend because no path is accepted.
- **Append-only.** Never truncates, never deletes, never reads back. A jurist that could read the file back could audit the executor's folding of it, which is a different and unrequested capability.
- **Target is a scratch file, not the vault** — e.g. `~/dotfiles/claude/daybook/YYYY-MM-DD.inbox.md`. The executor folds entries into the daily note and the scratch file is a handoff, not a record.
- **The vault note stays single-writer.** This is the same lesson the thinking-mirror taught today: two writers to one destination is how a failure goes unnoticed for five months.
**Why a scratch file rather than the note directly.** The steward's own phrasing — *"amend to some kind of temporary file you could read"* — is the better design, and not only for tidiness. A jurist writing the note directly would make the note a shared artifact with no single party accountable for its register; the plain-language discipline the steward asked for would have no owner.
**What this does NOT establish.** It does not give the jurist a way to *modify* governance state, and must not grow one. If a future need looks like "the jurist should be able to write X," that is a new item, not an extension of this one.
**Files affected:** new `~/dotfiles/scripts/daybook-mcp.py`; `/wrap-up` §7.5 (fold step); awaiting steward hand: `claude_desktop_config.json` merge + app restart.
**Recommendation:** Build it, with a selftest carrying the same paired positive/negative controls as its sibling, and hold installation until the daily log has run long enough to show it survives — a jurist inbox for a practice that lapsed would be the fourth abandoned attempt rather than the first durable one.
**Awaiting:** Steward authorization.
@@ -138,6 +138,45 @@ old, and the Daily-Notes plugin is not configured. There is no dormant capture h
practice would be built from nothing. `00a. _inbox` holds 11 notes.
### 2026-08-23 (later) — the capture practice is started, and it is a different genre from the one that failed
**The steward specified it, and the specification excludes the old daily note.** The 2025 template
(sleep, supplements, homeopathy, skin, family, reflection) was an attempt to reconcile an analogue
practice with a digital one; pen and paper won, and **it stays retired — that is a finding, not a
gap.** What is wanted instead: *"a daily note in obsidian which I can refer to, which explains in
relatively simple terms what it is that we have done, decisions taken, and references to any
commits"*, plus *"any significant insights or exchanges that happened between us."*
**Two prior attempts at exactly this genre were found before designing anything** (the tracker's own
"read `daily-log/` and `sessions/` first" flag, honoured): `daily-log/` — 4 entries, Mar 10/12/20 +
Apr 5, sections Summary / What we did / Status / Blocked / Next, already recording an exchange
(*"Thistleweld caught the meta-problem at line 183"*); and `sessions/` — 3 long-form narratives,
linked as children of the daily note. **Both died inside four weeks. Neither had a trigger.** So the
design question was not what the note should say but what makes it survive.
**Built, steward-chosen at each fork:**
- **Location:** `01. Daily/YYYY-MM-DD.md` — its normal home. **Project work under its own H2/H3
inside the note, NOT in project folders** (steward, explicitly).
- **Trigger:** `daybook-ensure.py`, wired into the `SessionStart` hook chain. **Creates only** —
it will not touch an existing note, which is the control the design turns on. 7 checks, incl. a
positive control proving the `no-dir` report is real. Registered via the `update-config` skill;
`jq -e` validates, and a diff against a backup shows only the 8 added lines.
- **Accretion:** the executor writes during the session; `/wrap-up` §7.5 finalises rather than
begins. A wrap-only design inherits the wrap's failure mode — 2026-08-19 died unwrapped.
- **Register:** plain language, explicitly. The hardest part, and named as such in §7.5, because the
executor's default is dense and written for itself.
- **Jurist:** recorded by the executor as exchanges pass through it. **PENDING-155** filed for the
steward's better idea — an append-only MCP surface writing a scratch file the executor folds in.
⚠ Deliberately **not** a tool on `governance-mcp.py`, whose read-only guarantee is enforced by a
live AST control with a positive control, not by a comment.
⚠ **Correction:** the executor told the steward mid-session that Claude.app "has no filesystem
access." False since 2026-08-08 — `governance-mcp.py` gives it mediated read access (PENDING-82,
closed). Same failure class as the two earlier corrections today: asserted from recall where the
substrate was one file away.
---
## Current state — OPEN
@@ -177,10 +216,8 @@ Items 1–4 are the **frontmatter tail**: real, bounded, and *not the goal*. Ite
- **2, 3** are steward-judgement work, not executor work: 73 pointer-form `canonical` values and
213 `related` targets need an eye that knows what the notes *mean*.
- **4** (16 YAML parse errors) has never been in scope and is a session-sized bite.
- **5 — the capture practice — has still not been started**, and the 2026-08-23 check made it
sharper, not smaller: `01. Daily/` holds 11 notes, newest **2025-09-04**, and the Daily Notes
plugin is unconfigured. There is no dormant habit to revive.
⚠ Note for whoever designs it: `00. Compass/00b. Constellations/CapableMind/` already contains
`daily-log/` and `sessions/` folders. **Read those before designing anything** — a capture
practice may already have been attempted here, and re-inventing over it would be the third
duplicate mirror in spirit.
- **5 — the capture practice — STARTED 2026-08-23**, after 4 days as the unstarted goal. Hook +
in-session accretion + wrap finalisation + a first real note. **It is not proven yet:** two
predecessors reached 3 and 4 entries. The measure of this one is whether it is still being written
in four weeks — and §7.5 now requires the wrap to SAY SO if it lapses, rather than letting it fade
a third time in silence.
+39
View File
@@ -239,6 +239,45 @@ that had nothing to do, and that is how five months of silence went unnoticed.
path unreachable from a restricted context), say that too; the diagnosis is in
`session-ledger-2026-04-16.md` and it is macOS Full Disk Access, not git and not rsync.
### 7.5. The daily note — finalise the day's work log
`01. Daily/YYYY-MM-DD.md` in the vault. **A session-start hook (`daybook-ensure.py`) has already
created it**, so this step never begins a file — it finishes one. If the note is still only the
skeleton, that is the failure this step exists to catch: the day's work was never written down as it
happened, and writing it all now from a compacted context is exactly the reconstruction the log is
meant to replace.
**Who it is for, and therefore how it reads.** The steward, on a day when he wants to know what we
did without reading `git log`. **Plain language.** Explain the jargon or drop it. Short sentences.
A reader who was not in the session must be able to follow it. This is the register the steward
asked for on 2026-08-23 — *"relatively simple terms"* — and it is the hardest part of this step,
because the executor's default register is dense, hedged, and written for itself.
**Shape** (steward-specified 2026-08-23):
- `## The day in short` — one paragraph. What actually happened, and why it mattered.
- `## <Project>` — **one H2 per project touched**, H3 beneath if it needs it. Project work goes
under its own heading, *not* into project folders. Put commit hashes here, inline, next to what
they did: `→ f1c91d9`.
- `## Decisions taken` — what was decided and *by whom*, briefly. Cross-project, so it stays global.
- `## Insights and exchanges worth keeping` — the distinctive section, and the one most likely to be
skipped under time pressure. A sentence that changed how we saw the problem; a correction that
landed; a phrase worth keeping. **Record the jurist's contributions here too** — rulings, design
gates and reasoning relayed by the steward reach this session and belong in the record the same
way the executor's do. Claude.app cannot write to the vault itself (its MCP surface,
`governance-mcp.py`, is structurally read-only and audited to stay that way), so this step is
where its side of the exchange gets kept.
- `## Open / next` — what is unfinished, with dates where they exist.
**Accrete DURING the session; do not save it all for the wrap.** A session that dies unwrapped loses
whatever only lived here — 2026-08-19 died exactly that way. The hook guarantees the file; only
writing into it as the day goes guarantees the content.
**Never rewrite a previous day's note.** It is a record, not a document under maintenance.
⚠ **Two previous attempts at this log died inside four weeks** — `daily-log/` (4 entries) and
`sessions/` (3), both Mar–Apr 2026, both abandoned without a note saying so. Neither had a trigger.
If this one lapses, say so in the wrap rather than letting it fade a third time.
### 8. Output
Produce a brief summary for the steward:
+128
View File
@@ -0,0 +1,128 @@
#!/usr/bin/env python3
"""daybook-ensure — the day's work-log note exists before anyone remembers to want it.
Two previous attempts at this log died inside four weeks (`daily-log/` 4 entries, `sessions/`
3, both Mar-Apr 2026). Neither had a trigger; both depended on someone remembering. This hook
removes the remembering: at every session start the day's note exists, skeleton in place, so
writing into it is an append rather than a decision to begin.
Deliberately narrow:
- CREATES only. An existing note is never opened for writing, never rewritten, never
reordered. The prose is the executor's and the hook must not be able to damage it.
- Says so when it fails. The job this replaces died silently for five months behind a
`> /dev/null 2>&1` (see the 2026-08-23 daily note). A hook that cannot write must
announce that, not shrug. Constitutional Constraint #4, at hook scale.
- Always exits 0. Honest reporting must never cost the steward a session.
"""
import datetime, os, sys
VAULT = os.path.expanduser(
"~/Library/Mobile Documents/iCloud~md~obsidian/Documents/David, root-and-branch")
DAILY = os.path.join(VAULT, "01. Daily")
SKELETON = """---
title: "{date} — {dayname}"
type: daily
date: {date}
status: active
tags:
- daily
- worklog
---
# {date} — {dayname}
*A plain-language record of the day's work, so the history is readable without reading `git log`.
Project work sits under its own heading; decisions, insights and open threads are collected at the end.*
## The day in short
## Decisions taken
## Insights and exchanges worth keeping
## Open / next
"""
def note_path(day, daily_dir=DAILY):
return os.path.join(daily_dir, f"{day.isoformat()}.md")
def ensure(day=None, daily_dir=DAILY):
"""-> (status, path). status in {'exists','created','no-dir','error:<msg>'}.
'exists' is not a failure and 'created' is not a success worth announcing loudly —
the caller decides what is worth a line of the steward's attention.
"""
day = day or datetime.date.today()
p = note_path(day, daily_dir)
try:
if not os.path.isdir(daily_dir):
return "no-dir", p
if os.path.exists(p):
return "exists", p
body = SKELETON.format(date=day.isoformat(), dayname=day.strftime("%A"))
# x-mode: refuse to clobber if something raced us between the check and the write.
with open(p, "x", encoding="utf-8") as fh:
fh.write(body)
return "created", p
except FileExistsError:
return "exists", p
except OSError as e:
return f"error:{e.__class__.__name__}", p
def selftest():
import tempfile, shutil
ok = fail = 0
def chk(label, cond):
nonlocal ok, fail
print(f" [{'ok ' if cond else 'FAIL'}] {label}")
ok, fail = (ok + (1 if cond else 0), fail + (0 if cond else 1))
d = tempfile.mkdtemp()
try:
day = datetime.date(2026, 8, 23)
st, p = ensure(day, d)
chk("creates the note when absent", st == "created" and os.path.exists(p))
txt = open(p, encoding="utf-8").read()
chk("skeleton carries the ISO date and weekday", "2026-08-23" in txt and "Sunday" in txt)
chk("skeleton is spec-conformant (type/status/tags present)",
"type: daily" in txt and "status: active" in txt and "worklog" in txt)
# THE control that matters: an existing note must survive untouched.
open(p, "w", encoding="utf-8").write("PROSE THE EXECUTOR WROTE\n")
st2, _ = ensure(day, d)
chk("does NOT overwrite an existing note [the whole point]",
st2 == "exists" and open(p, encoding="utf-8").read() == "PROSE THE EXECUTOR WROTE\n")
chk("reports 'exists' rather than 'created' on the second run", st2 == "exists")
missing = os.path.join(d, "nope")
st3, _ = ensure(day, missing)
chk("reports a missing directory instead of creating one [no silent mkdir]",
st3 == "no-dir" and not os.path.exists(missing))
# positive control: the failure path must be reachable, or 'no-dir' proves nothing.
st4, _ = ensure(day, d)
chk("the same call succeeds where the directory exists [positive control — proves"
" 'no-dir' reports the directory, not a broken function]", st4 == "exists")
finally:
shutil.rmtree(d, ignore_errors=True)
print(f"\n{'SELFTEST PASS' if not fail else 'SELFTEST FAIL'} — {ok} ok, {fail} failed")
return 0 if not fail else 1
if __name__ == "__main__":
if "--selftest" in sys.argv:
sys.exit(selftest())
status, path = ensure()
if status == "created":
print(f"DAYBOOK — created {os.path.basename(path)}")
elif status.startswith("error") or status == "no-dir":
# Loud on purpose. The predecessor's silence cost five months.
print(f"DAYBOOK — COULD NOT WRITE the day's note ({status}). "
f"Expected at: {path}")
sys.exit(0)