hephaestus/docs/explanation/design.md
Erich Blume cbf859b2d7
Some checks failed
Build / validate (push) Failing after 2s
Set up hephaestus from template and add design + tech spec
Customize the generated repo (rename Dagger module to hephaestus_ci /
HephaestusCi, set docs baseUrl, add All-Rights-Reserved LICENSE, update
README/AGENTS), and add the project's foundational design documentation:

- docs/explanation/design.md — rationale + decision-history record
- docs/reference/tech-spec.md — implementation-ready technical spec

These define hephaestus as a self-hosted, client/server + offline-first
system unifying a markdown knowledge base with task management: typed node
graph, the lived priority discipline ("what is next?"), recurrence with
fresh-per-occurrence checklists, op-log/CRDT sync with conflict resolution,
OIDC/Authentik auth, the heph.nvim surface, and a TDD strategy.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-05-31 09:37:28 -07:00

43 KiB
Raw Blame History

title modified tags
Design Document 2026-05-31
explanation
design

Hephaestus — Design Document

Status: Living design record. This is the rationale + decision-history document. For the clean, implementation-facing spec the next session builds from, see tech-spec. Sections marked OPEN are unresolved; 🔒 DECIDED are settled.

1. Purpose & Vision

Hephaestus is a personal context management system that fuses two systems the owner already relies on into a single, purpose-built, self-hosted application:

  1. The Zettelkasten (~/code/personal/zk) — an Obsidian vault of ~600 markdown files acting as a personal wiki, journal, knowledge base, and "task-context" store for long-running projects. Notes use [[wiki-links]], YAML frontmatter (id, aliases, tags), timestamp-based note IDs (1722897441-MWFE), and daily notes (YYYY-MM-DD).
  2. Todoist — the owner's primary task manager, accessed today via a bespoke Python mise-task in blumeops (mise-tasks/blumeops-tasks) that polls the Todoist v1 API (projects → tasks; fields: content, description, priority, due) using a token from 1Password.

