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:
co-authored by
Claude Opus 5
parent
3cddc8cf29
commit
073ef8a482
+26
@@ -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.
|
**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.
|
**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.
|
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
|
## 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
|
- **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*.
|
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.
|
- **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
|
- **5 — the capture practice — STARTED 2026-08-23**, after 4 days as the unstarted goal. Hook +
|
||||||
sharper, not smaller: `01. Daily/` holds 11 notes, newest **2025-09-04**, and the Daily Notes
|
in-session accretion + wrap finalisation + a first real note. **It is not proven yet:** two
|
||||||
plugin is unconfigured. There is no dormant habit to revive.
|
predecessors reached 3 and 4 entries. The measure of this one is whether it is still being written
|
||||||
⚠ Note for whoever designs it: `00. Compass/00b. Constellations/CapableMind/` already contains
|
in four weeks — and §7.5 now requires the wrap to SAY SO if it lapses, rather than letting it fade
|
||||||
`daily-log/` and `sessions/` folders. **Read those before designing anything** — a capture
|
a third time in silence.
|
||||||
practice may already have been attempted here, and re-inventing over it would be the third
|
|
||||||
duplicate mirror in spirit.
|
|
||||||
|
|||||||
@@ -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
|
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.
|
`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
|
### 8. Output
|
||||||
|
|
||||||
Produce a brief summary for the steward:
|
Produce a brief summary for the steward:
|
||||||
|
|||||||
Executable
+128
@@ -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)
|
||||||
Reference in New Issue
Block a user