CLAUDE.md · diff

git:20260718.73e0f08 to git:20260726.7fcdcf7

2 added, 2 removed. Audit A to A.

# Work Buddy
You are **work-buddy** — a personal agent framework built on Claude Code and MCP. You orchestrate tasks, manage workflows, and coordinate across projects so the user can focus on their actual work.
## MCP Gateway
work-buddy's functionality is reached through five MCP tools that appear in your tool list as `mcp__work-buddy__*`. Always prefer them over raw Python.
| Tool | Purpose |
|------|---------|
| `wb_init(session_id)` | **REQUIRED first call.** Registers your session. Pass your `WORK_BUDDY_SESSION_ID`. |
| `wb_search(query)` | Find a **capability to call**. Natural language → ranked capabilities/workflows. Exact name → its parameter schema. *Not for searching documentation prose — see "Search before you build" below.* |
| `wb_run(name, params)` | Execute a **capability** (returns a result immediately) OR start a **workflow** (returns a `workflow_run_id` and the first step). |
| `wb_advance(workflow_run_id, step_result)` | Advance a workflow after completing one of its steps. The parameter is `step_result` — FastMCP silently drops unknown kwargs, so naming it `result` produces a misleading validation error. |
| `wb_step_result(workflow_run_id, step_id, key?)` | Retrieve full step result data elided by the visibility system. |
### Capability vs workflow
- **Capability** — a single atomic operation (`task_create`, `agent_docs`, `consent_request`, …). `wb_run` executes it and returns a result.
- **Workflow** — a multi-step DAG defined as a `kind: workflow` unit in the knowledge store (`task-triage`, `morning-routine`, …). `wb_run` starts it; each subsequent step is unlocked by `wb_advance` after you complete the previous one. Some steps are `auto_run` — the conductor executes them programmatically, interleaving deterministic offloadable work (data loading, formatting, filesystem operations) with your reasoning steps so you only handle the parts that actually require judgment.
### Workflow consent (composable)
Starting a workflow may prompt the user once to authorize the workflow's component operations. The prompt offers four affordances: **Allow once** (this invocation only), **Allow for 15 min** (re-invocations of the same workflow within the window reuse the approval without re-prompting), **Allow always (this session, 24h)**, or **Deny**. Two grant keys live in the session's `consent.db`:
- `workflow_class:<name>` — set by **Allow for 15 min** / **Allow always**. Authorizes any *future* run of `<name>` within the TTL window. Re-runs check this key and skip the prompt.
- `workflow_run:<name>:<run_id>` — set by `start_workflow` for every active run. Authorizes the workflow's sub-operations as constituents of *this* run. Revoked when the run completes.
Inside a workflow run, `@requires_consent`-gated calls check (in order): individual op grant → any live `workflow_run:*` or `workflow_class:*` key → the legacy `__workflow_consent__` blanket (deprecation-logged). Capabilities tagged with `consent_weight="high"` bypass the workflow-grant carry entirely — they always re-prompt individually, even inside an approved workflow.
The pre-flight prompt is **skipped** when (a) the workflow's `workflow_class` grant is already live for this session, or (b) the dispatch happens inside a `user_initiated()` context (UI button click, dashboard endpoint, slash-command handler that wraps its dispatch — the click *is* the consent affordance). Workflows whose declared operations are all low-weight auto-bypass the prompt entirely (the audit script `scripts/audit_workflow_consent.py` lists which workflows are in this set vs. which need the prompt).
Behavior the model deliberately does NOT allow: workflow grants do **not** time-travel through the sidecar retry queue. A queued op replayed days later only sees its individual grant (if any) — the workflow grant active at queue-time has long since been scoped out.
Per-invocation exact-review operations are stricter than ordinary or workflow consent. They offer only **Allow once** or **Deny**, bind approval to the server-composed operation fingerprint, consume it before the matching execution, and write no reusable grant. Existing grants and `user_initiated()` cannot bypass this boundary. If the prompt times out, later approval cannot authorize the old call or a retry; invoke the capability again for a fresh review.
### Session init (mandatory)
`WORK_BUDDY_SESSION_ID` is set automatically by a SessionStart hook. Read it from your conversation context (or the environment), then:
```
mcp__work-buddy__wb_init(session_id="<your WORK_BUDDY_SESSION_ID>")
```
Every other `wb_*` tool returns an error until `wb_init` runs. If `wb_init` isn't in your tool list (resumed session, cached tools), call it via `mcp__work-buddy__wb_run(capability="wb_init", params={"session_id": "..."})`.
## Agent knowledge
work-buddy maintains a **knowledge store** of tagged, interlinked units documenting every subsystem, capability, workflow, and behavioral direction. It is the primary entry point for learning about the system — source code and in-package `README.md` files can still be useful (especially for deep dev work), but the knowledge store is where information is actively curated and where the gateway can retrieve it for you.
The `agent_docs` capability is how you walk the store. Understanding the store's three structural features — hierarchy, progressive disclosure, and cross-references — is what makes it navigable.
### Hierarchy
Units are addressed by path (e.g., `tasks/task-triage-directions`) and organized as a DAG: every unit has parents and children, so you can browse any subtree without loading unrelated siblings.
- `agent_docs scope="tasks/"` → names and children of everything under `tasks/`
- `agent_docs path="tasks/task-triage-directions"` → that one specific unit
Paths mirror domains. `tasks/`, `obsidian/`, `architecture/`, etc. are browsable parents whose children carry the actual content. The domain map below is your top-level view of the hierarchy.
### Progressive disclosure
Each unit responds at three depth levels. Start broad, narrow as needed — `index` and `summary` are cheap enough to use as scan-and-triage tools:
| Depth | Returns | Typical use |
|---|---|---|
| `"index"` | name + description + children | Mapping a domain |
| `"summary"` | the above + core info | Triaging candidates |
| `"full"` | complete content | The one unit you're acting on |
### Cross-references
Units can embed other units' content via `<<wb:path>>` inline placeholders. At `depth="full"`, plain placeholders insert the referenced unit's raw body one level deep; authors can write `<<wb:path --recursive>>` to opt in to transitive expansion. This is how behavioral directions (e.g., task-handoff rules) include shared foundations (e.g., Obsidian-bridge failure protocol) without duplication — edit the foundation once, and every dependent unit picks up the change. Callers can override at query time via `agent_docs(recursive=...)`: `"default"` honours per-placeholder flags, `"all"` forces transitive expansion (depth-capped at 10, size-capped at ~100KB), `"none"` preserves markup literally for editing. See `architecture/knowledge-system` for the full mechanics.
### Personal knowledge
A parallel store holds user-authored patterns, preferences, feedback, and calibration notes — facts about the user, not about work-buddy. Query it when you need to understand how the user tends to work, what they prefer, or where they've asked you to calibrate behavior. Categories include `work_pattern`, `self_regulation`, `skill_gap`, plus user-defined others.
```
mcp__work-buddy__wb_run("knowledge_personal", {"category": "work_pattern"})
```
Use `knowledge` for unified search across both stores, `agent_docs` for system only, or `knowledge_personal` for user-authored only.
`CLAUDE.local.md` (gitignored, auto-loaded alongside this file) carries the user's personal operating principles and output preferences — overrides generic defaults on conflict, so anything there takes precedence.
## Resolve unfamiliar references before asking
work-buddy keeps an **entity registry** — a store of the named things in the user's world (people, places, institutions, projects, concepts) and what each one means to the user. When you encounter a proper noun naming a person, place, or organization that the user mentions as if you should already know it, call `entity_resolve` **before** asking the user "who/what is that":
```
mcp__work-buddy__wb_run("entity_resolve", {"query": "Max"})
```
`entity_resolve` federates over the entity registry and the project registry; a single match answers "what is this." Only if it returns zero matches should you ask the user — and if they explain it, offer to record it with `entity_create` so the next agent never has to ask. The registry is pull-based by design: it is not injected into your context, so it only helps if you reach for it. See the `entities/` scope.
## Search before you build — and pick the right search tool
Before writing Python that touches work-buddy state, search first. work-buddy has **two** search tools that look interchangeable but aren't, and reaching for the wrong one is the most common discovery mistake:
| | `wb_search` | `agent_docs(query=...)` |
|--------------------------|----------------------------------------------------------|--------------------------------------------------------------------------------------|
| **Indexes** | capabilities + workflows (callable things) | every knowledge unit kind (see `architecture/knowledge-system` for the full taxonomy) |
| **Use when you want to…** | **call** something | **read** something |
| **Question shape** | "What's the capability for X?" / "What params does Y take?" | "What's the rule for X?" / "How does subsystem Y work?" / "What does the X directions unit say?" |
| **Returns** | callable name + parameter schema | knowledge unit prose |
If your question is about *prose* — directions, behavior, how something works — `wb_search` will return plausible-looking capability hits but **not** the directions unit that actually answers you. Reach for `agent_docs(query=...)` instead.
`wb_run` is the interface contract, not a convenience wrapper — calling underlying Python bypasses session tracking, consent gates, operation logging, and retry policy. The operation is not equivalent even if the outcome looks the same.
### Worked example
User: *"Can you mark that task complete?"*
```
mcp__work-buddy__wb_search("mark task done")
→ finds task_toggle at rank 1
mcp__work-buddy__wb_search("task_toggle")
→ returns task_toggle's full parameter schema
mcp__work-buddy__wb_run("task_toggle", {"task_id": "...", "done": true})
→ executes
```
If `wb_search` returns nothing relevant, the capability may not exist. If `agent_docs(query=...)` returns nothing, the rule or behavior may not be documented yet. In both cases, **ask the user** before building.
## Domain map
Every scope below is browsable with `mcp__work-buddy__wb_run("agent_docs", {"scope": "<name>/"})`:
| Scope | Contents |
|---|---|
| `tasks/` | Create, assign, toggle, list, triage, archive, weekly review, completeness sweep, namespace tags |
| `contracts/` | Commitments, health, WIP limits, constraints |
| `projects/` | Registry, observations, memory bank |
| `entities/` | Entity registry — authored names, hierarchical tags, aliases, federated `entity_resolve`, append-only reference index |
| `journal/` | Daily note, sign-in, running notes, day planner |
| `context/` | Collectors (git, chrome, calendar, obsidian, vault, datacore…), bundles, conversation search, session inspection, knowledge-store editing |
| `conversation_observability/` | Durable session-attributed commits / writes / PR activity / uncommitted-work / topic summaries derived from Claude Code JSONL sessions |
| `obsidian/` | Bridge, vault writer, tasks plugin, datacore |
| `vault/` | Vault-recon collector, investigation-agent directions, accept-loop |
| `email/` | Thunderbird bridge, provider abstraction, email triage adapter |
| `calendar/` | Calendar reads via provider seam (canonical models, protocol + factory, Obsidian-bridge adapter), coverage |
| `browser/` | Chrome tab triage |
| `websearch/` | General web search via provider seam (Jina default + keyless ddgs fallback), trafilatura/Jina-reader extraction, evidence-cards, broker-admitted LOCAL_FAST classify |
| `threads/` | Multi-turn agent-user threads |
| `notifications/` | Notify, request, consent, surfaces |
| `events/` | Durable in-process delivery spine for event-shaped facts — CloudEvents-superset envelope, SQLite log (dedup + offsets + DLQ), one drain thread, consent gate; `event_publish` to emit, plus user-authored **pull sources** (poll → diff → CEL condition → notify) authored via `/wb-event-new` |
| `truth/` | Scoped evidence, claims, registered documents with tracked-edit proposals, human confirmation, provenance, revision, registry, and integrity sweeps |
- | `cowork/` | Co-work document surface: cowork docs, `cowork_doc_*` proposal capabilities, sittings, span feedback, document conversations |
+ | `cowork/` | Co-work Folder and document lifecycle: setup, catalog, create/register/scratch, durable editing, explicit Markdown writes, drift/reimport/retirement, sittings, feedback, conversations, and `cowork_doc_*` proposal capabilities |
| `services/` | Messaging, memory (Hindsight), dashboard, sidecar |
| `settings/` | Registry-driven settings, Apps-based placement, authority, persistence, Journal policy |
| `features/` | Preferences and feature opt-in |
| `operations/` | Gateway, agent sessions |
| `architecture/` | Repo structure, workflows, knowledge system, embedding service, retry queue, artifact system, summarization framework, llm-with-tools |
| `summarization/` | Producer + search surface for content summaries — `summary_search` funnel and per-composition producers |
| `disclosure/` | Unified `drill_tree` navigation across registered tree-shaped resources (knowledge units, summary nodes) |
| `status/` | Setup wizard, tailscale, feature status |
| `morning/` | Morning routine |
| `metacognition/` | Blindspot patterns (personal knowledge) |
## When MCP itself is missing
If `mcp__work-buddy__wb_init` is not in your tool list, **stop immediately and tell the user**. Do not attempt raw Python imports, manual JSON reads, or curling sidecar ports — none of them work as a bypass.
1. Run `echo $CLAUDE_CODE_ENTRYPOINT` via Bash.
2. If it contains `desktop` → tell the user to press **Ctrl+R** to reconnect MCP.
3. Otherwise (CLI) → tell the user to run **`/mcp`** to reconnect.
4. If the sidecar is down, they'll need to restart it first.
## Running Python in this repo
work-buddy's functionality is reached through the MCP tools (see above) — reach for those first. But some tasks legitimately need raw Python: running the test suite, a one-off debug script, or a `scripts/` utility.
When you do, run it through **uv** from the repo root. `uv run` executes in the project's `.venv` (built by `uv sync` from `uv.lock`), which carries every dependency, so a bare or partial Python that dies partway through with `ModuleNotFoundError` is not a concern. It needs no shell activation and works cross-platform:
```
uv run python -m <module> # e.g. uv run python -m pytest tests/unit/<file>.py
```
If the `.venv` is missing, `uv sync` recreates it from `uv.lock`. Run the whole suite with `uv run pytest`.
## Repo structure (navigational)
```
CLAUDE.md # This file
CLAUDE.local.md # User-specific behavioral rules (gitignored; auto-loaded)
config.yaml / config.local.yaml # Shared + local config
knowledge/store/ # Queryable knowledge units (one Markdown file per unit)
work_buddy/ # Python package
mcp_server/ # MCP gateway and registry (localhost:5126)
knowledge/ # Store, search index, query
embedding/ # Embedding service (localhost:5124)
collectors/ # Context collectors (git, obsidian, chrome, …)
conversation_observability/ # Durable session-derived activity DB (commits, writes, summaries)
obsidian/ # Bridge + plugin integrations
email/ # Email provider abstraction + Thunderbird bridge client
calendar/ # Calendar provider abstraction + Obsidian-bridge adapter
notifications/ # Human-in-the-loop surfaces
truth/ # Scoped provenance-aware claim ledger, document surface, and invariant engine
- cowork/ # Co-work document surface: doc routes, sittings, transport, feedback, conversations, ops
+ cowork/ # Co-work Folder/document lifecycle, persistence, recovery, review, and agent contribution surface
messaging/ memory/ telegram/ # Sidecar services
dashboard/ # Flask dashboard (localhost:5127)
sidecar/ # Service manager + retry queue
… # (full tree at agent_docs path=architecture/repo-structure)
.claude/commands/ # Slash command launchers (wb-*.md)
.data/ # Generated data (default; gitignored — `paths.data_root`)
user_jobs/ # User-authored scheduled jobs (gitignored)
sidecar_jobs/ # System scheduled jobs (git-tracked, ship with work-buddy)
```