Skip to content

Voice interview channel: harvest open questions into a pre-recorded pruning tree, ask them as voice notes, record answers back into plan docs #3330

Description

@valorengels

Plan: https://github.com/tomcounsell/ai/blob/main/docs/plans/voice-interview-open-questions.md

Context: This repo keeps feature plans as markdown under docs/plans/, each carrying an ## Open Questions section for decisions only the human owner can make. Today those questions are answered by typing, either in a local Claude Code session or through the Telegram poll channel shipped in #2701. This issue adds a spoken channel: the agent pre-records each question as an audio clip, plays them back one at a time, and writes the recorded answers into the plan docs that asked them. The target user is a remote architect who would rather talk for fifteen minutes than read and type.

This feature is per-project and per-machine by design. Each project in projects.json is owned by exactly one machine (single-machine ownership), and the tooling built here runs against one repo on the machine that owns it, with its own calling link tied to that project. It ships to other machines the way every global capability does, and each instance harvests only its own repo. Cross-project harvesting is an explicit non-goal.

Problem

Unanswered architect questions are the current throttle on delivery velocity. Work sits waiting on a decision only the owner can make, and the channel for getting those decisions is narrow enough that the queue drains slower than it fills. The intended cadence for this feature is a call a few times per week, which is also why per-session preparation cost is acceptable and why clip reuse matters.

Open questions in this repo are answered by typing, and only when the owner happens to be at a keyboard with enough context loaded to answer well. Two things are broken.

First, the corpus is not machine-readable, so nobody knows what is actually outstanding. There is no tool that enumerates open questions across plan docs and issues. The one extractor that exists (bridge/message_drafter.py:100 _extract_open_questions) reads agent stdout, not files, and would misfire on the real corpus (see Recon Summary). Answering questions therefore requires a human to remember which plan asked what.

Second, there is no spoken channel at all. Outbound audio exists (tools/tts/, /do-debrief) and inbound transcription exists (tools/transcribe/), but they have never been joined into a question-and-answer loop, and every synthesized clip is deleted immediately after send.

Current behavior:

  • No command answers "what open questions exist right now, and which ones block the most work?" Verified: nothing in tools/, scripts/, or the skills directories walks docs/plans/ or gh issue list for questions.
  • Answering happens through /ask-me, which drafts its blocker list from model context per invocation. Nothing is harvested from disk, so the list is not reproducible between runs.
  • tools/tts.synthesize() writes a fresh OGG/Opus file per call and the documented send path passes --cleanup-after-send, so bridge/telegram_relay.py:690 unlinks the audio after delivery. There is no clip library and no content-keyed audio cache anywhere in the repo.
  • Answers arrive as chat prose. Getting them back into the plan doc that asked the question is manual.

