Dictate Goals
This is the canonical goal document for Dictate. INTENT.md owns the durable
product intent; this file owns the current operating goals, release blockers,
and future target queue.
Current Release Lane
The current public Windows lane is the Microsoft Store update draft for
Arc Forge Dictate.
Current state:
- Microsoft Store
Submission 2is open in Partner Center. - Store listing copy, screenshots, and square Store assets are staged.
- Package upload is intentionally held until the next app version is bundled and tested.
- Certification submission is intentionally held.
Release gates:
- Bundle the next Windows Store MSIX.
- Test the bundled version before upload.
- Upload the package to the existing Store draft.
- Run a real Windows 11 install/runtime smoke after package download or Store availability.
- Review the Partner Center draft.
- Submit for Microsoft certification only after explicit approval.
To Be Implemented / Proven Before Stable
These are product requirements that must be either implemented or explicitly closed as already done before the next stable public push.
- Production cloud API account path: The hosted API/control-plane system must be live in production, not only sketched locally. Acceptance requires an owner-linked production account with real entitlement, usage, and hosted transcription API access; that account must be the account used by the maintainer's day-to-day Dictate installs. This must be proven on Windows as well as the current development platform.
- Audio visualizer proof: The visualizer must be confirmed responsive to real microphone audio, not only synthetic animation. Acceptance requires a runtime smoke showing live input changes the visualizer, plus a graceful denied/no-device fallback.
- Hardware-tiered install defaults: First-run setup should classify the
machine into
minimum,recommended, oradvancedfrom simple local facts such as OS, CPU cores, RAM, GPU/CUDA availability, and disk headroom. Default to English Parakeet on CPU when available. On NVIDIA CUDA, provide an English-only performance lane and a multilingual quality lane; the current integrated CUDA-capable backend remains faster-whisper until Parakeet CUDA is implemented and benchmarked. AMD GPUs need separate MIGraphX/ROCm and Windows DirectML/MIGraphX paths rather than assuming the CUDA stack applies. Minimum-spec must remain a good experience with consistent transcription quality; it may trade speed and disable heavier features. Advanced-spec is reserved for GPU and future local WhisperX/diarization work until those paths pass packaging, legal, and benchmark gates. - Benchmarked quality and speed tiers: Define simple benchmark targets for quality and speed before changing model defaults. Quality should vary little across tiers; speed and optional features may vary. Benchmark details live in ../benchmarks/README.md.
- No default hotwords: Public builds must not ship user-visible default hotwords. The default config is empty, and UI placeholders must not seed sample words into real or perceived state. Future hotword UX should let a user click an incorrect dictated word, correct it, update the copyable transcript, and add the correction to hotwords automatically. Manual training is an acceptable early-access feature before automated correction.
- Early-access feature releases: Some working features should be gated from stable releases because customers pay for early access. Decide the release model before shipping paid early access: either keep the repository open source with gated release channels/artifacts, or move to an open-core model where selected features are gated while the core remains open.
Production P0: Dictate Pro Purchase And Entitlements
Status: Architecture documented; public marketing is live; purchase path is not wired end-to-end.
The Upgrade to Pro button on https://arcforge.au/download/dictate currently
links to /login as a portal sign-in placeholder. Dictate Pro cannot be sold or
unlocked end-to-end until Stripe, the account system, and this app agree on
entitlement state.
Before treating Dictate Pro as shippable:
- Stripe: Create Dictate Pro product/price(s) and checkout, separate from Arc Forge Agents Go/Plus/Ultra. Likely through gateway billing API + webhooks; see the checkout sketch in dictate-pro-subscription-architecture.md.
- Account / entitlement service: Map active subscription state to a
Dictate Proentitlement on the customer account, including grace, expired, and quota states. - Website: Point Upgrade to Pro at the real checkout entry.
- Desktop app: Expose entitlement in
GET /api/state; gate hosted meeting mode, quota UI, and Pro affordances on subscription state. - Verification: End-to-end flow: marketing CTA -> pay -> portal shows Pro -> app unlocks meeting transcripts and early-access features.
Cross-repo surfaces:
arc-forge-website: marketing CTA and download page.- gateway/deck: checkout and webhooks.
- customer portal:
console.arcforge.au. dictate: entitlement consumption and UI gating.
Product Surface Goal
Dictate's current production surface is desktop. The desktop version should keep a local-first posture for normal dictation wherever that gives the best user experience, privacy, and reliability.
The product direction is broader than desktop:
- Keep the desktop app as the primary daily-driver implementation target for now.
- Do not design product concepts or architecture in a way that prevents a future mobile/on-the-run capture surface.
- Treat mobile as a future product surface for quick capture, transcript review, and sending text onward, not as a replacement for desktop push-to-talk.
- Keep shared concepts portable across surfaces: captures become transcripts or notes, transcripts can be accepted, copied, inserted, exported, or expanded, and hosted-model use is explicit.
Transcription Notes Goal
Dictate should move toward a transcript-first note capture model. The canonical local transcription, recording, meeting, model-lane, timestamping, speaker attribution, and GPU plan is TRANSCRIPTION_PLAN.md. That document owns the current model direction.
Immediate direction:
- Treat every capture as a
Note: short dictation and long meetings are the same record type with different duration, provider, speaker, and processing state. - Preserve two user intents through mode/defaults, not separate heavy screens: dictation/plain recording and meeting.
- Meeting mode always requires speaker attribution. The UI should expose this as "Meeting" rather than engine terminology such as "diarization".
- Local recording and push-to-talk dictation should use the same ASR behavior; only Meeting changes the pipeline by adding speaker attribution.
- Hide or de-emphasize hosted providers that cannot satisfy the meeting contract. Provider selection should be capability-based, not a flat model list.
- Store transcripts as the durable artifact. Do not retain live-recording audio by default.
- Long recordings must be streamed/chunked. The app must not hold hours of raw audio in memory, upload one giant file, or block on one final transcription request.
- Persist note metadata and transcript segments incrementally: note id, mode, provider/model, start/end timestamps, duration, processing status, chunk sequence, speaker ids/labels, transcript text, and errors if any.
- The UI should support in-app acceptance of dictation: after capture, the user can read, edit, accept, copy, insert, export, or expand the transcript without requiring another focused text field.
Local meeting diarization:
- Follow TRANSCRIPTION_PLAN.md for ASR, diarization, timestamping, AMD/NVIDIA GPU, and benchmark decisions.
- Before release, benchmark accuracy, RAM/VRAM use, runtime, installation weight, and long-meeting stability on representative recordings.
- If local meeting speaker attribution is exposed before it is broadly proven, label it as experimental or high-performance-machine-only.
Silence auto-pause (note recording)
Status: Partial. Auto-pause after 120s sustained silence, shared pause click, and UI copy for silence-triggered pause are implemented. Manual pause/finish and chunked local transcription are in place.
Long note sessions should not hold the mic open indefinitely during silence. Quiet meetings must not fail silently — the user should get a clear, gentle signal before capture stops waiting.
Direction:
- Auto-pause on sustained silence — after 120 seconds below a speech threshold, pause the note recording (same session as manual pause; do not auto-finish).
- Paused + silence — do not count silence while already paused. The idle timer resets on resume.
- Remote cost — auto-pause saves mic battery/CPU during long waits; VAD-at-transcribe only helps after the user stops and only affects decode, not live capture or remote upload size.
- Audible feedback — play a short system or shipped sound (gentle click) when pause happens, for both auto-pause and manual pause. Reuse one asset so pause always feels the same.
- UI — surface paused state clearly (existing cradle + finish flow); optional copy such as “Paused — no speech detected” for auto-pause only.
Non-goals for the first slice:
- Do not auto-finish on silence; finishing stays explicit.
- Do not use a shorter threshold that would interrupt legitimate quiet meeting stretches without the audible pause cue.
Future Target: Media Notes
Status: Future product target selected. Implementation not started.
Media Notes extend the transcript-first note model to imported audio/video. This is not the current release blocker, but it is the next coherent product expansion after the live transcription and Dictate Pro entitlement path is stable.
The feature is not just file transcription. It is the foundation for video digestion, where transcript segments, timestamps, playback, frames, visual context, and extracted work can be joined into one useful note.
First production slice:
- Add Import audio/video to the Notes or notebook surface.
- Accept common phone and web media formats:
.m4a,.mp3,.wav,.mov,.mp4, and.webm. - Imported media becomes a durable note immediately, with visible processing
state:
queued,processing,ready, orfailed. - Keep recorded dictation and imported media in one Notes experience, but do not force imported media into the current rolling text-only history store.
- Preserve the original imported file and derived assets until the media note is deleted. This is different from live dictation, where audio should still be discarded by default after transcription.
- Make hosted-model use explicit when a step leaves the device.
Media Notes Phase 1: Transcription
Ship this first.
- Create a durable media-note store separate from
recent-history.json. - Add a local async job runner for media processing.
- Copy the original imported file into Dictate app data.
- Probe metadata with
ffprobe. - Extract normalized audio with
ffmpeg. - Generate a timestamped transcript.
- Write
transcript.json. - Write
captions.vttfor in-app playback subtitles. - Optionally write
captions.srtfor export compatibility. - Show the media note in Notes with processing state.
- In expanded note view, show a player and clickable transcript segments that seek playback to the matching timestamp.
Media Notes Phase 2: Visual Grounding
After Phase 1 is stable:
- Generate a proxy video if needed for reliable playback.
- Sample frames around transcript segments.
- Add scene-change frame extraction.
- Store selected frame thumbnails as derived assets.
- Link transcript segments to relevant frames.
- Show frame evidence in the expanded media note.
Media Notes Phase 3: Digestion
After transcript and frame evidence are reliable:
- OCR selected frames.
- Run targeted multimodal analysis over transcript windows plus selected frames.
- Extract meeting action items, decisions, open questions, mentioned tools, and referenced files or people.
- Extract how-to procedures with step text, timestamps, and screenshots.
- Keep every generated item linked back to exact media timestamps and evidence frames.
Media Note Shape
{
"id": "media_...",
"kind": "audio|video",
"title": "Uploaded file name",
"createdAt": "...",
"status": "queued|processing|ready|failed",
"source": {
"originalPath": "...",
"mime": "video/mp4",
"duration": 612.4
},
"assets": {
"original": "...",
"audio": "...",
"proxyVideo": "...",
"captionsVtt": "...",
"captionsSrt": "...",
"transcriptJson": "...",
"framesDir": "..."
},
"summary": null,
"tasks": []
}
Media ingestion needs a richer transcript contract than
SpeechToText.transcribe(...) -> str:
@dataclass
class TranscriptSegment:
start: float
end: float
text: str
speaker: str | None = None
@dataclass
class MediaTranscript:
text: str
segments: list[TranscriptSegment]
words: list[dict] | None = None
The initial implementation can use an in-process worker thread with persisted job state. Avoid Redis, Celery, or a service dependency until Dictate has a server-hosted ingestion path that truly needs it.
Media Notes first-slice non-goals:
- Do not send the entire video to a multimodal model as the only pipeline.
- Do not build summaries, task extraction, OCR, or frame reasoning before timestamped playback and transcript navigation work.
- Do not store imported media in the existing capped recent-history file.
- Do not make this a separate video app. It belongs inside Notes.
Future Target: Local STT Quality Backlog
Status: Queued. Follows the local dictation streaming + hardware-aware
default-model work merged in the forge/local-stt-streaming review (dictation
now decodes overlapping, silence-aligned, prompt-threaded chunks that are merged
instead of a blind join of 2s context-free windows; one hardware-aware resolver
picks turbo on capable machines and small on weak CPUs, consistently across
startup, tray, doctor, UI, and installers).
These are the deliberately-deferred follow-ups from that review, roughly in priority order.
TOP PRIORITY — Input audio level management (AGC / normalization / clipping
guard). Real-world finding: on a machine whose microphone works fine in
Telegram, Zoom, and normal voice calls, Dictate produced hallucinated/garbled
transcripts because it captures the raw sounddevice stream with no gain
management. On a hot mic the raw capture clips (peak > 0 dBFS) and Whisper
emits coherent nonsense ("thanks for watching", "Kayla and I are happy to fund
it"); at low gain the RMS is too quiet and it hallucinates from the language
prior. Voice apps avoid this with WebRTC-style automatic gain control + noise
suppression; Dictate has none. Lowering the OS mic gain is only a fragile
manual workaround. Implement, in the capture/pre-decode path:
- Peak/RMS input normalization toward a target (e.g. ~ −20 dBFS RMS) with headroom, applied before the model sees the audio.
- Clipping / too-hot detection with a clear user warning (and, ideally, soft limiting) — a clipping mic currently fails silently, which is exactly the "unusable" symptom.
- Lower / adaptive Silero VAD threshold for quiet capture, and revisit the
hardcoded
SILENCE_RMSgate innote_chunker.pyso soft speech is not dropped as silence. - Consider optional lightweight noise suppression / AGC so Dictate "just works" on the same mics every other voice app handles. This is the second half of making local dictation genuinely usable — the streaming fix removed the boundary "confetti"; this removes the garbage-in/garbage-out from unmanaged input levels.
- Dynamic runtime backpressure / auto-tier-down. Static hardware gating
(RAM + core count) currently decides turbo-vs-small at startup. Add a runtime
guard that measures streaming decode real-time-factor and degrades gracefully
when the streamer falls behind: drop turbo->small mid-session, and when even
small cannot keep real-time (weak 2-4 core / no-AVX2 CPUs, Pi-class hardware),
disable local streaming and surface a "recommend a hosted key" notice via the
existing
result.notice/supervisor path rather than silently backing up the partial-audio queue and dropping words. - Quiet-microphone robustness. Add peak/RMS input normalization before
decode and lower the Silero VAD threshold for quiet capture; revisit the
hardcoded
SILENCE_RMSgate innote_chunker.pyso soft-spoken speech on built-in laptop mics is not misclassified as silence. - Language pinning. Persist a
languagesetting in config (and expose it viadictate config) and thread it through, so short accented utterances stop flipping language per chunk under auto-detect. - Hardware guidance docs. Publish a spec sheet in the README/install docs from TRANSCRIPTION_PLAN.md, including CPU minimums, NVIDIA CUDA, and first-class AMD GPU support.
- GPU local model lanes. Implement and benchmark the model lanes defined in TRANSCRIPTION_PLAN.md. Do not make any GPU or CPU multilingual lane the default until it passes the benchmark matrix and packaging checks.
- Dead-code cleanup. Remove the unwired
whisper_cpp_backend.py(plus its test, theWhisperCppModelliteral, and the stale NeMo__pycache__remnant), or wire it up deliberately — it is currently unreachable. - CLI flag rename (breaking). The installer prepare-step flag is still named
--no-prepare-turbo/-NoPrepareTurboalthough it now prepares the hardware-aware default. Rename to--no-prepare-modelin a batch with other CLI-contract changes so existing install scripts are not broken piecemeal.
Accepted limitation (documented, not a bug): when an utterance ends exactly on a
silence boundary, the final decoded chunk keeps long_form off-guard behavior
from the previous chunk; fixing it would require a re-decode that reintroduces
end-of-clip latency, which the streaming design explicitly avoids. Trailing
silence is already trimmed, so hallucination risk is negligible.
Current Operating Posture
- Microsoft Store is the primary public Windows distribution target.
- Signed website/GitHub downloads remain a secondary fallback path.
- Hosted PowerShell bootstrap instructions are developer/source install guidance, not the normal public Windows install path.
- New npm package and install paths use
@arcforgelabs/dictate. - The deprecated personal npm package path should remain published only as a compatibility landing point unless there is a specific security or legal reason to remove it.
- Dictate is a public open-source repository.
- Arc Forge ClawSweeper and its durable state repository remain private.
- ClawSweeper is currently review/comment only for Dictate.
- Partner Center state changes over time; check Partner Center or
.github/workflows/msstore-publish-msix.ymlinmode=statusfor live Store status rather than relying on historical run IDs in this public doc.