#+TITLE: Wrap-Up Inbox/Transcript Routing — Spec #+AUTHOR: Craig Jennings #+DATE: 2026-06-13 #+TODO: TODO | DONE SUPERSEDED CANCELLED * Metadata | Status | ready for review | |----------+-----------------------------------------------------| | Owner | Craig Jennings | |----------+-----------------------------------------------------| | Reviewer | Codex (spec-review) | |----------+-----------------------------------------------------| | Related | [[file:../../todo.org][todo.org: wrap-up routing task]] · [[file:2026-06-13-wrapup-inbox-transcript-routing-proposal.org][archsetup proposal]] | |----------+-----------------------------------------------------| * Summary At wrap-up, an inbox handoff that belongs to another project has nowhere to go but the current project's =todo.org= or a deferral. This adds an optional routing step to =wrap-it-up.org=: surface the items that belong elsewhere, recommend a destination project for each, and move the whole batch there on one confirmation. A parallel step files meeting-transcript recordings into the right project's =assets/=. * Problem / Context =process-inbox.org= dispositions each handoff as act / fold / file / reject, and "file as TODO" lands the task in the *current* project's =todo.org=. When the real home is a different project, the choices today are: file it locally and let it rot in the wrong tracker, hand-edit two projects' =todo.org= files, or defer it and carry the debt to next session. The wrap-up's existing Step 3 "Inbox sanity check" only counts unprocessed items and blocks the wrap until they clear. It answers "is the inbox clean?" — it doesn't route anything. Meeting transcripts have the same homelessness: a recording dropped during a session belongs in some project's =assets/=, but nothing moves it there at wrap. The friction is small per-item but recurring, and the manual cross-project edit is error-prone (two files, two repos, easy to leave one half-done). * Goals and Non-Goals ** Goals - At wrap-up, surface inbox items (and transcripts) whose home is a different project, with a recommended destination each. - Move the whole batch on one confirmation ("go with recommendations") or leave it entirely ("skip"). No per-item triage. - Move a task/event item as a proper org task into the destination's "Open Work" section per =todo-format.md=; move a transcript as a flat-filed artifact per =working-files.md=. - Keep the move atomic and visible (it shows in the destination's next git diff, with a provenance note). - Discover any project with a =todo.org= as a candidate destination, not only =.ai/= projects. ** Non-Goals - Not a wrap gate. A skip is a clean, complete wrap. - Not per-item triage. The interaction is batch-level: go or skip. - Not a replacement for =process-inbox.org='s value gate. Routing assumes the item is already an accepted keeper. - Not a confidence-free auto-mover. A low-confidence destination recommendation says so, and the batch "go" stays trustworthy because the surfaced list is reviewable before the keystroke. ** Scope tiers - v1: task/event routing to a destination project's =todo.org=. The interaction, the recommendation engine, the atomic move helper, the widened project discovery. - Out of scope: per-item destination editing, an interactive correction loop, moving items that aren't accepted keepers. - vNext: meeting-transcript filing (gated on the unresolved source-location decision and the file-vs-file+extract question — see Decisions). * Design ** User-facing (the wrap interaction) The router is a new sub-step of =wrap-it-up.org='s Step 3, running after the existing inbox sanity check. Its input is filed keepers, not raw inbox files (decision: Reading B): tasks =process-inbox= accepted and filed into the local =todo.org= this session whose inferred home is a different project. When the router finds such a keeper, it surfaces it in a list, one line each: the task, the recommended destination project, and a confidence marker when the inference is weak. Then two options, batch-level: 1. Go with the recommendations — apply every recommended move. 2. Skip — leave the whole batch in place. A skip is a clean wrap. That is the entire interaction. No per-item walk. The surfaced list is the review surface; the single keystroke is trustworthy because the list was reviewable and low-confidence recommendations flagged themselves. A move of a task/event relocates it into the destination project's "Open Work" section as a proper org task (terse heading, body for detail, tags on the heading line, per =todo-format.md=), and removes it from the source. A skipped or unroutable item stays where it is; the existing sanity check still governs whether the wrap is clean. ** Implementer (the mechanics) *Candidate set (what the router considers).* Reading B means the router does not scan the whole local backlog — it would otherwise suggest moving legitimate local tasks every wrap. The candidate set is keepers =process-inbox= filed this session whose inferred home differs from the current project. How those are marked is an implementation detail for Phase 3/4: either =process-inbox= tags a cross-project-candidate keeper at file time, or the router infers from a =CREATED= stamp dated this session plus content. The reviewer should pin which; the design constraint is "session-filed inbox keepers only, never the standing backlog." *Destination discovery.* Widen the project-discovery filter from "directory with a =.ai/protocols.org= marker" (what =inbox-send.py= and the =ai= launcher use) to "directory with a =todo.org= containing a level-1 'Open Work' heading." A plain code repo Craig keeps a =todo.org= in is a valid destination; an =.ai/= directory is not required. *Destination anchor.* Reuse =todo-cleanup.el='s existing matcher: =tc--find-section= locates the unique level-1 heading containing "Open Work" (case-insensitive) and returns =nil= / ='multiple= when absent or ambiguous. A destination whose =todo.org= lacks a clean Open Work heading is surfaced and skipped, never guessed at. *The move helper.* A small tool inserts a task subtree under a named project's "Open Work" heading and removes the source atomically — extend =todo-cleanup.el= (it already owns the section matcher and the subtree-move logic for =--archive-done=) or add a sibling =.ai/scripts= tool. Hand-editing across two repos is the error-prone path this replaces. *Recommendation engine.* Infer the destination from the item's content — project names, file paths, topic words — matched against the discovered project list. Conservative by design: a weak match is labeled low-confidence so "go" stays a safe single keystroke. The engine is the interesting, uncertain part; it earns the spec. *Cross-project write discipline.* Moving an item into project X's =todo.org= writes into X's scope (=cross-project.md=). The batch "go" authorizes it, but the move stays visible (X's next git diff) and leaves a one-line provenance note on the moved task naming the source project. * Alternatives Considered ** Per-item triage instead of batch go/skip - Good, because it gives precise control over each destination. - Bad, because it taxes the common case (a batch that's all-correct, or all-stay) with a walk. Craig explicitly asked for two options, not a triage loop. - Neutral, because per-item correction could return as a vNext refinement if batch-only proves too blunt. ** Fold the router into the existing Inbox sanity check step - Good, because one inbox step is simpler than two. - Bad, because the sanity check *gates* the wrap (blocks until clean) and the router is *optional* (skip is clean). Merging a blocking check with an optional action muddies both. - Neutral, because the two share discovery code while staying separate steps. (Resolved: D1 keeps them separate, with the router acting on filed keepers rather than inbox files.) ** Reuse process-inbox's "file as TODO" with a destination argument - Good, because it avoids a second mechanism. - Bad, because =process-inbox= runs per-item mid-session against the local project; the router runs at wrap, batch-level, cross-project. Different cadence, different scope. - Neutral, because both ultimately call the same atomic move helper — the helper is the shared primitive, the two callers stay distinct. * Decisions [6/6] ** DONE Reuse the Open Work matcher for destination anchoring - Context: the move needs a reliable insertion point in the destination =todo.org=; guessing risks corrupting another project's file. - Decision: We will reuse =todo-cleanup.el='s =tc--find-section "open work"= matcher, which already handles the unique / missing / ambiguous cases, and skip+surface any destination without a clean Open Work heading. - Consequences: easier — no new parser, consistent with =--archive-done=. Harder — destinations must carry the "Open Work" heading convention, so a project with a differently-named section is silently unroutable until it conforms. ** DONE Move atomically through a helper, never hand-edit two repos - Context: a move touches two files in two repos; a half-done move loses or duplicates a task. - Decision: We will route every move through one helper (extend =todo-cleanup.el= or a sibling =.ai/scripts= tool) that inserts under the destination's Open Work heading and removes the source as one operation. - Consequences: easier — no partial-move corruption, one place to test. Harder — a new helper to build and cover with tests before the router can ship. ** DONE Cross-project writes stay visible and carry provenance - Context: writing into another project's =todo.org= crosses the =cross-project.md= scope boundary. - Decision: We will treat the batch "go" as the authorization, leave the move visible in the destination's git diff, and stamp a one-line provenance note (source project + date) on each moved task. - Consequences: easier — the boundary rule is honored without a per-move prompt. Harder — the destination's next session sees an externally-authored task it didn't file, so the provenance note is load-bearing, not decorative. ** DONE Separate router step, operating on filed keepers (Reading B) - Context: the sanity check gates the wrap on inbox/ contents; the router is optional. The deeper question was the router's input — raw inbox files (Reading A, which overlaps the sanity check) or already-filed keepers that belong elsewhere (Reading B, a todo-routing concern). - Decision: We will keep the router a separate optional sub-step after the sanity check, and its input is Reading B: accepted keepers process-inbox filed into the local =todo.org= whose inferred home is another project. The sanity check stays a pure inbox gate; the router is a todo-routing action that shares only the destination-discovery code. - Consequences: easier — each step has one job, the gate can't be muddied by an optional action, and the router never competes with the inbox gate over the same files. Harder — the candidate set (which local tasks the router considers) needs a marking mechanism (see the Implementer "candidate set" note); Reading A's "dispose raw inbox files at wrap" convenience is given up. ** DONE Transcript routing deferred to vNext - Context: transcripts file as artifacts, not tasks, and a meeting usually produces both a recording to keep and action items to track. Two unknowns block it: where recordings accumulate (a recordings inbox, a downloads dir, wherever the meeting tooling drops them), and whether filing should also extract action items into the destination's =todo.org=. - Decision: We will defer transcript routing to vNext. Both the source-location dependency and the file-only-vs-extract-action-items question are deferred with it, to be settled when the vNext work is specced. v1 ships task routing only. - Consequences: easier — v1 isn't blocked on the unresolved source location. Harder — until vNext, a meeting recording still has no automatic home; only its action items (if filed as tasks) route through v1. ** DONE Keep defer-and-stage and the router as distinct policies - Context: the 2026-06-12 Skeptical Review added a defer-and-stage path in =process-inbox.org= that files a =[#B]= VERIFY for shared-asset proposals parked for review. That also turns an inbox item into a =todo.org= task — overlapping surface with this router. - Decision: We will keep them distinct. Defer-and-stage parks a proposal-under-review locally as a VERIFY; the router moves an accepted keeper to its home project as a TODO. They differ on review status (proposal vs accepted) and destination (local vs cross-project), and share only the atomic move helper, not the policy. Reading B makes the split clean: the router acts on accepted keepers, never on proposals under review. - Consequences: easier — two clear, non-competing policies on one shared primitive. Harder — the workflow prose must name the boundary so a future reader doesn't collapse them and reintroduce the ambiguity. * Implementation phases ** Phase 1 — Widened project discovery A discovery function returning every project with a =todo.org= that has a clean Open Work heading, reusing =tc--find-section=. Unit-tested against fixtures: =.ai/= project, plain-code-repo-with-todo, todo-without-Open-Work (excluded), ambiguous-Open-Work (excluded). Leaves the tree working — nothing calls it yet. ** Phase 2 — Atomic cross-project move helper Extend =todo-cleanup.el= (or sibling tool) with a "move this subtree into project X's Open Work" operation that inserts at the destination and removes the source as one step, stamping the provenance line. ERT coverage: successful move, missing-destination-heading refusal, source-removal-on-success, no-partial-move-on-failure. ** Phase 3 — Recommendation engine + candidate-set marking Infer destination from item content against the discovered list, with a confidence label. Pure function over (item, project-list) → (destination, confidence). Unit-tested: strong match (project named in item), weak match (topic-only → low-confidence), no match (stays put). Also settle the candidate-set marking (tag at file time vs CREATED-this-session inference) so the router considers only session-filed inbox keepers, never the standing backlog. ** Phase 4 — Wrap-up step wiring Add the router sub-step to =wrap-it-up.org= Step 3: surface the batch, the two options, apply-on-go via the Phase 2 helper. Per the D1/D5 decisions once settled. Sync the =.ai/= mirror. ** Phase 5 — Transcript routing (vNext, gated on the transcript decision) Only after the transcript-scope decision resolves. File a recording into the destination =assets/= per =working-files.md=, batch go/skip mirroring the task router. * Acceptance criteria - [ ] At wrap, an inbox item naming another project is surfaced with that project as the recommended destination. - [ ] "Go" moves every recommended item into its destination's Open Work section as a valid org task with a provenance line, and removes it from the source. - [ ] "Skip" leaves every item in place and the wrap completes cleanly. - [ ] A destination =todo.org= without a clean Open Work heading is surfaced and skipped, never corrupted. - [ ] A low-confidence recommendation is visibly labeled in the surfaced list. - [ ] A plain code repo with a =todo.org= (no =.ai/=) is a valid destination. - [ ] A failed move leaves both source and destination unchanged (no partial move). * Readiness dimensions - Data model & ownership: items are org subtrees; the destination owns the moved task after the move (provenance note records origin). N/A for remote/cached state — all local files. - Errors, empty states & failure: missing/ambiguous Open Work heading → skip+surface; failed move → atomic no-op; empty routable set → router stays silent (no prompt). - Security & privacy: N/A — local org files, no credentials or external services. - Observability: the move shows in the destination's git diff plus the provenance line; the surfaced batch list is the pre-move view. - Performance & scale: bounded by inbox size (single digits) and project count (tens); no hot path. - Reuse & lost opportunities: reuses =tc--find-section= and todo-cleanup's subtree-move; widens existing discovery rather than adding a parallel one. - Architecture fit & weak points: the recommendation engine is the weak point (a wrong-confident destination is the worst failure) — mitigated by the confidence label and reviewable batch list. - Config surface: possibly a discovery-root list (defaults to =~/projects/=, =~/code/=, matching =inbox-send.py=). Name it if it needs to be user-visible. - Documentation plan: =wrap-it-up.org= step prose; a note in =cross-project.md= that the router is a sanctioned cross-project write path. - Dev tooling: ERT for the elisp helper + discovery; the existing =make test= picks up new test files by glob. - Rollout, compatibility & rollback: additive workflow step; rollback is removing the sub-step. No persisted-data migration. - External APIs & deps: none. * Risks, Rabbit Holes, and Drawbacks - *Recommendation accuracy is the rabbit hole.* A confidently-wrong destination silently files a task in the wrong project. Dodge: keep the engine conservative, label low confidence, and keep the batch list reviewable before the keystroke. Don't chase a clever inference model in v1. - *Two inbox-touching steps* (sanity check + router) risk reading as redundant. Dodge: the D1 decision states the gate-vs-optional split in the workflow prose. - *Scope creep into transcripts* before the source-location question is answered would stall v1. Dodge: transcripts are explicitly vNext behind decision D4. * Review and iteration history ** 2026-06-13 Sat @ 01:23:13 -0500 — Claude Code (rulesets) — author - What: initial draft. Problem, goals/scope tiers, two-altitude design, alternatives, six decisions (three DONE from grounding, three TODO for Craig), five implementation phases, acceptance criteria, readiness dimensions, risks. - Why: the archsetup 2026-06-13 handoff cleared the spec bar in inbox triage and was filed spec-bound rather than applied. This draft turns the proposal into a reviewable design with the open questions isolated as decision tasks. - Artifacts: proposal source at =docs/design/2026-06-13-wrapup-inbox-transcript-routing-proposal.org=; grounded against =wrap-it-up.org= Step 3, =todo-cleanup.el= =tc--find-section=, and =inbox-send.py= discovery. ** 2026-06-13 Sat @ 01:36:28 -0500 — Craig Jennings + Claude Code (rulesets) — author - What: resolved all three open decisions. The router's input is Reading B (filed keepers that belong elsewhere, not raw inbox files), so D1 keeps it a separate sub-step from the inbox gate and D5 keeps it distinct from the defer-and-stage router; D4 defers transcript routing to vNext. Reworked the design (input definition, a candidate-set note bounding the router to session-filed keepers) and Phase 3 to match. Cookie now [6/6]; Status moved to ready-for-review. - Why: Craig chose Reading B after the A-vs-B input ambiguity surfaced as the root under D1 and D5. Reading B keeps the inbox gate, the router, and defer-and-stage each simple instead of entangling three mechanisms. - Artifacts: this spec; the candidate-set marking mechanism is the one detail flagged for spec-review to pin.