Desired outcome:

  • python -m tools.open_questions list prints every genuinely open question across docs/plans/*.md and open issues, each with a stable ID, its source location, and an unblock-leverage score.
  • The owner receives questions as Telegram voice notes, one at a time, highest-leverage first, and answers by replying with a voice note. Answering one question can prune others that its answer moots, so the session ends early rather than reading the whole list.
  • Recorded answers land in the ## Open Questions section of the plan doc that asked them, attributed and dated, committed to main.
  • Re-running the harvester after a session shows the answered questions resolved and does not re-ask them.

Definitions

Term Definition Reference
Plan doc A markdown feature plan under docs/plans/, with YAML frontmatter including status, type, appetite, and tracking (the GitHub issue URL). docs/plans/, PLAN_TEMPLATE.md
## Open Questions The plan-doc section holding decisions the human owner must make. Mandated by the plan template; /do-plan removes it on finalize. PLAN_TEMPLATE.md:495
Question tree A pre-computed DAG over harvested questions. Each node is one question; each edge says which other questions a given answer disposition moots. Traversal order is decided before the session, not during it. New in this issue
Pruning edge An edge asserting "if the answer to Q1 is disposition A, then Q4 and Q7 no longer need asking." New in this issue
Unblock leverage How much work a question's answer releases: count of plan docs and open issues that cannot proceed until it is answered, plus questions it would prune. New in this issue
Clip A persistent OGG/Opus audio file of one question's spoken text, content-addressed by the question's hash so it is reused rather than re-synthesized. New in this issue
Project isolation Each project is owned by one machine, and its knowledge sources partition by project key. Context does not cross project boundaries. docs/features/single-machine-ownership.md, CLAUDE.md
Voice note A native Telegram audio bubble. Sent by valor-telegram send --voice-note, which enqueues to the Redis outbox the relay drains. tools/valor_telegram.py:1379
Defaulted question An item under ## Open Questions that is explicitly not an ask: it records a decision already made with a stated default, overturned only by an owner objection. "Silence keeps the default." docs/plans/improvement-controller-lane-3-control-journal-fenced-dispatch.md:787

Prior Context

Solution Sketch

Direction is settled: pre-record the questions, so the delivery agent only plays clips and captures answers. All intelligence runs before the session starts, where it has full context and no latency budget. Spending generously on preparation is the intended trade.

Scope boundary, load-bearing. Everything below operates on a single repo: this one, on this machine. The harvester reads the local working tree and this repo's own issues. It must not reach into another project's checkout, another repo's issues, or a remote repo's contents API. Other machines get the same capability by the normal global-tooling sync and harvest their own project independently, each with its own calling link.

Four components.

1. Harvester. A tool that enumerates open questions from docs/plans/*.md (excluding tools/plan_doc_scope.py's NON_LANE_PLANS) and open issues, emitting records with a content-hash ID, source path and line, and the owning plan's tracking: issue. It must distinguish genuinely open questions from resolved ones and from defaulted ones. The corpus uses at least seven mutually incompatible resolution conventions, so this is the hard part and the part with standalone value.

2. Preparation pass. Score each question by unblock leverage; for each, enumerate plausible answer dispositions and compute which other questions each disposition moots; pick the root that maximizes expected pruning; emit a tree bounded by a target session length. For each node, author the spoken question text, a deeper-context clip for "what's this about?", a readback line, and a draft of the writeback that each anticipated disposition would produce. Synthesize every clip and store it content-addressed so re-preparation only re-cuts what changed.

3. Delivery. One clip per outbound voice note; the owner's reply voice note is the answer to the clip that was just sent. The binding is structural per message, which is why the first transport should be Telegram rather than a live call: no timestamp alignment is needed. After each answer, follow the pre-computed pruning edges to select the next node. Off-script answers stop the traversal and are recorded verbatim for the writeback pass.

4. Writeback. Per plan doc, draft the resolution and any issue edits the answer implies. Drafting fans out in parallel; applying and committing is strictly sequential and single-writer. See Pre-requisites: concurrent plan-doc writes on the shared main checkout have already caused unrecoverable damage, and no lock exists. Pre-drafted writebacks are re-verified against current main before being applied, because they were authored against an older tree.

Constraints discovered in recon that the plan must honor:

Recon Summary

Four parallel Explore agents. Findings that changed the issue are marked.

Confirmed: 6 items

  • No aggregator exists. Nothing in tools/, scripts/, or the skills directories harvests questions from plan docs or issues. No duplication risk.
  • ## Open Questions is a real convention with 79% coverage: 19 of 24 top-level plan docs carry a question-or-decision section. PLAN_TEMPLATE.md:495 is its source.
  • A reusable starting point exists: bridge/message_drafter.py:100 _extract_open_questions, with tests at tests/unit/test_open_question_gate.py. Its heading regex and placeholder filter are sound; its input is stdout, not files.
  • Audio primitives are sufficient for the Telegram leg. Outbound: tools/tts.synthesize() returns OGG/Opus, never raises, local Kokoro ONNX with OpenAI tts-1 fallback. Inbound: tools/transcribe.transcribe() via SuperWhisper with an OpenAI whisper-1 fallback, and bridge/media.py:461 already transcribes inbound voice notes into the agent's message text automatically.
  • Delivery has a supported non-bridge producer path: valor-telegram send --voice-note, or RPUSH the payload directly. reflections/pm_briefings/delivery.py is a working in-process precedent.
  • Commit rules for plan-only commits on main are as described in the Solution Sketch. core.hooksPath is set to .githooks on this machine, so both validation legs are live.

Revised: 4 items

  • The in-repo corpus is 15 genuinely unresolved questions today, and that is a floor set by the bottleneck, not a ceiling on demand. A full pass over the 24 top-level plan docs found 15 genuinely unresolved questions in 6 documents (13 under a strict reading), against 46 already resolved and 11 "defaulted" items that are explicitly not asks. Of 127 open issues, only 16 have any question-shaped section. Owner ruling: the low count reflects velocity already throttled by waiting on architect answers, so draining the queue raises velocity and generates more questions. The count is therefore not a gate on building, and the original framing of this bullet as a reason to reconsider scope is withdrawn.

  • Resolution marking is not machine-readable. Seven incompatible conventions in active use: section-level "None.", heading rename to ## Resolved Questions, a blockquote RESOLVED banner over retained original text, per-item inline markers with varied wording, strikethrough, "all open questions resolved" prose, and RETIRED as a disposition distinct from resolved. Nine distinct heading strings, one of them an H3. The naive extractor would emit 9 false positives from the defaulted items in improvement-controller-lane-3 and lane-5 alone.

  • Issues are effectively not harvestable as a structured source. 16 of 126 carry a question section, across 14 distinct heading strings, varying in case, heading level, and plurality. Treat issues as a best-effort secondary source, not a peer of plan docs.

  • Writeback must be sequential, not a parallel fan-out. This reverses the original framing of one subagent per plan doc writing concurrently. Plan-doc writes in the shared main checkout have no single-writer protocol #2650 documents seven real collision shapes on the shared main checkout, including an unrecoverable 2026-08-13 incident. No lock exists. Parallel drafting is safe and retained; parallel writing is not.

Pre-requisites: 3 items

  • No single-writer protocol for plan docs. docs/archive/plans-completed/plan-doc-single-writer-lease.md still reads status: Ready and was moved into the completed-plans archive by a content-free rename (433c166a9), apparently swept up by PR Move completed plans to docs/archive/plans-completed/ #2942's bulk move. It was archived as completed without being built. This issue does not depend on it being built, because sequential single-writer writeback sidesteps it, but that discrepancy should be filed separately.
  • A resolution-marking convention needs choosing before the harvester can be reliable. Either normalize the seven existing shapes into one (a PLAN_TEMPLATE.md and /do-plan change, touching existing docs) or teach the harvester all seven. The defaulted-question shape is the specific blocker: it lives under ## Open Questions but must not be asked, and no marker distinguishes it mechanically.
  • /do-plan removes the ## Open Questions section on finalize, which means a finalized plan has no place to record the answer. The writeback target needs deciding for plans already past that point.

Dropped: 3 items

  • The live phone call, and the Tel-Agent dependency, are out of scope. Tel-Agent is AGPL-3.0 with network-service copyleft, speaks only SIP so it needs a PBX reachable on the LAN, and does not document pre-recorded clip playback (it is built around streaming TTS). Independently, this repo has no streaming STT: tools/transcribe is batch and file-based, driving a macOS GUI app over the filesystem. A live duplex leg is greenfield across licensing, infrastructure, and transport. It should be its own issue, taken only if the Telegram leg proves the pipeline.
  • The live 5-way intent classifier. Only needed for duplex traversal. Also: the nearest measured precedent, run_typed_local on granite4.1:3b, runs ~1.1s median and ~1.4s p95 per config/settings.py:383, which is marginal inside a call's turn-taking loop. Deferred with the call itself.
  • Cross-project harvesting. An earlier draft of this issue considered aggregating questions across the projects in projects.json, including reading plan docs from repos with no local checkout via the GitHub contents API. Rejected by owner ruling: client projects are deliberately separate and each is hosted on its own machine, so a cross-repo harvest would both violate project isolation and put one machine's calling link in front of another project's questions. Each machine harvests its own repo only. The probe that motivated the idea is recorded here so it is not retried: 4 of the 9 configured projects are not checked out on this machine, and 2 of those have no discoverable GitHub repo under either org.
  • Timestamped transcript alignment. Unnecessary on the Telegram transport, where one inbound voice note is structurally the answer to one outbound clip. It would also force the cloud STT path, since the local SuperWhisper backend returns no timestamps and no detected language.

Acceptance Criteria

  • A CLI lists every genuinely open question across docs/plans/*.md and open issues, each with a stable content-hash ID, source path and line, and owning tracking: issue.
  • The harvester correctly classifies all seven resolution conventions found in the corpus, and excludes defaulted questions. Verified against the audited counts: 15 genuinely unresolved (or 13 under the documented strict reading), 11 defaulted, 46 resolved.
  • The harvester excludes NON_LANE_PLANS and does not crash on the known frontmatter irregularities (tracking: null, last_comment_id: none, bare last_comment_id:, the two plans with no frontmatter).
  • A preparation pass emits a question tree: ordered nodes, pruning edges, and a leverage score per node, bounded by a target session length.
  • Every node has a synthesized OGG/Opus clip, content-addressed by question hash, persisted outside the send-and-delete path, and reused on re-preparation when the hash is unchanged.
  • Spoken clip text contains no multi-digit identifiers and no clip exceeds the 4096-character synthesis limit.
  • A session delivers clips as Telegram voice notes one at a time, and an inbound voice-note reply is bound to the clip it answers without timestamp alignment.
  • Answering a question follows its pre-computed pruning edges, and a session that prunes its remaining nodes ends without asking them.
  • An off-script answer stops traversal, is recorded verbatim, and does not produce a fabricated resolution.
  • Writeback drafts in parallel and applies sequentially: one writer at a time, Edit only on plan docs, staging and commit in one atomic invocation with explicit paths, no git add -A.
  • Writeback commits touching only docs/plans/ carry no closing keyword and pass scripts/check_issue_disposition.py.
  • Every pre-drafted writeback is re-verified against current main before being applied; a drifted question is reported, not silently overwritten.
  • An answer that does not resolve its question leaves the question open and records the partial steer.
  • The harvester reads only this repo's working tree and this repo's issues. It has no code path that reads another project's checkout, another repo's issues, or a remote repo's contents API.
  • Re-running the harvester after a session shows the answered questions resolved and does not re-ask them.

Open Questions

  1. Normalize the resolution convention, or teach the harvester all seven shapes? Normalizing means editing existing plan docs and changing PLAN_TEMPLATE.md, and would make every future harvest cheap. Teaching seven shapes is contained but permanently fragile.
  2. How are defaulted questions marked going forward? They live under ## Open Questions but must never be asked. A distinct heading, an explicit per-item marker, or a frontmatter key.
  3. Should this share the /ask-me transport or sit beside it? Polls already ship, /ask-me already ranks and merges questions, and Poll feature: deferrals carried out of PR #3092's review, with no tracking issue #3095 is open backlog on that channel. A voice transport inside /ask-me reuses the ranking and the needs_human edge; a separate skill keeps the poll channel from growing a second mode.
  4. Where does the answer go when /do-plan has already removed the plan's ## Open Questions section? The tracking: issue, a new section, or reinstating the section.
  5. Does the delivery half belong in .claude/skills-global/ from the start? Every machine will need it, but the harvester's handling of resolution conventions may turn out to be repo-specific, which is what .claude/skill-context/ exists for.

Downstream

This issue feeds into /do-plan, which will produce a plan document at docs/plans/{slug}.md, then executed by /do-build.

Constraints for the planner:

  • No new external vendor dependency. The Telegram leg needs nothing beyond what ships today. Telephony, streaming STT, and hosted voice vendors are out of scope per the Recon Summary, and the repo declares no STT or TTS vendor key beyond OPENAI_API_KEY.
  • Do not build a plan-doc lock. Sequential single-writer writeback is the chosen mitigation. If the plan concludes a lock is required, that is a scope change to raise, not to absorb.
  • Single project, single repo. No cross-project harvesting, no reading repos that are not checked out here. The corpus is this repo's docs/plans/ and this repo's issues.
  • Assume replication. Other machines will run this against their own projects, so avoid hardcoding anything specific to this repo's plan-doc dialect in a place that cannot be overridden per repo.

Triage 2026-09-15

Status: planned feature work, not a defect. Not subject to the hotfix/complexity questions applied to the rest of today's batch.

Classification: net-new capability with a written plan (docs/plans/voice-interview-open-questions.md). Largest single scope opened in the last three days.

Triage note: this is the one issue in the batch that adds surface area rather than repairing it. Every other open issue from this window is a guard, ledger, or fixture that drifted from reality. Worth sequencing this behind the pipeline-reliability cluster (#3312, #3303, #3301) — a new per-project, per-machine channel inherits whatever SDLC and lane-identity weaknesses are still unfixed when it lands.

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    bridgeRelated to the Telegram bridge (bridge/)planTracked plan document in docs/plans/skillsRelated to skills, tools, and SDLC pipeline

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions