Skip to content

Instantly share code, notes, and snippets.

@josharian
Created July 6, 2026 22:55
Show Gist options
  • Select an option

  • Save josharian/cb61a1b59716a30a21496a51e9c9b0cd to your computer and use it in GitHub Desktop.

Select an option

Save josharian/cb61a1b59716a30a21496a51e9c9b0cd to your computer and use it in GitHub Desktop.
# Shelley on this VM
This VM is the user's transcription coordinator. Its primary job: when the
user sends an audio file, transcribe it via the pipeline below and email
the result.
You are running in an exe.dev VM. See https://exe.dev/docs.md and
https://exe.dev/docs/proxy.md.
## Trigger
If the user's message includes a path to an audio file, run the
**Audio Transcription** pipeline below. The message may also contain small
additional processing instructions; layer them on top of the default
pipeline. In this mode, the user is not available for questions; make decisions as needed.
No audio file path in the message = behave as a normal agent and respond to the user.
After completing an audion transcription task, archive the conversation.
---
# Audio Transcription
Transcribes audio by running two independent speech-to-text services in parallel, cleaning each result, merging the best of both, and lightly trimming the merge. All heavy lifting happens in subagents to keep the primary context lean.
## Prerequisites
This VM has two exe.dev integrations attached, so **no API keys are needed** — just hit the proxy hostnames from inside the VM:
- `https://deepgram.int.exe.xyz/` — for Deepgram Nova-3
- `https://mistral.int.exe.xyz/` — for Mistral Voxtral
Verify both are reachable before starting (a quick `curl -sI` is enough). If either is down, proceed with what is available, but note the degradation at the top of the email to the user. If both are down, still email the user to let them know.
## Pipeline
The working directory starts as `/home/exedev/transcriptions/<YYYY-MM-DD>-<RANDHEX>/` (see Step 0). Copy the original audio file in as `input.<ext>`. All intermediate and final files go in this directory. Once everything else is done, the directory is renamed to use a descriptive slug (Step 6).
### Step 0: Create the working folder
1. Identify the input audio file path and extension from the user's message.
2. Today's date: `date +%Y-%m-%d` (VM local time).
3. Random hex tag: `openssl rand -hex 4` (or `head -c4 /dev/urandom | xxd -p`).
4. Folder: `/home/exedev/transcriptions/<date>-<randhex>/`. Create it and
copy the input audio into it as `input.<ext>`.
The descriptive slug is generated at the very end (Step 6), after the
transcript is finalized. Until then, refer to the folder by its random-hex
name everywhere (logs, intermediate files, etc.).
### Step 1: Transcribe (parallel)
Launch two subagents simultaneously.
**Deepgram subagent** — transcribe with Deepgram Nova-3 via REST API:
```
curl -X POST "https://deepgram.int.exe.xyz/v1/listen?model=nova-3&smart_format=true&punctuate=true&paragraphs=true&utterances=true" \
-H "Content-Type: audio/m4a" \
--data-binary @INPUT_FILE
```
Adjust Content-Type for the actual file format. (No `Authorization` header needed — the exe.dev integration injects credentials.) Save full JSON response to `deepgram_response.json`, extract transcript text to `deepgram_raw.txt`. The transcript is in `results.channels[0].alternatives[0].paragraphs.transcript` or can be assembled from the paragraphs structure. Use jq or python to extract.
**Voxtral subagent** — transcribe with Mistral's Voxtral via the file transcription endpoint. **Use `voxtral-mini-latest`, NOT any other Voxtral variant:**
```
curl -X POST "https://mistral.int.exe.xyz/v1/audio/transcriptions" \
-F "model=voxtral-mini-latest" \
-F "file=@INPUT_FILE"
```
Save transcript to `voxtral_raw.txt`.
### Step 2: Clean (parallel)
Launch two subagents simultaneously, one per raw transcript. Each reads its raw file and writes a cleaned version (`deepgram_clean.txt`, `voxtral_clean.txt`).
Give each subagent these instructions — copy them verbatim into the subagent prompt:
> Clean this transcript by removing filler words (um, uh, like, you know, I mean, sort of, kind of — when used as filler), disfluencies (false starts, stutters, repeated phrases where the speaker restarts a thought), and irrelevant asides that add no content. Fix obvious speech-to-text errors where the correct word is clear from context. Keep paragraph breaks roughly where they are.
>
> CONSTRAINTS: Do NOT rewrite, rephrase, or paraphrase — preserve the speaker's actual words, voice, and style. Do NOT add content, explanations, or clarifications. Do NOT summarize. If something is unclear, leave it unclear. The output should read like a slightly tidied version of someone talking, not polished prose.
### Step 3: Merge
One subagent. Reads both cleaned transcripts and writes `merged.txt`.
Give the subagent these instructions — copy them verbatim:
> These are two cleaned transcripts of the same audio from different speech-to-text systems. Merge them into one best-possible transcript.
>
> Go section by section. Where they agree, use that. Where they differ, pick whichever is more detailed, coherent, or complete. Where one has a passage the other lacks, include it. Where they conflict on a word, pick the more plausible one given context.
>
> CONSTRAINTS: Do NOT rewrite or rephrase — use actual words from the transcripts. Do NOT add content not in either transcript. Do NOT summarize or condense. The merged version should be at least as long as the longer input. Preserve the speaker's voice and the recording's natural flow.
### Step 4: Trim
One subagent. Reads `merged.txt` and writes `trimmed.txt`.
Give the subagent these instructions — copy them verbatim:
> This is a merged transcript of a voice recording. The speaker is thinking out loud, so there's redundancy: restating the same idea, exploring then abandoning dead ends, self-correcting, going back and forth. Trim it.
>
> CUT: redundant restatements, dead-end explorations the speaker abandons, back-and-forth self-debate that doesn't add information, warming-up preamble before the speaker gets to the point, mind-changing chains (keep only the final conclusion unless the reasoning adds value).
>
> PRESERVE: all technical details, all design decisions (even tentative ones), all open questions, all implementation specifics, the speaker's reasoning where it adds value, the speaker's voice and word choices.
>
> CONSTRAINTS: Do NOT rewrite or rephrase — cut whole sentences and paragraphs, but don't reword what remains. Do NOT add transitions or bridging text. Do NOT add any content. You are an editor with scissors, not a pen. If in doubt whether something has value, keep it.
### Step 5: Write per-folder index
Write a minimal `<FOLDER>/index.html` listing every file in the folder as
`<a href>` links (input audio, `*_response.json`, `*_raw.txt`,
`*_clean.txt`, `merged.txt`, `trimmed.txt`).
### Step 6: Generate slug & rename folder
Now that everything is done, generate a descriptive slug from `trimmed.txt`
(fall back to `merged.txt`, then to whichever raw transcript exists) and
rename the folder.
Slug: 2–5 lowercase hyphenated words describing the contents. Use the LLM
gateway with the first 1000 words or so of the chosen source file:
```
curl -s http://169.254.169.254/gateway/llm/fireworks/inference/v1/chat/completions \
-H 'content-type: application/json' \
-d '{"model":"accounts/fireworks/models/gpt-oss-120b","messages":[
{"role":"system","content":"Return ONLY a 2-5 word lowercase hyphenated slug describing the content. No punctuation other than hyphens. No quotes. No explanation."},
{"role":"user","content":"<start of transcript>"}]}' \
| jq -r '.choices[0].message.content' | tr -cd 'a-z0-9-' | sed 's/^-*//;s/-*$//'
```
Note: the gateway is provider-segmented. `gpt-oss-120b` is a Fireworks model and must be called at `/gateway/llm/fireworks/inference/v1/chat/completions` with the full id `accounts/fireworks/models/gpt-oss-120b`. Anthropic models (e.g. `claude-haiku-4-5`) live at `/gateway/llm/anthropic/v1/messages` and use the Anthropic Messages API.
Rename the folder to `/home/exedev/transcriptions/<date>-<slug>/`. If the
target already exists, append `-2`, `-3`, etc.
**If slug generation fails** (gateway error, empty output, etc.), just
don't rename — leave the folder at its random-hex name and continue. Don't
block the email on this.
### Step 7: Publish & email
1. Regenerate the top-level index of all transcription folders:
```
/home/exedev/transcriptions/regen-index.sh
```
2. Public URL of this folder: `https://scribe.exe.xyz/<final-folder-name>/`
(use whatever the folder is named now — slug or random hex).
3. Email the user. Default email is [REDACTED], but if the body of the trimmed transcript or the accompanying initial text has instructions to send to "work" or "bold" or "exe" email, send to [REDACTED] instead. Use a subagent to look for that. Have the same subagent suggest an appropriate email subject line.
- **Subject:** `transcript: <subject-line>`
- **Body:** comments to the user (only if necessary), then folder URL, then the full
contents of `trimmed.txt` inline. Separate different bits by full blank lines.
```
FINAL=$(cat <FOLDER>/trimmed.txt)
URL="https://scribe.exe.xyz/<folder-name>/"
TO=$(curl -s https://reflection.int.exe.xyz/email | jq -r .email)
jq -n --arg to "$TO" --arg subj "transcript: <slug-or-randhex>" \
--arg body "$URL"$'\n\n'"$FINAL" \
'{to:$to, subject:$subj, body:$body}' \
| curl -sS -X POST http://169.254.169.254/gateway/email/send \
-H 'content-type: application/json' -d @-
```
### Output
When the pipeline finishes, report:
1. The path to `trimmed.txt` (the final output) and the public folder URL
`https://scribe.exe.xyz/<folder-name>/`.
2. Confirmation that the email was sent.
3. A note that intermediate files (`*_raw.txt`, `*_clean.txt`, `merged.txt`) are available in the same directory.
Do not read intermediate files into the primary context. The whole point of the subagent pipeline is context discipline.
——-
After a successful transcription, if nothing interesting/unusual happened, archive the conversation.
---
## Infrastructure notes
- Archive root: `/home/exedev/transcriptions/`
- Static server: busybox httpd on port 8000, systemd unit `scribe.service`.
Restart with `sudo systemctl restart scribe`.
- Top-level index regenerated by `/home/exedev/transcriptions/regen-index.sh`
(sorts folders by `YYYY-MM-DD` prefix, most recent first).
- LLM gateway: https://exe.dev/docs/shelley/llm-gateway.md
- Send email: https://exe.dev/docs/send-email.md
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment