Personal Workbench · 个人工作台

Turning the day's incoming clutter into searchable, actionable structured assets

A personal operating system running on Claude Code. Storage is plain markdown under git — no database, no service, no account. A link, a slide deck, or a stray to-do dropped into a Discord channel from a phone gets extracted, classified, templated, and filed, arriving in the repository as a pull request waiting for review.

Storage markdown + git Runtime Claude Code Intake Discord REST polling bridge Rules / checks 7 / 6 Started 2026-07-27
01

What it solves

PROBLEM
Pain pointWhat this does about it
Tasks scattered across chat apps, email, and memory Every action item converges on one ledger, tasks/TASKS.md, whose format a script validates and whose IDs must be unique
Incoming material arrives in every format — web pages, Word, slides, PDFs, recordings scripts/intake/extract.py reduces them all to plain text, which wb-intake then digests
Saving something is not finding it — three months later it is gone Mandatory templates and YAML frontmatter, machine-validated, filed under PARA, with a generated index
You see something useful on your phone and have forgotten it by the time you're at a desk Drop it in the Discord #inbox channel; the bridge polls it back, and Claude digests it and opens a PR
02

The pipeline: from a careless drop to a reviewable diff

PIPELINE

Three intake channels converge on one pipeline. There is a single step in the middle — the wb-intake skill extracts, classifies, and applies a template — and the output fans out to four destinations. Every digest ends in a pull request rather than a write straight to the trunk: the human gate is the one part of this system that cannot be skipped.

INTAKE DIGEST OUTPUT Discord #inbox drop from a phone · polled over REST local inbox/pending/ drag a file into the directory say it in the session feed it inside Claude Code wb-intake extract → classify → template extract.py + enforced frontmatter daily/2026-07-28.md daily log resources/<area>/xxx.md resource card · filed by topic projects/xxx/… project document tasks/TASKS.md action item · one single ledger Pull request · human gate Rule 6: Claude does not merge its own PR 6 check scripts guard CI: frontmatter · ledger · filing · secrets · inbox · rule coverage
FIG. 1 — Three intake channels → one digest step → four kinds of output → one PR. Every write passes through a diff; no path bypasses human review.
03

Why every rule must carry a check script

GOVERNANCE

The daily writer in this system is Claude, not me. A rule written into CONTRIBUTING.md as a paragraph of natural-language advice will certainly be diluted after a few dozen sessions. So there is a meta-rule: every rule must carry a check that runs automatically (Rule 2), and one check verifies that property itself — all_rules_have_checks.py scans the rule list, and any rule without a matching check turns CI red.

  • Rule 1 Filed documents must carry valid frontmatter — frontmatter_valid.py
  • Rule 2 Every rule must carry a check — all_rules_have_checks.py
  • Rule 3 The task ledger must be well-formed with unique IDs — tasks_ledger_valid.py
  • Rule 4 Raw inbox material never enters git — inbox_not_committed.py
  • Rule 5 No secrets in the repository — no_secrets.py
  • Rule 6 Claude does not merge its own PR — the human gate, enforced by the PR flow itself
  • Rule 7 Documents are filed by topic and the index stays current — docs_are_filed.py

One command, python3 scripts/run_checks.py, runs all of them. Green means the skeleton is intact.

04

Three decisions that set the shape

ADR

A decision earns a place in design/ on one test only: will a future me ask "why was it settled this way?", and is the answer absent from the code?

ADR-0001Use Discord as the intake channel, don't build a bot

One user, no ops budget, and when it breaks I am the one fixing it. Discord over Telegram or iMessage because the design needs several channels to sort into (#inbox / #tasks / #daily / #refs) and room to add more bots later, each with its own job. Domestic Chinese platforms would genuinely suit the network conditions and Office previews better, but they would mean maintaining a bridge from day one — against the principle of getting it working before adding complexity.

ADR-0002Markdown + git for storage, not a database or Notion

The real constraint is not features, it is that the writer is an agent. Markdown + git makes every write a diff — the precondition for the human merge gate, and the reason full-text search is just grep. No dependencies, no service, no account; cat will still read it in ten years. The cost is that complex queries need a script, and that a few thousand files will eventually call for an index.

ADR-0003Replace the official channels plugin with a self-built REST polling bridge

With the official plugin fully configured, the bot still never answered. The cause was that it receives over the Discord gateway — a WebSocket — and WebSocket does not get through on this machine: DNS resolved to an address in a Facebook range, and once DNS was fixed the IP and SNI layers were blocked too. After testing seven variations one by one, the conclusion was clean: only WebSocket was stuck; REST was entirely fine. So the bridge became a zero-dependency Python job polling the Discord REST API, with urllib reading https_proxy natively.

Method

The criterion in that investigation was not "it connected" but the HELLO (op:10) frame the Discord gateway must send after a handshake. Watching only for "it connected" yields false positives — three processes sat in SYN_SENT while looking like they were running. Picking a criterion that cannot lie to you is the only thing that saves time on this class of network problem.

05

Keeping it alive on its own

RUNTIME
  • launchd as a resident service — the polling bridge is registered with macOS launchd: it starts at login and restarts on crash. The trap: ProgramArguments must name the interpreter's real path, not the /usr/bin/python3 shim, or the permission check attaches to the wrong binary.
  • A process lock — early on, two polling instances ran at once and overwrote each other's watermark, so messages were duplicated or lost. A file lock now guarantees a single consumer at a time.
  • Permission failures are no longer silent — a denial used to quietly do nothing; it now raises loudly, because "looks like it is running but is not" is the most expensive failure mode.
  • Attachments verified for real — the Discord attachment path only counted once it had carried an industry report and a batch of 26 screenshots end to end, not because the code looked right.
06

Lineage

LINEAGE

The governance mechanisms — every rule carries a check, the agent-loop / agent-verify contracts, keeping CLAUDE.md short — are distilled from an earlier project and from a toolkit built alongside it; the "inbox → PR" pipeline shape is borrowed from that project's reference scout. In other words this is not a workflow designed from nothing, but two sets of practices already proven in real work, converged into the smallest skeleton one person can maintain.

Repo personal-workbench (private) Skills wb-intake / wb-daily / wb-task / wb-brief / wb-loop / wb-verify Checks python3 scripts/run_checks.py → OK: 6 check(s) passed Rev 2026-07-28