Today these are loosely coupled by fragile cross-links (todoist://<task-id> in notes, [[note]] text in Todoist comments). Hephaestus replaces the fragile coupling with one unified data model and database where tasks and knowledge are first-class, linkable entities.

Guiding principles

  • Context-aware: A task like "Fix the roof leak" is coupled to the relevant knowledge — the home-repair log, the log of contractor calls, etc. Surfacing that context is a core feature, not an afterthought.
  • Workflow-optimized: Highly optimized, concise answers to recurring questions — above all "What is next?" — are the primary UX. Speed and concision beat feature breadth.
  • Owner-built, owner-hosted: A standalone codebase replacing a loose stack of tools, hosted out of blumeops.

2. Goals & Non-Goals

Goals (v1 / prototype)

  • 🔒 Unified data model linking notes and tasks as first-class, cross-linkable entities.
  • 🔒 SQLite backend.
  • 🔒 Rust implementation.
  • 🔒 Distributed dual-mode operation: useful fully offline, auto-syncs to a central self-hosted instance in blumeops (k3s container) when reachable. The central instance may be unavailable for long periods.
  • 🔒 Surfaces over a shared core (via the hephd daemon): CLI (everyday driver), nvim plugin (primary editing UX), web UI (hub-hosted).
  • 🔒 Optimized workflow queries, especially "What is next?", with context surfacing.
  • 🔒 Auth from the ground up (OIDC/Authentik), with isolated multi-user accounts — sensitive data.

Non-Goals (v1) / Later

  • iOS app + Apple Watch app for quick voice-dispatched task capture — explicitly a follow-up goal, not v1.
  • Sharing/collaboration between users — accounts are isolated for v1 (multi-user isolation is in scope; multi-user collaboration is not).
  • Obsidian feature parity (canvas, graph view, plugins) — clean break.
  • Migration / import / Todoist sync in v1 — the prototype is a clean break: heph is the system of record from day one. No ZK import, no Todoist bridge. (Migration may return as a later, optional phase.)
  • Encryption at rest / E2E in v1 — security model is access restriction (OIDC auth) only; plain SQLite. May revisit.
  • Inferred/semantic context surfacing in v1 — explicit links + search only.

3. Data Model

🔒 DECIDED (shape). SQLite is the canonical store; a node's body is plain markdown text; an export mode materializes the whole store as a directory of .md files. Clean break from Obsidian-the-app, but markdown stays the portable payload. Tasks are thin; rich context lives in documents; everything is connected by typed links.

Entities

  • Node — the base entity. Every first-class thing is a node with a stable, sync-safe ID (see §3.1). A node has a kind discriminator. Kinds (initial set):
    • doc — a rich markdown context document (the bulk of the knowledge base; replaces ZK articles, journals, logs). Body = markdown text. Has aliases, timestamps.
    • taskthin: ID + state, plus a few scalar attributes (attention-state, do-date, late-on, recurrence) and links to a project/tags/context. No prose body of its own — context is supplied by linked doc nodes (or a doc is the work-log for the task). This is the key unification: ZK-articles-with-todos and Todoist-tasks-with-context both reduce to typed links between thin tasks and rich docs. The full task-attribute model is in §6.2 (it encodes the owner's actual lived prioritization system).
    • project — a grouping node (maps to Todoist projects / ZK project dirs like payrix/).
    • tag — a label node (so tags are linkable/queryable, not just strings).
    • journal — a daily-note node keyed by date (YYYY-MM-DD).
  • Link — a typed, directional edge between two nodes. Types (initial set): wiki (a [[link]] found in a doc body), context-of (doc ↔ task), blocks, parent/child, tagged, in-project. Wiki-links in markdown bodies are parsed and materialized as wiki links so the graph and the prose stay consistent.

3.1 Identity

  • Leaning ULID (sortable, sync-safe, collision-free across offline devices) as the internal primary key, with a human-facing slug/alias layer on top so [[wiki-links]] can be written by name. The ZK's timestamp-IDs (1722897441-MWFE) are ULID-like already; a migration can map them.

Tags and projects are nodes (linkable, queryable). Attention-state, do-date, late-on, recurrence are scalar columns on the task. See §6.2 for the semantics — these columns are not arbitrary; they encode a specific, battle-tested model. Due dates are not first-class nodes (a task has one project/context; "this Friday" doesn't need its own context).

3.3 Recurrence model (recurring tasks & checklists) — IN SCOPE for v1

A recurring task carries an RFC-5545 RRULE and acts as a recurring definition.

The anti-pattern we must avoid (Todoist's mistake): modeling a recurring task's checklist sub-items as standalone, non-recurring tasks that carry their completion state forward when the parent recurs. This is the corner; designing around it now is why recurring checklists are in v1 rather than deferred.

heph model (proposed — one point to ratify):

  • The recurring definition holds a checklist template (the ordered set of sub-item definitions).
  • Each occurrence materializes a fresh checklist instance — brand-new context items, all outstanding — scoped to that occurrence. Completion state is per-occurrence and never carries forward.
  • Occurrences are retained as history (no hard deletes; ties naturally to the per-task log, §6.4). The "what is next?" engine surfaces the current/active occurrence; completing it advances to the next per the RRULE.

The one fork to confirm:

  • (a) Occurrence instances (recommended): the definition spawns a lightweight instance per occurrence (its own do-date + its own fresh checklist items). Full per-occurrence history; unambiguously "a fresh instance per recurrence"; heavier (more rows).
  • (b) Roll-forward in place: a single task node; on completion, log the finished occurrence (to the per-task log) and reset the checklist to outstanding + advance the do-date. Lighter; history is narrative (in the log) rather than queryable per-occurrence.

Both satisfy the correctness requirement (fresh checklist, no carried state). (a) is recommended for clean history and zero ambiguity; (b) is the pragmatic-lite option.

4. Architecture

🔒 DECIDED (shape).

Layers, top to bottom:

  • Surfaces (thin clients): the nvim plugin (heph.nvim) is the primary surface for v1 — a full "org-mode"-style experience (markdown buffers backed by doc nodes; agenda / "what is next" / capture / linking / journaling as plugin commands). It is the explicit successor to obsidian.nvim, which the owner currently drives the ZK with (telescope picker, <Leader>o* verbs, <Enter> to follow [[wiki-links]], dailies, multi-state checkboxes) and rarely uses Obsidian-proper alongside. heph.nvim must reach that feature parity (see §6.5) and replace it. The CLI (heph) is a secondary/utility surface (export, scripting, admin) — explicitly not a focus, though it shares the same command surface. The web UI is the occasional hub-served surface. A later iOS/Watch client talks to the hub directly.
    • Dual-mode binary: a single Rust binary that can run as the server/daemon or expose the same operations as CLI subcommands or act as the backend the nvim plugin drives. Appealing (one codebase, one command surface) — to confirm as the packaging model.
  • Per-device daemon (hephd): owns the local SQLite handle, the CRDT/op-log state, and background sync. All local surfaces connect to it over a local socket. This is what makes multi-surface access concurrent and safe on one SQLite file, and gives one place to run background sync.
  • Core crate (Rust lib): data model, query engine, markdown parsing + wiki-link extraction, and sync logic. Linked by both hephd and the hub server.
  • Hub: hephd running in "server" mode on blumeops k3s — central SQLite, the sync rendezvous point, and the web UI host. May be offline for long periods; devices keep working and reconcile when it returns.

Open points:

  • nvim ↔ daemon transport: JSON-RPC over a unix socket (nvim has a native RPC channel; allows the daemon to push updates when sync brings in remote changes) vs. the plugin shelling out to the heph CLI. Leaning socket RPC.
  • Web front-end style: server-rendered (Axum + HTMX/Askama) vs. WASM SPA (Leptos/Dioxus). Leaning SSR + HTMX since it's the lowest-traffic surface — favor simplicity.

5. Sync & Conflict Resolution

Requirements (gathered from the owner)

  • 🔒 ~99% of interaction today is via the CLI on Gilbert (dev laptop). Future split: ~90% Gilbert, ~9% phone/watch, ~1% web.
  • 🔒 Must work seamlessly across tailnet workstations (Gilbert, ringtail, others): dispatch a change on one, walk to another, see it arrive "in short order."
  • 🔒 Genuine offline conflicts will occur — e.g. Gilbert edits offline, then ringtail edits, then Gilbert reconnects. Must resolve gracefully: auto-merge where possible; for the unresolvable remainder, a conflict queue + alert ("you have N merge conflicts, run heph conflicts").
  • 🔒 Auth from the ground up — this is extremely sensitive data. Use Authentik (OIDC) as IdP, support multiple isolated user accounts (in practice just eblume).
  • 🔒 A web UI is part of the hosted (hub) model.

Proposed model — for confirmation

A hub-and-spoke, op-log + CRDT design:

  • Topology: each device runs hephd with a full local SQLite replica; the blumeops hub is the rendezvous. Devices push/pull an append-only operation log to the hub when reachable. Hub-and-spoke (not P2P mesh) — simpler, matches "central self-hosted app," and the hub is normally up on the tailnet so propagation is prompt. When the hub is down, devices keep working and the op-log drains on reconnect.
  • Merge semantics (so "graceful" is precise):
    • doc bodies (markdown): a text CRDT (e.g. Yrs/Automerge) → concurrent edits always merge with no hard conflict. This covers the common case.
    • Scalar task fields (completion, due, priority): last-writer-wins via Hybrid Logical Clocks. Deterministic and cheap.
    • Links / set membership (tags, projects): add/remove as a CRDT set (OR-Set) → no conflicts.
    • Conflict queue: because CRDTs auto-merge, true blocking conflicts are rare. We surface (not block) the cases that are semantically ambiguous: a discarded LWW value on a meaningful field (e.g. completion flipped both ways, divergent due dates), or concurrent edits to overlapping text regions. These go to heph conflicts with an alert; sync never stalls.
  • Identity & ordering: ULIDs for nodes (§3.1); HLC timestamps stamp every op for causal ordering across offline devices.
  • Auth: the hub's sync + web endpoints require an OIDC bearer token from Authentik. Devices obtain tokens via the OAuth device-code flow; refresh token stored in the OS keychain / 1Password. Every node/op is owned by a user_id; the hub enforces per-user isolation. Offline devices operate on cached credentials.

Open points

  • Hub-only vs. P2P fallback: if the hub is down but Gilbert and ringtail are both on the tailnet, do they sync directly, or wait for the hub? Hub-only is simpler; direct P2P is more available. (Leaning hub-only for v1.)
  • Encryption at rest: given "extremely sensitive," should each device's SQLite (and the hub's) be encrypted at rest (e.g. SQLCipher), and/or should op-log payloads be end-to-end encrypted so the hub stores ciphertext it can't read? (E2E complicates server-side search/web UI.)
  • Todoist's role during/after migration — two-way bridge during transition, import-once, or retire on cutover?
  • How "short order" is short — is a few seconds of propagation (push-based) needed, or is periodic pull (e.g. every 3060s) fine?

6. Workflow Features

Driven by "highly optimized specific workflows." heph is organized around a small set of workflows, each a first-class surface in heph.nvim:

  • Task / "What is next?" (§6.1§6.3) — the Tactical / Strategic / Organizational task engine. The flagship.
  • Log (§6.4) — daily journal + optional append-only per-task work-logs ("narrative > list").
  • Wiki / KB (§6.5) — the whole context corpus as a browsable/queryable personal wiki; the direct obsidian.nvim replacement.
  • Calendar (§6.6) — entering via the calendar, tied to Strategic mode. Deferred, but scaffolded carefully.

"What is next?" — the flagship

ACTIVE DEEP-DIVE. This is the defining workflow and has ~10 years of the owner's prior art and prior tool attempts behind it. Being designed from that prior art rather than from scratch — see §6.1. The blumeops-tasks ranking (overdue-first, then priority, p3 = backlog) is a known reference point, not the target.

Context surfacing — v1 scope

🔒 DECIDED for v1. Explicit links only; no inferred/semantic context yet.

  • Every task automatically gets one canonical doc (context) node, created and linked on task creation. That canonical doc is the jumping-off point and can wiki-link onward to other docs/cards.
  • nvim commands provide telescope/fzf/rg-style search over context docs, optionally with preloaded filters (e.g. scoped to a project).
  • Inferred context (shared tags, full-text/semantic similarity) is deferred — large surface area, not the v1 focus.

6.1 "What is next?" — prior art & synthesis

Distilled from ~10 years of the owner's thinking — predating the ZK by ~5 years — chiefly the owner's direct account (this design conversation), plus zk/mole/20240202113646.todo.md (the most developed written framework), daily logs (zk/home/2023-09-01.log.md, 2023-10-12.log.md), and zk/payrix/friction_log.md. Prior tool code may exist at github.com/eblume/{mole,hermes} (check detached-head branches — several restarts); the owner expects little reusable there beyond what's captured here.

History: Hermes → Mole → heph (and the lessons)

  • Hermes — the "time accountant" (≈ earliest). Goal: a timeline-based system fed "blueprints" of what a day should look like, with rules like "check email twice a day, unless a high-priority email comes in — but even then no more than once an hour," continuously re-solving the calendar in real time. Built a working prototype using a SAT solver (OR-tools). Killer feature deduced: a quick, meaningful answer to "what is next" — here, the next thing on the calendar. Why it failed: the calendar became a battle zone, swinging wildly every few minutes as the solver re-resolved the dependency graph → impossible to plan around. Lesson + salvage: there may be fruit here as a future, opt-in heph planning mode, but real-time auto-rescheduling of a visible calendar is anti-useful.
  • Mole — "whack-a-mole" rules engine. A rules engine that constantly re-evaluated rules and materialized Todoist tasks (canonical first test: "is there an active task 'whack the mole'? if not, make one"). "What is next" = "what Todoist says is available." Worked reasonably. Why it stalled: Todoist accumulated too many tasks, so choosing the actual next thing became the whole problem again. This pushed the owner to design the Todoist project/priority/filter discipline in §6.2. The deeper lesson: Mole's automation wasn't adding much value over the project/priority discipline itself — so the owner turned Mole off and just kept using Todoist. The manual discipline was the real engine.
  • Today. No automation beyond blumeops' read-only blumeops-tasks mise poller. Just Todoist + the §6.2 discipline — but as the direct descendant of all the above. Implication for heph: the value is in embodying and strengthening the discipline (and finally fixing the weak note↔task link), not in clever auto-scheduling or a rules engine. Earn any automation only after the discipline is faithfully supported.

"What's next?" is three questions, not one

The owner's framework splits the question by mode:

  • Tactical — blank-slate "what do I do right now?" Stay in the editor/terminal; capture interruptions and resume in milliseconds ("save state and walk away in milliseconds"). This is the mode a single ranked list serves.
  • Strategic — "I want to do X — what's the next step, or where was I?" Exploring a goal, generating the sub-tasks to reach it.
  • Organizational — "I want to do X — help me plan the steps." Project-level enumeration of what needs doing, not how.

🔒 REAFFIRMED. The owner still strongly identifies with this split (the earlier "maybe" was conflating it with the unrelated CLI-first process note on the same page). The clean operational test, in the owner's words: "looking at a project's list of todos = Organizational; deciding which to work on = Strategic; working a todo = Tactical." Escalation flows upward (project-infra work can become Strategic, then Tactical).

How heph embodies it — as named modes/views in the nvim plugin, not an inference engine that guesses your mode:

  • Tactical (execution view): you are "tactically engaging" a specific committed task. Loads its canonical context + sub-items into a goal stack; millisecond interrupt-capture pushes new context items onto the stack; "smallest amount of contextual guidance and next steps to stay productive." Includes Chore Mode (synthesize a goal stack from a query — e.g. the Chores project — and chug through one by one).
  • Strategic (planning view): editor present but the goal is to explore/decompose, not edit. "To do X I must also do Y and Z." Produces a fleshed-out task with ready sub-items so Tactical can grab and go. When Strategic completes, no work has been done — only new/changed tasks, docs, links.
  • Organizational (survey view): project-level enumeration — what needs doing, not how.
  • Shared primitive — the goal stack (see §6.3) spans Tactical and Strategic; transitions between modes are first-class (Strategic produces → Tactical consumes).

The real cost is resumption, not ranking

The recurring pain in the logs is reorienting after a context switch, not deciding raw priority. Design rules that follow:

  • Resumption hint invariant: when work pauses on a task/todo, it should leave at least one concrete next-step so future-you can re-enter quickly. heph should make capturing/“leaving a breadcrumb” frictionless and surface it first on return.
  • Narrative > list: daily logs proved more motivating and contextual than flat task lists ("a log is the way"). Journaling/log nodes are first-class, and "what is next" should be able to lean on recent log narrative, not just task metadata.

The #1 unsolved problem: the fragile, clunky coupling between Todoist and notes (manually typing nb o 45 into task descriptions). heph's thin-task ↔ auto-created canonical-context-doc model is the direct fix: the link is structural, not a hand-typed string.

Mapping prior taxonomy → heph entities

Prior (nb/Todoist) Meaning heph
nb-project grouping; completing all members completes it project node
nb-todo planning complex multi-step work; has "a single unified context log" task + its canonical context doc
nb-task immediate, short-lived "might eventually be done"; checking off = "no longer might be done" (not necessarily completed) thin task node
Todoist "deciding what is next and knowing what needs to be done now" heph's "what is next?" engine
Calendar scheduling / future due dates (+ later calendar integration)

🔒 Preserved. The nb-task semantic — checking off means "no longer outstanding," not "accomplished" — carries into heph. Context items are outstanding / not-outstanding (see §6.3). For committed tasks, heph distinguishes end-states: done (accomplished) vs. dropped/dismissed (let go, e.g. during a Blue review). Both are "not outstanding"; the distinction is kept for honesty and history.

Process steer (now historical)

The owner's old rule — "avoid ncurses and interactive UIs; write atomic code and call it manually with subcommands… until a strong pattern emerges" — was a focus device for hand-building tools. It no longer constrains heph: we go straight to the nvim org-mode plugin backed by the Rust server (the patterns are now well understood; see §6.2/§6.3). The CLI remains as a secondary/utility surface.

Distilled requirements for heph's "what is next?" — to confirm/refine

  1. Mode-aware (Tactical / Strategic / Organizational), not a single global sort.
  2. Sub-millisecond capture and fast state-save (Tactical interrupt handling).
  3. Resumption-first: on returning to a task, surface the last breadcrumb/next-step before anything else.
  4. Context-coupled: every task one keystroke away from its canonical context doc and onward links.
  5. Log-aware: can factor in / surface recent daily-log narrative.
  6. A sane default ranking for the Tactical blank-slate case — the concrete model is §6.2, not the blumeops-tasks placeholder.

6.2 The lived priority discipline (the working basis for heph)

🔒 This is the real model. The owner has used this Todoist discipline daily for years; Mole was eventually turned off because this discipline outvalued the automation. heph should embody and strengthen it. Captured verbatim-in-spirit from the owner's account.

Projects = contexts (a task has exactly one). Examples: Life (catch-all), Work, Chores, Maintenance, Outside, Allison (wife), Errands, Cooking, Health. A project contextualizes a task; it is not a plan.

Attention-state (thought of by color, not number):

State Meaning
White (default) Able to be done once its do-date arrives. (See do-date semantics below.)
Orange Top of mind. Keep ≤ 6, reassessed every day. Goal: you know your top-of-minds without looking at the tool.
Red Top of mind + there will be a consequence if not done ASAP. Red means the existence of a consequence, NOT its severity/importance. Flagging by importance is a trap → anxiety (everything due feels important).
Blue On Deck (backlog). Doable, but deliberately cooling off. The pressure-relief valve that keeps the working set light.

Do-date, not due-date (key insight): "due date" means DO datedon't do it before this date. Optionally a separate "Late On" marker for when it actually becomes a problem. Due dates are a terrible planning tool — calendar-style deadlines make everything "yell for attention" constantly. heph must treat the do-date as earliest actionable, not a deadline alarm.

Working-set discipline (the healthy tensions — a feature, not a bug):

  • Keep White+Orange+Red ≤ ~30 total, or the system collapses into noise. Keep Orange ≤ 6.
  • Push freely to Blue to stay light; periodically review Blue: keep vs. let go (this is where "drop/dismiss" happens). Target On-Deck < 100 (owner is at ~149 and hates it).
  • These tensions map the owner's emotional state to the task system and should be surfaced honestly. heph must avoid two failure modes: (1) "fake happy" — telling you everything's fine when it isn't; (2) overwhelm — yelling that you're buried until you freeze and make no progress. Reflect true state; help maintain the tensions.

Filters = saved views (queries over the above):

  • Top of Mind — all Red + Orange.
  • Tasks / Work Tasks — Red/Orange/White that are actionable now (do-date arrived), scoped by project.
  • Chores — like Tasks but from Chores, slightly tuned down so the (near-universally recurring) chores don't become a nuisance.
  • On Deck — all Blue.
  • Schedule — recurring checklists from a few projects (e.g. Morning Routine → brush teeth, feed birds, coffee, medicine). The owner uses this particular filter sparingly, but the underlying recurring-checklist mechanism is IN SCOPE for v1 (see §3.3): the only way to be sure we avoid Todoist's mistake is to model it up front. Each recurrence gets a fresh checklist instance; completion never carries forward.

heph's default "what is next?" (Tactical blank-slate) therefore ≈ Top of Mind (Red first — by consequence, then Orange) + White items whose do-date has arrived, scoped to the current project/context, with Blue hidden. Concise, honest, light.

6.3 Two kinds of task: commitments vs. context items

🔒 DECIDED (shape). A commitment axis orthogonal to the §6.2 attention-states.

  • Committed task — long-lived, strongly tied to context (e.g. "Complete PLAT-1234", "Build a nursery"). Participates in §6.2: has an attention-state, a project, a do-date; is a candidate for "what is next"; may carry sub-structure and a web of context.
  • Context item — ephemeral, created mid-flow ("six things before this is done", "oh crap, another" pushed on the stack). Does not enter the §6.2 scheduling system — it is part of its containing task's context, with only outstanding / not-outstanding state. Never appears in global "what is next."
  • Promotion is first-class: a context item can be promoted to a committed task when you decide to actually schedule it (the old "nb-todo → Todoist-task on activation").
  • Ephemeral ≠ deleted: deletes effectively never happen — tombstones + a dedicated cleanup mode only. "Ephemeral" is lifecycle/visibility, not storage. (This also keeps the CRDT sync simple.)
  • The goal stack is a session-scoped, synthesized construct: seeded from a committed task (Tactical) or from a query over contexts (Chore Mode — "grind on some chores"). It holds committed tasks and/or context items for the duration of a work session.

Editing surface for context items — deliberately deferred to the prototype

🔑 Key realization: this is NOT a data-model fork. In both options below the stored representation is identical — context items are outstanding nodes linked to their container; the daemon owns them; markdown export renders them as checkboxes regardless. The choice is purely the nvim editing affordance, so the backend is shared and we prototype both and pick by feel (ergonomics can't be judged on paper).

  • Option A — checkboxes in the body, derived on save. Edit the task's canonical context doc as plain markdown; - [ ] lines are materialized into context-item state on :w (BufWritePost), reusing the wiki-link extraction machinery. No real-time scanning needed (debounced live updates are optional polish). Remote CRDT edits are pushed from the daemon to reconcile an open buffer. Item identity is low-stakes (reword = tombstone-old + add-new) since items are ephemeral; identity is pinned only at promotion. Most "personal org-mode"-native; millisecond capture = "type a line."
  • Option B — structured items, rendered. Items are nodes from the start; the body stays pure prose. Render inline via virtual text/extmarks (no pane) or a transient floating window (avoids the disliked persistent cutaway); capture via a leader chord + vim.ui.input. Cleaner separation and trivial promotion; heavier capture; more UI to build.
  • Lean: A for capture flow, but build the shared node backend first and trial both affordances in the plugin.

Prior-art note (multi-state checkboxes): the owner's obsidian.nvim config uses checkbox states " ", "x", ">", "~", "!" (todo / done / forwarded / ~ / important). heph's task/context-item state model should accommodate richer-than-binary states (at least the done/dropped/outstanding set in §6.1; possibly forwarded/deferred).

6.4 The Log workflow

🔒 In scope for v1 (daily journal certainly; per-task logs as the model allows). Logs embody the "narrative > list" insight and the resumption-hint invariant from §6.1.

Two related shapes:

  • Daily journal. Today driven by the zkd abbrev → Obsidian dailies: a telescope picker to create/open dated journal buffers. heph keeps this as a first-class feature: journal nodes keyed by date, a picker to jump to today / recent / create. The daily journal is the catch-all narrative stream.
  • Per-task log. Sometimes a task wants an append-only "here's what I was working on" side-commentary. It half-belongs to the task's context and half doesn't — so model it as a separate buffer/node from the task's canonical context doc, optionally present only for tasks that need longer-duration commentary. Properties:
    • Append-only (entries accrete; you don't rewrite history).
    • Dispatchable from an isolated context: you must be able to fire a log entry without leaving / losing your place in the context buffer you're navigating — i.e. append to the log from elsewhere (a quick-capture into the task's log), while the context buffer stays where it is. (This is the ergonomic crux; likely a quick-capture command targeting "the current task's log".)
    • The per-task log is a strong candidate to be the resumption breadcrumb store (§6.1).

Open: is the per-task log a distinct node kind (log, append-only), or just a doc flagged append-only and linked log-of → task? Leaning a dedicated append-only log kind to make "dispatch an entry" a cheap, well-defined op.

6.5 The Wiki / KB workflow

🔒 First-class in v1. This is the "view all my contexts as one big personal wiki" surface — the direct replacement for obsidian.nvim.

The owner estimates ~90% of the value comes from a few well-worn primitives, so heph.nvim should reach obsidian.nvim feature parity on day one:

  • Follow [[wiki-links]] on <Enter> to jump between context docs (the core navigation gesture).
  • Telescope-based search (rg), quick-switch between docs, tag browse, backlinks, outgoing links — mirroring the owner's <Leader>o* verbs (to be rebound under heph's own prefix).
  • Logical layout + tagging of context docs so the corpus is browsable/queryable as a whole.
  • New-doc-from-query and insert-link from the picker (obsidian.nvim's <C-x>/<C-l>).

heph.nvim replaces obsidian.nvim outright — the owner already rarely opens Obsidian itself. This workflow is the knowledge-base analogue of the Organizational task view: surveying and traversing the doc graph.

6.6 The Calendar workflow

Deferred (later version) — but scaffold now, don't paint ourselves into a corner. Ties closely to Strategic mode (planning against time).

  • The owner uses Calendar.app bound to a Gmail calendar (almost certainly CalDAV; Gmail also exposes ICS/WebCal feeds — to confirm when implemented).
  • Eventual goal: integration between calendar views and task views — enter the system via the calendar, see tasks/do-dates in temporal context.
  • ⚠️ Hard-won gotcha: enabling Todoist's gcal integration flooded the calendar with a million events because of recurring rules. heph must never explode recurrence into individual calendar events. This echoes the Hermes "battle zone" failure (§6.1) — calendar integration must be read-mostly, careful, de-duplicated, and opt-in, not a firehose.
  • Scaffolding guidance (decisions to keep clean now): keep a crisp separation between tasks/do-dates and calendar events; model recurrence as RRULEs that are expanded lazily for display, never materialized as stored events; design sync so a future read-only CalDAV ingestion (calendar → heph) is easy, and treat any heph → calendar export as a later, explicitly bounded, non-exploding feature.

7. Hosting & Deployment

Reuse the established blumeops patterns (🔒 confirmed by repo conventions):

  • Containerized service deployed to k3s (ringtail cluster) via ArgoCD app-of-apps + Kustomize raw manifests (no Helm/Flux).
  • Secrets via 1Password + external-secrets operator (ClusterSecretStore onepassword-blumeops).
  • Image built via Dagger + Forgejo Actions, pushed to Zot registry (registry.ops.eblu.me).
  • Rust build approach for the container: Nix (like kingfisher) vs. Dagger-native cargo multi-stage build.
  • Persistence for the central instance's SQLite — NFS PVC (sifaka) vs. local PV. (SQLite over NFS has known locking caveats.)
  • How do clients reach the central instance — Tailscale tailnet only, or public via Caddy/Fly proxy?

8. Technology Choices (to decide)

  • nvim plugin stack: Lua + JSON-RPC over a socket to hephd (with daemon→plugin push) vs. CLI shell-out. Telescope as the picker dependency.
  • Web framework (Axum + HTMX/Askama? Leptos? Dioxus?).
  • SQLite access layer (sqlx, rusqlite, SeaORM, Diesel) and migrations.
  • CRDT / sync library (Automerge-rs, Yrs, bespoke op-log).
  • Markdown parser/renderer (pulldown-cmark, comrak) and wiki-link / checkbox extraction.

9. Open Questions Summary

Decided

  • Source of truth: SQLite-primary, markdown as the body payload, with a directory export mode. Clean break from Obsidian-the-app.
  • Note/task model: typed node graph — thin task nodes, rich doc nodes, connected by typed links.
  • Front-end: per-device hephd daemon with thin clients; nvim plugin as primary editing UX, CLI as everyday driver, web UI on the hub.
  • Sync (shape): hub-and-spoke, op-log + CRDT, HLC ordering, OIDC/Authentik auth, conflict queue for the ambiguous remainder. (Details below still open.)

Decided since (B/C/D)

  • Context surfacing v1: explicit links only + auto-created canonical context doc per task + nvim fuzzy search. Inferred/semantic deferred.
  • v1 is distributed, not local-only: client/server + offline-first sync with automatic merge + conflict queue + full OIDC/Authentik auth with per-user isolation are all IN v1 (tech-spec §12/§13). Web UI and k3s deployment are later (hub binary is built deployable).
  • Security v1: OIDC/Authentik auth + per-user isolation is the boundary; plain SQLite at rest (no encryption). May revisit.
  • Migration v1: clean break, no ZK import / no Todoist bridge. heph is system of record day one.
  • License: All Rights Reserved / private. Open-sourcing considered later.

Decided (workflows)

  • heph organized around first-class workflows (§6): Task (what-is-next), Log, Wiki/KB, Calendar.
  • Log (§6.4) in scope: daily journal (picker over dated journal nodes, à la zkd/Obsidian dailies) + optional append-only per-task work-logs (separate buffer from the canonical context doc; dispatchable without leaving the context buffer).
  • Wiki/KB (§6.5) first-class: heph.nvim reaches obsidian.nvim feature parity (follow [[links]] on <Enter>, telescope search/quick-switch/tags/backlinks/links) and replaces it.
  • Calendar (§6.6) deferred but scaffolded carefully: never explode recurrence into stored events (Todoist-gcal-flood / Hermes lesson); keep tasks vs. events separate; lazy RRULE expansion; design for a future read-only CalDAV ingest.

Decided (workflow model)

  • "What is next?" rests on the lived priority discipline (§6.2): projects-as-contexts; attention-states White/Orange/Red/Blue (Red = consequence, not severity); do-date not due-date; working-set tensions surfaced honestly (avoid "fake happy" and overwhelm).
  • Tactical / Strategic / Organizational reaffirmed as named modes/views in the nvim plugin, sharing a goal-stack primitive (§6.1).
  • Commitment axis (§6.3): committed tasks (in §6.2 system) vs. ephemeral context items (outstanding/not-outstanding, scoped to a container); promotion is first-class; no hard deletes (tombstones + cleanup mode).
  • Task end-states: done vs. dropped/dismissed (both "not outstanding").
  • Prototype goes straight to the nvim plugin + Rust server; CLI is secondary.
  • Rust stack RATIFIED (§11.2): rusqlite + tokio/JSON-RPC + pulldown-cmark + ulid + rrule + clap.
  • Recurrence + recurring checklists IN SCOPE for v1 (§3.3): RRULE-driven; each occurrence gets a fresh checklist instance, completion never carries forward (avoids Todoist's corner).

Still open

Prototype-blocking (resolve at kickoff — see §11 / tech-spec):

  1. Recurrence model (§3.3): (a) occurrence-instances vs (b) roll-forward-in-place — both avoid the corner; leaning (a).
  2. CRDT library for body merge: yrs (leaning) vs automerge; bespoke op-log/HLC for scalar fields either way.
  3. Hub network transport (tech-spec §6.1): axum HTTP/JSON (leaning) vs gRPC; sync propagation cadence (push vs periodic pull).
  4. Context-item editing surface (§6.3): Option A vs Bdecided inside the prototype by feel (shared backend).

Not prototype-blocking (later phases):

  1. Per-task log representation: dedicated append-only log node kind vs. doc flagged append-only (§6.4).
  2. Calendar protocol confirmation (CalDAV vs ICS/WebCal) and the careful, non-exploding integration (§6.6).
  3. Web front-end style (SSR + HTMX vs. WASM SPA).
  4. k3s deployment specifics: Rust container build (Nix vs Dagger-native cargo); central SQLite persistence (local PV vs NFS); client reachability (tailnet-only vs public).
  5. Encryption at rest / E2E (currently out; may revisit).

10. Roadmap (provisional)

  • Phase 0 — Design (this document + tech-spec): done enough to build.
  • Phase 1 — v1 prototype (one C1 effort, built in TDD slices): heph-core (model, schema, extraction, recurrence, "what is next", op-log/HLC/CRDT merge) → hephd client mode → hub mode + offline sync + conflict queueOIDC/Authentik auth + per-user isolationheph.nvim + heph CLI. Runnable client/server, offline-capable, on the tailnet.
  • Phase 2 — k3s deployment: Dagger→Zot image, ArgoCD app + Kustomize manifests, external-secrets; hub on blumeops.
  • Phase 3 — Web UI on the hub.
  • Phase 4 (later, optional) — calendar integration (careful CalDAV); migration from ZK / Todoist; iOS / Apple Watch voice capture; Hermes-style planning mode.

11. v1 Prototype — Scope, Stack, and Kickoff

The handoff target for the next context session. Items here are proposals to ratify; once ratified, a fresh session can build directly from this.

The canonical, implementation-ready scope/stack/schema/API now live in tech-spec. This section keeps the intent and the kickoff process; defer to the spec for details (and update the spec when decisions change).

11.1 Scope — distributed from day one (ratified)

v1 is not local-only. It is a client/server, offline-first system:

  • In: the full data model (§3, §6) incl. recurrence + recurring checklists (§3.3); the "what is next?" engine; client/server architecture (per-device client hephd + a runnable/deployable hub hephd); offline-first op-log + CRDT sync with automatic merge and a conflict queue (tech-spec §12); OIDC/Authentik auth with per-user isolation (tech-spec §13); the heph.nvim + heph CLI surfaces.
  • Out (later, scaffolded): the web UI (hub serves sync only in v1); actual k3s deployment to blumeops (hub binary is built deployable; ArgoCD/Kustomize/Dagger is a fast-follow); calendar; iOS/Watch; inferred context; P2P-over-tailnet fallback; persistent goal stack (an in-plugin session stack suffices).

Why this bigger v1: the distributed/offline/merge behavior and the auth model are architecturally load-bearing — bolting them on later would mean reworking the core. The owner explicitly wants them proven from the start. (Recurrence-with-checklists is in for the same reason — see §3.3.)

11.2 Stack — RATIFIED

Core: rusqlite (bundled) + migrations · tokio + JSON-RPC/unix-socket (local) · ulid · rrule · pulldown-cmark · clap · anyhow/thiserror · tracing. Distributed/auth additions (some to confirm at kickoff): text CRDT yrs (vs automerge) for bodies + bespoke op-log/HLC for fields · hub transport axum/reqwest · OIDC via openidconnect + keyring. Full detail and the SQLite schema: tech-spec §4.5, §10, §12, §13.

11.3 First-session kickoff checklist

  1. mise run ai-docs; read tech-spec (the build spec) and this doc's §3/§6 for rationale.
  2. Classify as C1 — greenfield, no existing system to Mikado-untangle. Single long-lived feature branch + early draft PR, docs-first, push as you go (agent-change-process).
  3. Scaffold the cargo workspace + heph.nvim skeleton; fill the AGENTS.md Project Structure section (last template TODO).
  4. Build outward in testable slices (TDD, tech-spec §2/§9): heph-core (schema, model, extraction, recurrence, "what is next", op-log/HLC/CRDT merge) → hephd client mode (local RPC) → hub mode + sync + conflict queueOIDC authheph.nvim + heph CLI.
  5. Confirm the kickoff-open picks: recurrence model (a vs b, §3.3), CRDT lib (yrs vs automerge), hub transport, context-item editing surface (A vs B, §6.3).