Skip to content

Flags that mean what they say + time-aware capture · Closes #244 · Refs #179 - #248

Merged
Ambisphaeric merged 1 commit into
mainfrom
feat/time-aware-capture
Jul 18, 2026
Merged

Ambisphaeric merged 1 commit into
mainfrom
feat/time-aware-capture

Conversation

@Ambisphaeric

Copy link
Copy Markdown
Owner

Why

Live QA: with extraction feature flags ON, Moments and Relevant-now stayed empty while distillates flowed, and a first-person intent with a spoken deadline ("provide feedback to QA in eighteen minutes") produced no moment, no to-do, and no deadline.

What

1. Flags are authoritative across both drain paths (the silent-inert fix)

The direct drain read distill.moments / distill.index from the flag store; the workflow executor ADDITIONALLY required a moments / index drain step, so a document without those steps silently disabled the Settings toggle under workflow.enabled.

Design chosen: the executor treats the flag as authoritative. moments/index are not independent pipeline stages — they are two boolean OPTIONS on the single distill call the drain already makes — so the user-facing Settings flag is the authority. A present step may re-bind its gate to a different flag (advanced document composition stays meaningful as data); a step's absence falls back to the flag, exactly as the legacy direct drain does. For the seeded default (which binds these very flags) behavior is identical.

distill.fields was audited and is immune: it rides distillThrottled (the shared distill seam), gated by its flag directly, never by a workflow step. Drain acts (task-extract) are intentionally left step-keyed — an act names a runner + template, so the flag alone cannot drive it.

2. First-person commitments

The extract-moments template's commitment definition now explicitly includes first-person intent ("I need to… / I'll… / I have to… / let me… / I should…"), keeping the 4 shipped kinds and the strict-JSON invent-nothing discipline.

3. Deadlines — the #179 opener (smallest roadmap-true slice)

TodoItem.due (optional, validated absolute ISO time) + a dueSource provenance marker (model | anchored). Chose TodoItem over Commitment: the to-do path is already wired end-to-end (extract → merge → store → render); Commitment has zero producers and no store/render path, so extending it would be a much larger slice, not the smallest opener. Commitment remains the eventual durable home for #179.

  • task-extract prompt gets a {{now}} anchor (the extraction wall-clock = window end) and requests an optional ISO due.
  • Model output is a proposal, validated post-hoc: parseable ISO within a -60s .. +60 days horizon (60s absorbs clock skew; >60 days is almost certainly a hallucinated date, not a live deadline). Invalid ⇒ due dropped, todo kept.
  • Deterministic fallback shipped (in N minutes/hours/days, digits + number words incl. "eighteen", "forty-five") anchored to now, so the flagship case works on weak local models that echo the phrase but cannot compute ISO.
  • todos surface renders due as an honest, unselectable due 3:29p decoration — absent/invalid due ⇒ no decoration, no fake urgency; copy payload stays the bare todo text.

Tests

  • Flag-authority regression (executor unit: no step + flag ON ⇒ extracted; the present-step re-bind branch) + an HTTP e2e reproducing the QA scenario (workflow.enabled ON, no moments step, flag ON ⇒ moments extract).
  • Due validation (garbage / past / far-future dropped; valid kept; model vs anchored provenance) — unit + composeTaskExtract integration.
  • Template assertions (first-person commitment; {{now}} anchor + optional ISO due).
  • Client todos rendering incl. the selection-hygiene by-construction guard (.due unselectable) and a copy-payload negative guard.
  • Rewrote the pre-existing "PUT edited workflow" e2e, which had codified the silent-inert behavior, to prove hot-edit via re-binding the present step's gate.

Full gate green: contracts 114, engine 1125, client 621. Schema regen committed zero-drift; governance dry-run PASS.

Do not merge pending review.

Two QA defects and the #179 opener.

1. Flag tangle. The workflow executor gated moments/index on BOTH the flag
   AND the presence of a drain step, so a document without a moments/index
   step silently disabled the Settings toggle under workflow.enabled — the
   headline QA case (flags ON, empty Moments/Relevant-now). Make the flags
   AUTHORITATIVE: a present step may re-bind its gate (documents stay
   meaningful as data), but its ABSENCE falls back to the flag, exactly as
   the legacy direct drain does. distill.fields is already immune — it rides
   the shared distillThrottled seam, gated by its flag directly, not a step.

2. First-person extraction. The extract-moments template defined a commitment
   as "someone promised to do something"; small models returned [] for
   "I need to…/I'll…". The commitment kind now explicitly includes first-person
   intent, keeping the 4 shipped kinds and the strict-JSON invent-nothing rule.

3. Deadlines (#179 opener). Add optional TodoItem.due (validated absolute time)
   + a dueSource provenance marker. task-extract gets a {{now}} anchor and
   requests an optional ISO due; the engine validates post-hoc (parseable ISO,
   within a -60s..+60d horizon; invalid dropped, item kept) and falls back to a
   deterministic "in N minutes/hours/days" resolver (digits + number words) over
   the item text so the flagship "in eighteen minutes" works on weak models.
   todos surface renders due as an honest, unselectable "due 3:29p" decoration.

Closes #244 · Refs #179
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant