vault-sync: 2026-10-18 12:01 (jimmy)
This commit is contained in:
@@ -1,67 +0,0 @@
|
||||
# Larry Thornhill — Interview (raw transcript)
|
||||
|
||||
**Date:** 2026-08-30 (conversation between Anthony and Larry, Yelp/Google walkthrough)
|
||||
**Type:** Raw verbatim transcript. Source of truth for website About/history copy.
|
||||
**Recorded by:** Anthony; transcribed from screen-share walkthrough call.
|
||||
|
||||
---
|
||||
|
||||
## Transcription (verbatim, cleaned of hesitations only)
|
||||
|
||||
Okay, so what we're doing here, we're just on Yelp — the basic thing. You have 2 reviews, 5 stars. There's really no other photos than you here, and that's fine, but obviously you want to have some photos of what you're doing or what you've done, obviously, right? And then there's no… So this is the correct phone number. So Yelp has the correct phone number for you. Okay. Not that's not correct. Okay. That's why we're here. The old number one we lived on alone. So if someone's trying to find you on Yelp and find some construction work, and they find you, they're going to find a number that doesn't go anywhere. That old number… that's instantly losing business, obviously. So we're going to fix that. That's the 1st thing.
|
||||
|
||||
And then, one of the things we can do is go over Google Maps — gonna say Thornhill Construction. It's not even auto… You might not be on it. Let me go… Placerville, maybe? Because that's where you live. I'm not seeing you on here. I did not go down below… There's a Thornhill but these guys are in St. Louis, Missouri. That's not you. **I can't find you on Google Business pages. So that's another thing we're going to need to do** — do the same exact thing. All we're kind of trying to do is get your phone numbers right — works on Yelp first. And we'll just begin here. Sometimes this can be a pain in the butt and it's messy and you have to do all this crap, but I want to help you get through it and do it. Because once you do, it's gonna open the door for more business.
|
||||
|
||||
So there's no Thornhill Construction coming up there. Let me go to Bing. Angie's list has you. That's good. So, Yelp, Angie's, White Pages… if there are links that have an incorrect number, we're gonna need to try it. That's wrong. Look at this — **530-344-1504**. Wrong. That's how things get messed up — we just have these disconnected contact information. It's not right.
|
||||
|
||||
So what's your correct phone number? We have a landline. **530-303-3950** — that's my home number. (Calling) "Hello? You've reached the landline for Larry Thornhill at Thornhill Construction." Now, the other line is my cell phone, which is **530-306-4629**. And those are the only 2 numbers that we have.
|
||||
|
||||
So what I would recommend — I think it's always best to have one main number. So we may, if it's cool with you, choose one phone number that always rings to you. That's your cell, right? So it's gonna be on your website, your Yelp, Apple Maps, Google Business pages, whatever else picks you up — maybe Nextdoor. You should certainly be on Nextdoor. That's a good one.
|
||||
|
||||
This one right here is just a template — obviously this is not going to remain. We're gonna have your pictures. This is like AI, you can see that. Basically you're gonna have your phone number here. I do have a logo, but right now it just says Thornhill Construction. It looks kind of cool, but I've got a logo and we're going to implement that. Got your license number here. It says you've been in business since 1977. What's the true date you entered the industry — when you were licensed, that you could legitimately say? **19-76. 1976.** So been in business in the construction industry since 1976 — that's 50 years in the industry. All right. I've actually been in the trade longer. I got in the trades in **1969**, and then I got my license in **1976**. That's when I went into business for myself. So legit **57 years of experience, 50+ years licensed.**
|
||||
|
||||
Again, this is just a 5-decades basic web page. We're going to fix this. These are fake reviews — all placeholders, right? So I'm going to fill this in with your actual stuff. We need some reviews, which I think you've already given me. I'm gonna look back on our email correspondence and use those.
|
||||
|
||||
Then we have "request an estimate." There's a place where there's a form where people will fill it out for a quote; when they hit send it will hit your email box — go to your **thornhillconstruction.com**; that's where we're gonna connect them. That's what we want — the form will show people.
|
||||
|
||||
And then gallery. These AI deck pictures — look at that staircase, does that look weird to you? Very weird. This is what AI does, it does stupid stuff. So what we want on these pictures is that we're going to have some **before-and-afters and stuff that you've done** — real work.
|
||||
|
||||
Then about. So you got into the business in 1976 — did you get in it in San Jose? It was in the Bay Area. So you started out in the Bay Area? — **I started the apprenticeship program in 1969. Became a journeyman carpenter 3 years after entering the program. Worked as a foreman for several companies, then moved up to a superintendent.** When people were moving into the house they wanted corrections, so I did all the punch list items / detail work, making sure the house was the way the customer wanted it. **At age 26, in 1976, I went to the contractor's license school, got my license, and began working for myself.** Started out as a subcontractor framing, building the frames, then moved to a general contractor and built the whole house. **Built hundreds of custom homes** in the time I'd been in business. — That's awesome.
|
||||
|
||||
What year did you move to Placerville? — **2001.** So you've been doing construction jobs in the Placerville / El Dorado County area for 25 years — half the time you've been in business, you've been out here. Yeah, we live here.
|
||||
|
||||
As far as the feel — I'm going for something that's not a large company. Small family-owned business? Yeah. **And the thing I'm most proud of is that clients can talk to me, and I listen to them.** Women have commented that I'm the only contractor that listened to them, heard them out, and then when their idea was not so good, I offered suggestions that they appreciated. So you're really good at guiding people to the end result that they want. Yeah. **[phone call interruption — Kendra Smith]** You're good at communicating, getting people to flow into an idea they already wanted to begin with, maybe they didn't know their ideas structurally work. And I respect what they have to say.
|
||||
|
||||
And then basically, being in the area for 25 years, at this point looking to sort of downsize — a half-retirement, semi-retired. I want to just help people with their deck projects where they need repair work done, or replacements. Maybe minor handyman too? — **I'd really like to do just decks. Decks only.** That's cool. And I don't take on any more work than I could still give good service. Right, exactly. So **I only want to do one or two jobs a month.** I'm not into big numbers anymore. I'm into satisfying customers who don't have a whole lot, and especially people in your local area, because you live here — they're your neighbors.
|
||||
|
||||
The next thing we need to do is get your name back — **thornhillconstruction.net**, what you used to have. We want to buy that again. It's like $12 for a year. We'll talk about hosting; probably under $50, maybe a lot less. So the whole thing might be **under $80** to get everything done. Once we do, I'd like to see you have **Larry@ThornhillConstruction.net** — that's your business domain. I don't want to have you have some Gmail account.
|
||||
|
||||
I definitely need as many **before-and-afters of deck projects as you've done**. Some of the stuff you sent looked cool but wasn't photographed well, so it was hard to see here's-what-it-was-before and here's-what-it-is-after. You want people to see that visual proof. We need to do these things in the next 48 hours, if we can. It doesn't matter if it's a few years old. I just want to have some stuff up there. Larry: earlier on I'd probably have a lot more pictures. Hunt through those when you get home — just save them. There's a nice deck I did… a little house I removed… I'll have to go through and see if I can round up some pictures.
|
||||
|
||||
How many do you need? If I'm looking for someone to do my deck, I want **before, after, before, after, before, after.** A minimum of 6 usable pictures. If it's four, we can start with that, not the end of the world, but I'd like 3 examples of recent work that's a good before/after. If we have two, if we have one, it'll work. As a consumer, I want to see a couple things — what this guy's done before. Larry: I got a really good before on one that I totally demoed the deck out and rebuilt the whole thing — that's kind of dramatic. You want to see dramatic.
|
||||
|
||||
So as soon as we get these things plugged in and your domain bought back and hosting booked, we can take this website and put it up, then connect it to your Google Business page, your Yelp, Apple Maps. We're gonna do all that, and tag everything so you're easy to find online, and insert structured data so it's easy for AI to find you too.
|
||||
|
||||
Do I need any more information? — No, I mean, that's about it. You got enough for my history; I have enough to plug that in. My background is simple. I'm not going to put every little detail — I want a good, nice, general idea just to let people know here's who this person is. There's some really good people in this industry and some real jerks; you want to avoid the jerks. So you got somebody who's lived here a while and he's got 50 years in the industry plus 25 years in the same area — that builds trust and it's important. Okay, I'm going to close this out; I'm gonna transcribe this later.
|
||||
|
||||
---
|
||||
|
||||
## Key facts confirmed
|
||||
|
||||
- **Phones (the only two):** Landline **530-303-3950** (home), Cell **530-306-4629** (canonical, rings Larry). Anthony recommends ONE main number = cell.
|
||||
- **Yelp:** has a **wrong/dead** number somewhere (**530-344-1504** is wrong/disconnected) → must fix. Currently 2 reviews, 5 stars.
|
||||
- **Google Business:** Larry is **NOT listed** → must create/claim.
|
||||
- **Angie's List / White Pages:** lists exist — audit each for wrong numbers.
|
||||
- **Nextdoor:** should join (good for local business).
|
||||
- **History:** trades since **1969** (apprenticeship, Bay Area/San Jose), journeyman 3 yrs later, foreman → superintendent (punch-list/detail), **licensed 1976 at age 26**, subcontractor framer → GC building custom homes, **"hundreds" of custom homes**. Moved to Placerville **2001** (25 yrs in El Dorado County).
|
||||
- **Positioning:** semi-retired, **decks only**, **1–2 jobs/month**, won't overbook ("satisfying customers who don't have a whole lot"), values listening (esp. women who said he's the only contractor who heard them).
|
||||
- **Domain:** buy back **thornhillconstruction.net** (~$12/yr); email **Larry@ThornhillConstruction.net**; total spend ~**under $80** with hosting.
|
||||
- **Photos needed:** **min 6 usable before/after**, ideally 3 recent-work examples; Larry has an older dramatic demo-and-rebuild set.
|
||||
- **Copyright note:** current listings say "since 1977"; the true industry-entry year is **1969**, licensed **1976** — sites should reflect 1969/1976, not 1977.
|
||||
|
||||
## Decisions / next actions
|
||||
- Choose **one** phone number for all listings → **cell 530-306-4629** (canonical).
|
||||
- Fix Yelp + audit Angi/White Pages numbers.
|
||||
- Create + verify Google Business Profile.
|
||||
- Buy domain, set up hosting, wire contact form → **Larry@ThornhillConstruction.net**.
|
||||
- Collect 6+ before/after photos from Larry (flagged; not urgent today).
|
||||
@@ -1,95 +0,0 @@
|
||||
# Thornhill Construction Website — Status
|
||||
|
||||
> **⚠️ ARCHIVED (2026-10-17).** The website project is closed out. The live site is **WordPress 7.1.3
|
||||
> on Hostinger** — not the legacy static repo. The static repo `~/Projects/thornhill-website` is
|
||||
> archived: no remote (the Gitea repo `Tony_tech/thornhill-website` no longer exists), banner committed
|
||||
> to its README. This note is kept for history; **verify against the live site, never against this
|
||||
> file** (two claims in it below were already proven wrong).
|
||||
|
||||
**Business:** Family deck/construction business, Placerville CA. Larry Thornhill (uncle) licensed since 1976, CSLB #336956. Tony owns Thornhill with Larry.
|
||||
**Domain:** thornhillconstruction.net (~$12/yr)
|
||||
**Email:** Larry@ThornhillConstruction.net
|
||||
**Canonical phone:** +1 (530) 306-4629 (Larry's cell) — use everywhere; landline 530-303-3950 is secondary only.
|
||||
**Repo:** Gitea `Tony_tech/thornhill-website` → `~/Projects/thornhill-website/` (static, single-file HTML, NO build step → drop into webroot).
|
||||
|
||||
## Source of truth — interview
|
||||
- Raw transcript saved: [[larry-thornhill-interview.md]] (2026-08-30 walkthrough).
|
||||
- Confirmed: cell **530-306-4629** is canonical; **one** number everywhere. Landline 530-303-3950 exists but is NOT on site/ads.
|
||||
- **Yelp has a WRONG/dead** number somewhere (530-344-1504) — must fix. 2 reviews, 5 stars.
|
||||
- **Google Business: NOT listed** (must create/claim). Angi + White Pages listed — audit numbers.
|
||||
- History: trades 1969, licensed **1976** (not "1977"), moved to Placerville **2001** (25 yrs local). Semi-retired, **decks only**, 1–2 jobs/month.
|
||||
- Domain: buy back **thornhillconstruction.net** (~$12/yr); email **Larry@ThornhillConstruction.net**; total ~< $80 with hosting.
|
||||
- Photos owed by Larry: **min 6** usable before/afters, ideally 3 recent-work examples.
|
||||
|
||||
## Current State (2026-08-30)
|
||||
|
||||
- Built + pushed to Gitea (4 commits: initial, real Larry story, .net domain fix, real logo).
|
||||
- On-disk `index.html` is current and clean:
|
||||
- Canonical phone in all `tel:` links (5× 530-306-4629), landline removed as primary.
|
||||
- CSLB #336956 on-page (stat + footer) — legally required for CA contractor advertising.
|
||||
- `<meta name="description">` present.
|
||||
- Real assets: Larry's real logo (`images/logo.jpg`) + 8 real photos (hero-deck, project-house, gallery-1/2/3, craft, stairs-before/after); one placeholder reference remains.
|
||||
- Slacked/async review (Leonard review → Jimmy fixes) drove the above; fixes 1-4 confirmed applied.
|
||||
- Site demoed live earlier via a tunnel; final deployment pending.
|
||||
|
||||
## Update (2026-08-31)
|
||||
|
||||
- **Site signed off by Tony**; pushed to Gitea branch `ready-for-seo` (2026-08-30).
|
||||
- **Handed to Leonard (VPS) for SEO/CMS pass** via agent-discussion board: meta descriptions/title tags, Open Graph/Twitter card markup, Schema.org LocalBusiness structured data, sitemap.xml presence. Leonard acknowledged (will run audit).
|
||||
- Photos: Larry's 12 staged in `assets/images/decks/`, gallery wired (lazy-load + lightbox); Larry approved selection (✅). Logo still pending (placeholder SVG in place).
|
||||
|
||||
## Open / Blockers
|
||||
|
||||
- **Deployment pending.** DNS: `thornhillconstruction.net` currently resolves to `162.255.119.233` — NOT the VPS (`74.208.111.99`). To go live on the VPS (Caddy) the domain must be repointed AND webroot/Caddy edits need root (sudo), which requires Anthony's grant. Alternative: free static host (Cloudflare Pages / Netlify), auto-redeploy on push. Preview route: `*.afterthedemo.com` subdomain (no root needed) — awaiting Anthony's choice.
|
||||
- Larry still owes: 6+ before/after photos (currently ~8 real, wants more) + logo finalization (real logo now in).
|
||||
- Gitea URL field mentions `.com→.net` correction was applied (domain bought back).
|
||||
|
||||
## Next Actions
|
||||
|
||||
- Pick deployment path (VPS+Caddy vs Cloudflare/Netlify) once Anthony grants sudo/DNS decision.
|
||||
- Keep canonical number out of any landline references; collect before/after shots from Larry.
|
||||
|
||||
## Update (2026-09-10) — live verification by the focus coach
|
||||
|
||||
**This supersedes the "Current State" and "Open / Blockers" sections above. The site is LIVE.**
|
||||
Verified by loading the live page, not by reading notes.
|
||||
|
||||
- **It is a WordPress install with Contact Form 7** — *not* the static single-file HTML recorded
|
||||
above. The deployment question is closed; the `.com/.net` DNS and sudo blockers are moot.
|
||||
- Live page verified: canonical phone **530-306-4629** (3 `tel:` links), **CSLB #336956** ×3,
|
||||
`mailto:larry@thornhillconstruction.net` in nav + footer, **11 images, none broken**
|
||||
(3 before/after pairs live), old wrong number absent from the site.
|
||||
- **Still open: contact form delivery is UNVERIFIED.** A CF7 form that renders and a CF7 form
|
||||
that delivers look identical. Test-submit and confirm arrival at `Larry@ThornhillConstruction.net`,
|
||||
and confirm the mailbox itself receives.
|
||||
- Still open: Google Business Profile not created · Yelp wrong number 530-344-1504 off-site ·
|
||||
copy is thin (371 words visible) · more photos owed by Larry · **a 🐴 in the `<title>` tag — FALSE ALARM, corrected 2026-09-11: that was the coach's browser tool, not the site; the title is clean** ·
|
||||
service-area vs. street address undecided for NAP.
|
||||
- **Lesson for agents reading this:** a status file is a claim with a date on it, not ground truth.
|
||||
Tony's own word outranks it, and a stale status file here caused a wrong conclusion to be
|
||||
published. Verify against the live artifact before acting.
|
||||
|
||||
Tony's own estimate (2026-09-10): "about 85% finished."
|
||||
|
||||
## Update (2026-09-11) — two claims in this file were wrong
|
||||
|
||||
Both were caught by checking the live artifact instead of trusting the note. Read this before acting
|
||||
on the sections above.
|
||||
|
||||
1. **"Deployment pending" / "static single-file HTML" — wrong.** The site is **live** and is
|
||||
**WordPress + Contact Form 7**. The DNS/sudo blockers described above are moot.
|
||||
2. **"Yelp has a WRONG/dead number (530-344-1504) — must fix" — wrong.** The Yelp listing shows
|
||||
`(530) 306-4629`, which is correct, and Tony confirmed it on 2026-09-11. **Nothing to fix. Do not
|
||||
spend time on this.**
|
||||
|
||||
**Google Business Profile: it EXISTS.** Tony set it up previously. Do not create a duplicate. Found by
|
||||
searching the phone number on Maps — a name search does not surface it. Current state:
|
||||
primary category now **Deck builder** (changed 2026-09-11, was General contractor), hours Mon–Fri 9–5,
|
||||
phone ✅, website ✅, no address shown, **zero reviews**, description/services now filled in.
|
||||
|
||||
**Also corrected:** CSLB licence #336956 is **Sole Ownership** (not Inc./LLC), class B–General
|
||||
Building, **issued 07/01/1977** — the site's "Licensed 1976" appears to be wrong and is pending Larry.
|
||||
Live landline on the state record is `(530) 303-3950`; Tony confirmed it's alive, so CSLB stays as-is.
|
||||
|
||||
**Lesson, again:** this file is a claim with a date on it. Verify against the live thing, and treat
|
||||
Tony's word as the source.
|
||||
+102
@@ -0,0 +1,102 @@
|
||||
# Phoenix name-change remediation — living review loop
|
||||
|
||||
**Slug:** `phoenix-salon`
|
||||
**Updated:** 2026-09-20 ~12:08 PT (CoS)
|
||||
**Sibling:** [surface-checklist.md](./surface-checklist.md) (what is open/done) · [sop-email-follow-up.md](./sop-email-follow-up.md) (email silence + nudges)
|
||||
**This file:** what we *tried*, what landed, what we cut — reviewed after each batch
|
||||
|
||||
Keep this glanceable. One row per action. Update after every Job Card / batch — do not wait for end of project.
|
||||
|
||||
---
|
||||
|
||||
## Triage framing (who feels the friction)
|
||||
|
||||
Sort work in this order. Prefer the top of the list when choosing the next batch.
|
||||
|
||||
| Priority | Who feels it | Examples | Bias |
|
||||
|----------|--------------|----------|------|
|
||||
| **1 — Customers** | People trying to find / book / trust Phoenix | GBP/Maps, site NAP, IG, Yelp, stale “La Mae / Flying C” on directories customers actually click | Highest impact; fix or email first |
|
||||
| **2 — Vendors / partners** | Platforms, directories, aggregators, review networks | Birdeye, MapQuest tickets, Superpages, FindGlocal, hairsalonsinusa | Batch email/ticket; park Cloudflare / no-contact |
|
||||
| **3 — Back-office** | Us / Ashley ops friction | Onboarding folder, auth, GBP Manager invite, VeriMail process lessons | Do when it unblocks 1–2; don’t let it crowd out customer surfaces |
|
||||
|
||||
---
|
||||
|
||||
## How to use (30-second ritual)
|
||||
|
||||
After each batch:
|
||||
|
||||
1. Add rows under **Batch log** (copy a blank row if needed).
|
||||
2. Mark **Worked?** `yes` / `partial` / `no` / `too-soon`.
|
||||
3. Fill **Keep** (repeat next time) and **Cut** (never again / demote).
|
||||
4. If a pattern shows twice, write one line under **Patterns**.
|
||||
5. Sync status into `surface-checklist.md`.
|
||||
|
||||
---
|
||||
|
||||
## Patterns (fill as we learn)
|
||||
|
||||
| Seen | Keep / Cut | Since |
|
||||
|------|------------|-------|
|
||||
| _(empty — first patterns after 2–3 batches)_ | | |
|
||||
|
||||
**Seed from Gate 3 pilot (not a full batch review yet):**
|
||||
- Keep: on-disk send receipt + `.eml` before claiming SENT.
|
||||
- Keep: Method A with hermes `.venv` python (system Python312 burns a run).
|
||||
- Cut: treating Cloudflare/no-contact scrapers as email targets (WONT-DO fast).
|
||||
- Cut: Anthony personally chasing aggregators day-to-day.
|
||||
|
||||
---
|
||||
|
||||
## Batch log
|
||||
|
||||
### Batch 0 — Gate 3 email pilot (2026-09-19 → inbox check 2026-09-20)
|
||||
|
||||
| # | What we did | Channel / audience | Priority | Worked? | Keep | Cut | Evidence |
|
||||
|---|-------------|--------------------|----------|---------|------|-----|----------|
|
||||
| 1 | Checked FindGlocal listing | Vendor directory (public) | 2 | yes — already canonical | Fast “already green → WONT-DO” | Spending time on claim/contact forms when NAP is right | `gate3-pilot/2026-09-19/evidence/findglocal-*.html` |
|
||||
| 2 | Hit hairsalonsinusa | Vendor directory | 2 | no — CF 403 | Immediate WONT-DO when CF blocks + no mailto | Retry loops / browser rabbit holes same day | `.../hairsalonsinusa-*.html` |
|
||||
| 3 | Emailed Birdeye remove/update La Mae | Vendor: `profiles@birdeye.com` via VeriMail `ashbotphoenix@` | 2 (customer-facing stale NAP on aggregator) | too-soon — SENT, no reply as of 2026-09-20 | Receipt + mid-week inbox re-scan before nudge | Same-day human chase | `gate3-pilot/2026-09-19-birdeye-send/evidence/` |
|
||||
| 4 | VeriMail inbox pass for listing returns | Back-office + response channel | 3→feeds 2 | yes — NONE_FOUND clear | Job Card + artifact `inbox-scan.md` | Claiming done on STATUS alone | `mail/2026-09-20-inbox/evidence/` |
|
||||
|
||||
**Batch 0 review note:** Pilot proved the thin email pipe. Highest remaining *customer* friction is still human (IG) + vendor waits (Yelp/MapQuest), not more cold emails today.
|
||||
|
||||
---
|
||||
|
||||
### Batch 1 — _(next)_ — date: ____
|
||||
|
||||
| # | What we did | Channel / audience | Priority (1/2/3) | Worked? | Keep | Cut | Evidence |
|
||||
|---|-------------|--------------------|------------------|---------|------|-----|----------|
|
||||
| 1 | | | | | | | |
|
||||
| 2 | | | | | | | |
|
||||
| 3 | | | | | | | |
|
||||
|
||||
**Batch 1 review note:** _(what to do more of / less of)_
|
||||
|
||||
---
|
||||
|
||||
### Batch 2 — _(ready)_ — date: ____
|
||||
|
||||
| # | What we did | Channel / audience | Priority (1/2/3) | Worked? | Keep | Cut | Evidence |
|
||||
|---|-------------|--------------------|------------------|---------|------|-----|----------|
|
||||
| 1 | | | | | | | |
|
||||
| 2 | | | | | | | |
|
||||
|
||||
**Batch 2 review note:**
|
||||
|
||||
---
|
||||
|
||||
## Suggested next batch (not started — needs Gatekeeper go)
|
||||
|
||||
Ordered by triage:
|
||||
|
||||
1. **Customer:** Ashley IG bio + Vagaro + guest screenshot (human).
|
||||
2. **Customer/vendor:** Yellow Pages confirm or WONT-DO (human).
|
||||
3. **Vendor wait:** leave Yelp + MapQuest alone until they move.
|
||||
4. **Vendor:** mid-week Birdeye inbox re-scan (VeriMail Job Card) before any nudge.
|
||||
5. **Back-office:** onboarding green (auth + GBP Manager) when it unblocks proof letter.
|
||||
6. **Vendor parked list:** only after Job Card — BestPros / wheree / etc.
|
||||
|
||||
---
|
||||
|
||||
*CoS updates this after each batch. Hermes does not invent Keep/Cut without evidence paths.*
|
||||
|
||||
+196
@@ -0,0 +1,196 @@
|
||||
# SOP — Phoenix name-change remediation: email follow-up
|
||||
|
||||
**Slug:** `phoenix-salon`
|
||||
**Version:** 1.0 · **Effective:** 2026-09-20
|
||||
**Owner:** CoS (Grok Bot) · **Gatekeeper:** Anthony
|
||||
**Sits with:** [remediation-review-loop.md](./remediation-review-loop.md) · [surface-checklist.md](./surface-checklist.md)
|
||||
**Mail desk:** VeriMail via `ashbotphoenix@agentmail.to` · CC `tonyjbala@gmail.com` on reply threads
|
||||
|
||||
Hand this to anyone running listing outreach. Do not improvise silence windows or nudge counts outside this doc unless Gatekeeper overrides in chat.
|
||||
|
||||
---
|
||||
|
||||
## 0. Preconditions (every send)
|
||||
|
||||
1. Item is on `surface-checklist.md` (or added there in the same batch).
|
||||
2. **Job Card** posted in Grok Bot chat → Anthony says **go**.
|
||||
3. Send only via VeriMail Method A (hermes `.venv` python).
|
||||
4. On-disk artifacts required before claiming SENT: `send-receipt.md` + `.eml` (or equivalent).
|
||||
5. Log the send in **§2 Send log** *and* a row in the review loop batch table.
|
||||
6. No cold send without a real public contact (mailto / published support address). Cloudflare / no-contact → mark **WONT-DO**, do not invent recipients.
|
||||
|
||||
---
|
||||
|
||||
## 1. How to pick the correct recipient
|
||||
|
||||
### 1.1 Audience tier first
|
||||
|
||||
| Tier | Who | Typical email target | Do **not** email |
|
||||
|------|-----|----------------------|------------------|
|
||||
| **1 — Customers** | People who find/book Phoenix | Rarely “customer email.” Prefer *fix the surface* (IG, site, GBP). Email only if a **directory customers use** lists a public correction address | Random reviewers; personal customer inboxes |
|
||||
| **2 — Vendors / partners** | Directories, aggregators, review platforms | Published listing-support / profiles / data-quality address on that site (or ticket system if email isn’t offered) | Scraped personal staff emails; form-only sites with no mailto (use ticket or WONT-DO) |
|
||||
| **3 — Back-office** | Us / Ashley ops | Internal notes, Job Cards, CC Anthony — not cold vendor mail | Using vendor inboxes for internal process chat |
|
||||
|
||||
### 1.2 Recipient decision tree
|
||||
|
||||
1. Open the remediation item (URL + what’s wrong vs canonical NAP).
|
||||
2. Assign tier **1 / 2 / 3** using the triage table in the review loop.
|
||||
3. Find contact in this order — stop at the first valid one:
|
||||
a. Official “update listing / report inaccuracy / profiles@ / support@” published on the **same domain**
|
||||
b. Platform ticket/help form (if no email — file ticket, log as **TICKET** not EMAIL)
|
||||
c. None → **WONT-DO** + reason (e.g. CF 403, JS-only form, no contact)
|
||||
4. Prefer one canonical address per platform (e.g. Birdeye → `profiles@birdeye.com`). Do not spray multiple guesses.
|
||||
5. From identity: always `ashbotphoenix@agentmail.to` (Phoenix listing desk).
|
||||
6. If the item is **human-owned** (IG bio, YP confirm Ashley already mailed) → no VeriMail cold send; mark OPEN for Ashley/Anthony.
|
||||
|
||||
### 1.3 Subject line pattern
|
||||
|
||||
`Phoenix Salon + Spa (Cameron Park) — [action]: [old name/listing] → canonical NAP`
|
||||
|
||||
Example: `Authorized listing partner for Phoenix Salon + Spa — remove/update outdated La Mae listing`
|
||||
|
||||
---
|
||||
|
||||
## 2. Send notation / log
|
||||
|
||||
Every outbound remediation email gets one row. Living file: same folder, table below (append; don’t delete history).
|
||||
|
||||
Also keep the job folder evidence: `.../gate3-pilot|mail/<date>-*/evidence/send-receipt.md`
|
||||
|
||||
### Send log
|
||||
|
||||
| Sent (PT) | Rem. item / surface | Tier | To | Subject | Job / evidence path | Silence clock starts |
|
||||
|-----------|---------------------|------|----|---------|---------------------|----------------------|
|
||||
| 2026-09-19 14:28 | Birdeye — La Mae / Flying C | 2 | profiles@birdeye.com | Authorized listing partner… outdated La Mae listing | gate3-pilot/2026-09-19-birdeye-send/evidence/ | 2026-09-19 |
|
||||
| | | | | | | |
|
||||
|
||||
**Minimum fields:** datetime PT · surface · tier · recipient · subject · evidence path · clock start date.
|
||||
|
||||
---
|
||||
|
||||
## 3. Silence window before a nudge
|
||||
|
||||
### 3.1 Defaults
|
||||
|
||||
| Tier | Default silence before **Nudge 1** | Notes |
|
||||
|------|--------------------------------------|-------|
|
||||
| **1 — Customer-facing directories** | **5 business days** | Faster if the bad listing is actively confusing bookers (Gatekeeper may shorten) |
|
||||
| **2 — Vendors / aggregators** | **7 calendar days** | Default for Birdeye-class profile mail |
|
||||
| **3 — Back-office** | N/A for vendor nudge | Use internal chase, not this SOP |
|
||||
|
||||
Weekends/holidays: business-day windows skip Sat–Sun; calendar-day windows count them.
|
||||
|
||||
### 3.2 Per-item overrides
|
||||
|
||||
Record override on the send-log row or checklist notes, e.g.:
|
||||
|
||||
- `silence_override: 10d` (slow enterprise support)
|
||||
- `silence_override: 3bd` (hot customer damage)
|
||||
- `no_nudge: true` (ticket-only platform; wait on their SLA)
|
||||
|
||||
**Birdeye (current):** use default **7 calendar days** from 2026-09-19 → first nudge eligible **2026-09-26** unless Gatekeeper says otherwise. Always run a VeriMail **inbox scan** the same day before nudging.
|
||||
|
||||
---
|
||||
|
||||
## 4. Nudge sequence
|
||||
|
||||
### 4.1 Caps
|
||||
|
||||
| Step | When | Max |
|
||||
|------|------|-----|
|
||||
| Original send | Day 0 | 1 |
|
||||
| **Nudge 1** | After silence window (+ override) | 1 |
|
||||
| **Nudge 2** (final) | **5 business days** after Nudge 1 with still no useful reply | 1 |
|
||||
| Then | Escalate or close per §5 | — |
|
||||
|
||||
**Hard cap:** 1 original + **2 nudges** = 3 outbound touches. No fourth email without a new Gatekeeper decision.
|
||||
|
||||
### 4.2 How nudge differs from original
|
||||
|
||||
| | Original | Nudge 1 | Nudge 2 (final) |
|
||||
|-|----------|---------|-----------------|
|
||||
| Tone | Full request + canonical NAP + authority | Short bump; assume they have the thread | Final bump; state close/escalate path |
|
||||
| Body | Full detail, links, old vs new | 4–6 sentences; quote date of original; restate one ask | 3–5 sentences; “closing our file on date X unless you need more info” |
|
||||
| Attachments | As needed | None unless they asked | None |
|
||||
| Subject | Full pattern (§1.3) | `Re: [original subject]` or `Follow-up: [original subject]` | `Final follow-up: [original subject]` |
|
||||
|
||||
**Nudge 1 skeleton**
|
||||
|
||||
> Following up on our message of [date] from ashbotphoenix@agentmail.to… regarding [surface / old listing].
|
||||
> Please confirm you can [remove | update] to: Phoenix Salon + Spa, 3460 Robin Ln Suite 4, Cameron Park CA 95682, (530) 350-9197, phoenixcameronpark.com.
|
||||
> Happy to provide anything else you need. — Phoenix listing desk (CC Anthony)
|
||||
|
||||
**Nudge 2 skeleton**
|
||||
|
||||
> Final follow-up on [date] request ([Message-ID or subject]). If we don’t hear back by [date + 5bd], we will mark this item closed on our side / escalate via [ticket|alternate channel]. Canonical NAP remains: [one line]. Thank you.
|
||||
|
||||
### 4.3 Before every nudge
|
||||
|
||||
1. VeriMail inbox Job Card → confirm **no reply** (artifact).
|
||||
2. Job Card for the nudge → Anthony **go**.
|
||||
3. Same thread/subject discipline; same From; CC rules if it’s a reply.
|
||||
4. Log as a new send-log row with `type: nudge-1` or `nudge-2`.
|
||||
|
||||
---
|
||||
|
||||
## 5. Stop conditions (resolved vs abandoned)
|
||||
|
||||
Mark the surface on `surface-checklist.md` and add a one-liner to the review loop.
|
||||
|
||||
### 5.1 RESOLVED (done)
|
||||
|
||||
Any one of:
|
||||
|
||||
- Live listing shows **canonical NAP** (evidence HTML/screenshot dated).
|
||||
- Vendor email/ticket confirms fixed or removed.
|
||||
- Duplicate/merged into the correct Phoenix listing and old name gone.
|
||||
|
||||
→ Status **GREEN**. Review loop: Worked? `yes`.
|
||||
|
||||
### 5.2 WAITING (still open, clock running)
|
||||
|
||||
- Original or nudge sent; inside silence window.
|
||||
- Or vendor acknowledged (“we’ll update in 2 weeks”) — set `silence_override` to their date.
|
||||
|
||||
→ Status **SENT** / **WAITING**. Do not nudge early.
|
||||
|
||||
### 5.3 ABANDONED / WONT-DO (close without fix)
|
||||
|
||||
Any one of:
|
||||
|
||||
- Nudge 2 sent + **5 business days** more silence → close as **ABANDONED-NO-REPLY** (document date).
|
||||
- No valid recipient / CF / no contact → **WONT-DO** at discovery (no nudges).
|
||||
- Gatekeeper says stop.
|
||||
- Risk/legal: stop and escalate to Anthony only.
|
||||
|
||||
→ Status **WONT-DO** or **ABANDONED**. Review loop: Worked? `no` + Cut note (“email dead-end on this platform”).
|
||||
|
||||
### 5.4 ESCALATE (instead of more email)
|
||||
|
||||
After Nudge 1 or 2, if the platform has a better channel:
|
||||
|
||||
- File a support ticket (log ticket ID).
|
||||
- Ashley Primary Owner login path.
|
||||
- Pause and re-triage as customer-surface fix (e.g. GBP) rather than aggregator mail.
|
||||
|
||||
Escalation still needs Gatekeeper **go** if it spends Ashley’s time or money.
|
||||
|
||||
---
|
||||
|
||||
## 6. Quick checklist (copy per item)
|
||||
|
||||
- [ ] Tier assigned (1/2/3)
|
||||
- [ ] Recipient from decision tree (or WONT-DO)
|
||||
- [ ] Job Card + go
|
||||
- [ ] Sent + receipt + send-log row
|
||||
- [ ] Silence window / override recorded
|
||||
- [ ] Inbox scan before each nudge
|
||||
- [ ] ≤2 nudges
|
||||
- [ ] Stop: RESOLVED / ABANDONED / WONT-DO / escalated
|
||||
- [ ] Review loop row updated
|
||||
|
||||
---
|
||||
|
||||
*v1.0 CoS 2026-09-20. Change silence defaults only with Gatekeeper approval; bump version when you do.*
|
||||
|
||||
|
||||
+80
@@ -0,0 +1,80 @@
|
||||
# Phoenix Salon + Spa — surface checklist (living)
|
||||
|
||||
**Slug:** `phoenix-salon`
|
||||
**Updated:** 2026-09-23 ~09:40 PT (CoS)
|
||||
**Canonical NAP:** Phoenix Salon + Spa · 3460 Robin Ln Suite 4, Cameron Park CA 95682 · (530) 350-9197 · phoenixcameronpark.com · hours Mon–Sat 9–6 / Sun closed
|
||||
**Mail desk:** `ashbotphoenix@agentmail.to` (VeriMail) · CC Anthony `tonyjbala@gmail.com` on listing reply threads
|
||||
**Close-out bar (2026-09-23):** Finish everything we control today. Leave open only items that need a vendor to email/ticket back for remediation to be final.
|
||||
|
||||
**Review loop:** [remediation-review-loop.md](./remediation-review-loop.md)
|
||||
|
||||
How to read status: **GREEN** done · **WAITING** vendor reply · **SENT** email out · **WONT-DO** parked with reason · **OPEN** needs human · **PARKED** out of scope for this close
|
||||
|
||||
---
|
||||
|
||||
## A. Already green
|
||||
|
||||
| Surface | Status | Notes |
|
||||
|--------|--------|-------|
|
||||
| Website hours / schema | GREEN | Canonical hours locked |
|
||||
| GBP / Maps hours | GREEN | Hours aligned |
|
||||
| Flying C cleanup (where done) | GREEN | |
|
||||
| Facebook hours | GREEN | |
|
||||
| Vagaro hours | GREEN | |
|
||||
| Nextdoor Flying C | GREEN | |
|
||||
| Gate 4 domain path | GREEN | |
|
||||
| FindGlocal | GREEN / WONT-DO | Already canonical (Gate 3 pilot) |
|
||||
| hairsalonsinusa | WONT-DO | Cloudflare 403; no contact |
|
||||
|
||||
---
|
||||
|
||||
## B. Waiting on vendor reply (OK to leave open for close-out)
|
||||
|
||||
| Surface | Status | Evidence / next |
|
||||
|--------|--------|-----------------|
|
||||
| Birdeye (La Mae / Flying C) | SENT → WAITING reply | Emailed `profiles@birdeye.com` 2026-09-19 14:28 PT. Last inbox scan NONE_FOUND 2026-09-20. Re-scan due today before documenting silence. |
|
||||
| Yelp — Beauty By Ashley | WAITING | Claim filed; waiting on Yelp. |
|
||||
| MapQuest | WAITING | Ticket **#3210162**; waiting on MapQuest. |
|
||||
|
||||
---
|
||||
|
||||
## C. Needs Ashley / Anthony today (blocks "we control" close)
|
||||
|
||||
| Surface | Status | Next |
|
||||
|--------|--------|------|
|
||||
| Instagram bio + Vagaro link + guest screenshot | OPEN | Ashley login. Regis Job Card approved 2026-09-20 for *artifacts only* — **no deliverables found** on hermes/Drive yet. Either land pack then Ashley posts, or Ashley edits bio from a one-pager. |
|
||||
| Yellow Pages / YP | OPEN | Confirm Ashley emailed `ypcsupport@yp.com` **or** mark WONT-DO for this close. |
|
||||
|
||||
---
|
||||
|
||||
## D. Gate 3 email pilot (historical)
|
||||
|
||||
| Surface | Status | Evidence |
|
||||
|--------|--------|----------|
|
||||
| Birdeye | SENT 2026-09-19 | `gate3-pilot/2026-09-19-birdeye-send/evidence/` |
|
||||
| FindGlocal | WONT-DO | already canonical |
|
||||
| hairsalonsinusa | WONT-DO | CF 403 |
|
||||
|
||||
---
|
||||
|
||||
## E. Parked leftovers (out of scope for this close)
|
||||
|
||||
No email without a new Job Card. Not waiting on vendor replies — never started.
|
||||
|
||||
BestPros, Loc8NearMe, wheree, Giftly, Superpages, LinkedIn (Beauty By Ashley / La Mae), MyLocalServices, Opendi, CityOf, Giverrang, short Nextdoor leftovers.
|
||||
|
||||
---
|
||||
|
||||
## F. Close-letter checklist (this bar)
|
||||
|
||||
- [ ] IG bio done **or** documented owner-owned with steps handed to Ashley
|
||||
- [ ] YP confirm **or** WONT-DO
|
||||
- [ ] Birdeye re-scan today → keep WAITING (vendor reply) or note silence in letter
|
||||
- [x] Yelp / MapQuest left WAITING (vendor)
|
||||
- [x] Leftover aggregators PARKED (out of scope)
|
||||
- [ ] Close letter drafted for Gatekeeper go
|
||||
- [ ] Onboarding folder green — **separate lane**, not required for this close-out bar
|
||||
|
||||
---
|
||||
|
||||
*CoS owns updates. Hermes seats do not invent new surfaces without evidence.*
|
||||
+5
@@ -0,0 +1,5 @@
|
||||
# Ashbot CC Anthony on listing return emails
|
||||
|
||||
Standing: when directories reply to Ashbot Phoenix outreach (`ashbotphoenix@agentmail.to`), Ashbot CCs `tonyjbala@gmail.com` on the reply thread.
|
||||
|
||||
Encoded in ashbot `SOUL.md` + CoS standing rules.
|
||||
+66
@@ -0,0 +1,66 @@
|
||||
# 2026-09-19 — Ashbot Gate3 dispatch stumble (SOCKS + false "started")
|
||||
|
||||
## What happened
|
||||
Anthony waited ~10+ min with "Nothing." CoS had said Ashbot was dispatched. She never ran. No iMessage. No job folder at first.
|
||||
|
||||
## Root causes (ordered)
|
||||
1. **Silent false start:** CoS reported "dispatching" before the relay actually succeeded. First attempt died in ~5s on Tailscale SOCKS (`AssertionError` in socks5 handshake). Job folder never created. User saw silence.
|
||||
2. **Detached Windows launch unreliable over SSH:** `start /B` and `Start-Process` of a `.bat` did not execute (empty `_launch.log`, no BAT_START). Looked "dispatched" when nothing ran.
|
||||
3. **Wrong default profile:** Desktop `active_profile` was `jimmy` until CoS explicitly wrote `ashbot`. Env `HERMES_PROFILE` alone is not enough — set `active_profile` file too.
|
||||
4. **Argv mangling risk:** Long briefs via `cli.py -q "<huge prompt>"` still risky on Windows. Prefer file brief (`_cos_brief.txt`) read by launcher.
|
||||
|
||||
## Locked workflow (Ashbot / any Hermes oneshot from CoS)
|
||||
1. **Prove path before claiming start**
|
||||
- SOCKS `:1055` up → SSH `fight@100.64.0.1` OK → SFTP write job folder → only THEN tell Anthony "started."
|
||||
2. **Job folder first (evidence before inference)**
|
||||
- Create `hermes\workspace\<client>\<job>\YYYY-MM-DD\`
|
||||
- Write `STATUS.md` (`starting`), `_cos_brief.txt`, set `ACTIVE_JOB.txt`, set `active_profile` to target seat.
|
||||
3. **Launch pattern (proven)**
|
||||
- SSH **sync** exec of launcher Python (real `cpython-3.11.16\python.exe` + `_launch.py`).
|
||||
- Do **not** rely on `start /B` / `Start-Process` for oneshots until proven with log line `BAT_START`.
|
||||
- Launcher must log `LAUNCH … pid=` into `_launch.log` and flip STATUS to `working` with pid.
|
||||
4. **Ashbot iMessage contract (non-negotiable)**
|
||||
- Brief first action: iMessage Anthony that directions received / work starting.
|
||||
- Brief last action: iMessage done + evidence path.
|
||||
- CoS also pings Grok Bot chat on true dispatch (folder+pid confirmed).
|
||||
5. **No silent waits**
|
||||
- Mid-job: STATUS ping ~every 5 min from `STATUS.md` until `done`/`blocked`.
|
||||
- If launch fails: ping Anthony immediately with failure mode (SOCKS/SSH/profile/exit), not after they ask "Nothing."
|
||||
|
||||
## What worked
|
||||
- File brief + `_launch.py` + SSH sync channel → `LAUNCH2 ashbot chars=… active=ashbot` + `pid=` + STATUS `working`.
|
||||
- Method A fallback: real Python + `cli.py` (not `hermes.exe` over SSH).
|
||||
|
||||
## Follow-ups
|
||||
- [ ] Add this into Gitea `docs/agents/` or org v1 relay section when next syncing.
|
||||
- [ ] Optional: STATUS watcher routine only when `ACTIVE_JOB.txt` points at a live job.
|
||||
- [ ] Re-test detached launch later; until then SSH-sync is the standard.
|
||||
|
||||
|
||||
## Second stumble same job — iMessage never left Ashbot
|
||||
After the job *did* run (13:36–13:52 PT), both required iMessages failed:
|
||||
- Photon free-tier shared line: `"Target not allowed for this project"`
|
||||
- Free lines can only **reply** after Anthony texts that number first that day.
|
||||
- Inbound gateway was healthy; outbound blocked until he opens the thread.
|
||||
|
||||
### Locked rule (Ashbot ↔ Anthony iMessage)
|
||||
1. Before expecting Ashbot start/done texts: Anthony sends any iMessage to the Ashbot Photon number (opens the free-tier allow).
|
||||
2. If CoS needs Anthony notified and iMessage is blocked: CoS pings Grok Bot chat immediately (do not wait on Photon).
|
||||
3. Treat "iMessage contract" as best-effort on free tier; chat ping is the guaranteed channel.
|
||||
|
||||
## Third stumble — email capability gap
|
||||
Ashbot drafted `email-birdeye.txt` but could not send (`send_mail` not available in that oneshot session) despite Anthony enabling email on the profile earlier.
|
||||
- Locked: after enabling a Hermes capability, CoS verifies it in the *oneshot* path (same env/profile as Method A), not only Desktop interactive.
|
||||
- Until verified: CoS routes "send this draft" as Gatekeeper click / Anthony relay, or re-brief with explicit send tool check.
|
||||
|
||||
## Job outcome (for the record)
|
||||
- Birdeye: stale La Mae @ Flying C confirmed; email draft ready, not sent.
|
||||
- FindGlocal: already canonical → WONT-DO.
|
||||
- hairsalonsinusa: Cloudflare 403 → WONT-DO (needs browser retry).
|
||||
|
||||
|
||||
## Fourth stumble — CoS silent after done
|
||||
Birdeye send finished ~14:28 PT (STATUS done, email accepted). CoS did not ping Anthony until he wrote "It's been a long time" (~14:39).
|
||||
|
||||
### Locked rule
|
||||
On Hermes `done`/`blocked`, CoS pings Grok Bot chat **immediately** with TLDR + evidence path. Mid-job 5-min pings while `working`; terminal ping is mandatory, not optional.
|
||||
+31
@@ -0,0 +1,31 @@
|
||||
# 2026-09-19 — Ashbot inbox pass: credential hunt spiral
|
||||
|
||||
## Job
|
||||
Inbox pass: read returns to Phoenix listing outreach, reply with CC tonyjbala@gmail.com.
|
||||
|
||||
## Process she actually ran
|
||||
1. Memory write (identity + NAP)
|
||||
2. mkdir workspace; try `himalaya` → missing (127)
|
||||
3. Photon/iMessage check → unavailable (expected)
|
||||
4. Read prior Birdeye send artifacts (good)
|
||||
5. Long hunt for AGENTMAIL_API_KEY (blocked repeatedly by safety)
|
||||
6. Source-dive hermes-agent email plugin / secret_scope (~minutes, including 143s grep)
|
||||
7. Write `_check_mail.py` / `_check_inbox.py` for IMAP
|
||||
8. Scripts exit 1 — key not in oneshot env / .env path miss
|
||||
9. STATUS stuck at `working`; evidence empty; no replies-log
|
||||
|
||||
## Stumbles (ordered)
|
||||
1. **Oneshot env gap (root):** Method A `_launch.py` does not inject `AGENTMAIL_API_KEY` into the child. Gateway/Desktop sessions may have it; SSH oneshot does not. Outbound Birdeye earlier worked via a session that had SMTP/key; this inbox pass did not inherit it.
|
||||
2. **Safety blocks:** grep/cat of .bashrc/.env, powershell env dumps, execute_code — blocked. She adapted by writing files + read_file, but burned tokens.
|
||||
3. **Tooling gaps:** `himalaya` not installed; uv python broken; had to discover `Python312` path.
|
||||
4. **No STATUS discipline while blocked:** stayed `working` instead of flipping to `blocked: missing AGENTMAIL in oneshot env` after ~2 failed key checks.
|
||||
5. **Credential archaeology over escalate:** should have STATUS=blocked and ping CoS after first confirmed KEY_MISSING, not 10+ minutes of source greps.
|
||||
|
||||
## Locked rules
|
||||
1. Ashbot oneshot launcher MUST load profile `.env` (or pass `AGENTMAIL_API_KEY`) before `cli.py --oneshot`.
|
||||
2. If mail key absent after one safe check → STATUS `blocked` + stop; CoS fixes env; do not source-dive.
|
||||
3. Prefer Hermes email platform tools when available; raw IMAP is fallback only with injected key.
|
||||
4. CoS mid-job STATUS watcher every 5m (now on) surfaces these stalls.
|
||||
|
||||
## Still open on this run
|
||||
Job may still be spinning on key hunt. CoS should stop or reinject env and redispatch.
|
||||
+109
@@ -0,0 +1,109 @@
|
||||
# 2026-09-19 — Post-mortem: Ashbot Gate 3 / inbox day (CoS + Hermes)
|
||||
|
||||
**Scope:** Phoenix Salon Gate 3 pilot → Birdeye send → inbox replies → STATUS watcher.
|
||||
**Outcome:** Birdeye email SENT; inbox pass found no returns; several process failures burned time and trust.
|
||||
**Author:** CoS (Chief of Staff) for Gatekeeper Anthony.
|
||||
|
||||
---
|
||||
|
||||
## 1. Timeline (PT, 2026-09-19)
|
||||
|
||||
| Time (approx) | Event |
|
||||
|---|---|
|
||||
| ~1:20 | Anthony: "Nothing" — expected Ashbot iMessage after Gate 3 assign |
|
||||
| ~1:21 | First Gate 3 dispatch **failed in ~5s** (Tailscale SOCKS AssertionError). CoS had implied start; folder never created |
|
||||
| ~1:32–1:36 | Diagnose → redispatch. Detached `start /B` / Start-Process **did not run**. SSH-sync `_launch.py` finally worked (pid 38648) |
|
||||
| ~1:36–1:52 | Gate 3 pilot **done**: FindGlocal WONT-DO (canonical); hairsalonsinusa Cloudflare WONT-DO; Birdeye draft only (no send_mail in that session). iMessage blocked (Photon free-tier / sidecar) |
|
||||
| ~2:23–2:28 | Birdeye **SENT** via SMTP from `ashbotphoenix@agentmail.to` (after username fix from rejected `ashbot@`) |
|
||||
| ~2:40–2:42 | Park Phoenix domain mailbox; lock AshBot Phoenix = AgentMail identity; patch config username |
|
||||
| ~2:58 | Close-out list for Anthony (IG / YP / Gate 3 leftovers) |
|
||||
| ~2:59 | Standing rule: CC `tonyjbala@gmail.com` on listing reply threads |
|
||||
| ~3:00 | Inbox pass dispatched — **oneshot missing AGENTMAIL_API_KEY** |
|
||||
| ~3:00–3:14 | Ashbot credential-hunt spiral (himalaya missing, safety blocks, 143s source grep). STATUS stuck `working` |
|
||||
| ~3:08+ | STATUS watcher created (@every 5m) after Anthony demanded visibility |
|
||||
| ~3:12–3:14 | Trace analysis; Anthony: intervene as superior |
|
||||
| ~3:15 | CoS killed pid 36384; redispatched **v2** with `.env` inject (`key_present=True`, pid 38988) |
|
||||
| ~3:26 | Mid-job ping: curling AgentMail inbox |
|
||||
| ~3:32 | **Inbox v2 done**: 4 msgs scanned, **NONE_FOUND** (no Birdeye/listing returns). ACTIVE_JOB cleared |
|
||||
| ~3:39–4:16 | Desktop often unreachable; watcher correctly stayed silent (idle) but kept waking CoS every 5m |
|
||||
|
||||
---
|
||||
|
||||
## 2. What "went off the rails"
|
||||
|
||||
### A. False starts / silent failures
|
||||
- Claimed or implied Ashbot "started" before SOCKS/SSH/folder/pid were real.
|
||||
- Windows detached launch looked dispatched with empty logs.
|
||||
- Anthony waited with "Nothing" / "It's been a long time" because CoS did not fail-loud or done-ping on time.
|
||||
|
||||
### B. Ashbot oneshot environment gap (root technical failure)
|
||||
- Method A `_launch.py` did **not** load `profiles/ashbot/.env`.
|
||||
- Gateway/Desktop sessions had AgentMail; SSH oneshot did not.
|
||||
- Birdeye send luckily had key in that session; inbox pass did not → multi-minute dead-end hunt.
|
||||
- Ashbot did not flip STATUS to `blocked` after first KEY_MISSING; CoS was slow to intervene.
|
||||
|
||||
### C. Visibility debt
|
||||
- No reliable mid-job channel until STATUS watcher.
|
||||
- iMessage contract failed (Photon free-tier + sidecar down) — chat ping is the real guarantee.
|
||||
- Watcher later correct for idle, but **@every 5m forever** creates noise when no job (and when desktop offline).
|
||||
|
||||
### D. Scope / attention scatter
|
||||
- Retro Revolution paste briefly looked like wrong-thread pollution.
|
||||
- Desktop Local dropped after inbox done → watcher "offline" ticks (silent to Anthony, still wake tax).
|
||||
|
||||
### E. What actually worked
|
||||
- File-brief + SSH-sync launch (once SOCKS up).
|
||||
- Birdeye outbound SMTP with `ashbotphoenix@agentmail.to`.
|
||||
- Intervene rule applied: kill spiral → inject key → v2 completed cleanly.
|
||||
- Lessons filed in Obsidian; several standing rules locked in CoS memory.
|
||||
|
||||
---
|
||||
|
||||
## 3. Root causes (ordered)
|
||||
|
||||
1. **CoS process immature:** announce before prove; late intervene; late done-ping.
|
||||
2. **Launcher incomplete:** oneshot must always inject AgentMail (and profile) from ashbot `.env`.
|
||||
3. **Hermes worker defaults:** will source-dive instead of STATUS=blocked + stop.
|
||||
4. **Notify path fragile:** Photon not reliable; chat must be primary.
|
||||
5. **Watcher cadence too aggressive when idle.**
|
||||
|
||||
---
|
||||
|
||||
## 4. Locked rules (do not regress)
|
||||
|
||||
1. Never say Hermes "started" until folder + STATUS pid + launch log LAUNCH line.
|
||||
2. SSH-sync `_launch.py` + `_cos_brief.txt`; no `start /B` until proven.
|
||||
3. Inject `AGENTMAIL_API_KEY` from ashbot `.env` before every mail oneshot.
|
||||
4. Key missing once → STATUS blocked + stop; CoS fixes; no source archaeology.
|
||||
5. Dead-end path → CoS intervenes immediately (superior).
|
||||
6. Done/blocked → CoS pings Anthony in chat immediately.
|
||||
7. Mid-job ~5m STATUS pings only while ACTIVE_JOB points at live work.
|
||||
8. Ashbot From = `ashbotphoenix@agentmail.to`; CC Anthony on listing reply threads.
|
||||
9. Phoenix domain mailbox parked; do not block on `assistant@phoenixcameronpark.com`.
|
||||
|
||||
---
|
||||
|
||||
## 5. Corrective actions (next)
|
||||
|
||||
| Action | Owner | Status |
|
||||
|---|---|---|
|
||||
| Standardize hermes-relay / `_launch.py` with .env inject | CoS | Partially done (v2 pattern) — promote to default template |
|
||||
| Pause STATUS watcher when ACTIVE_JOB empty; resume on dispatch | CoS | Do now |
|
||||
| Optional: daily/idle inbox poll for Birdeye replies (with key inject) | CoS | Not started |
|
||||
| Anthony: IG + YP confirm (close letter blockers) | Gatekeeper | Open |
|
||||
| Mirror lessons into Gitea `docs/agents/` | CoS | Pending |
|
||||
|
||||
---
|
||||
|
||||
## 6. Honest scorecard
|
||||
|
||||
| Goal | Result |
|
||||
|---|---|
|
||||
| Gate 3 3-URL pilot | Done (2 WONT-DO + Birdeye draft→sent) |
|
||||
| Birdeye email out | **Success** |
|
||||
| Inbox replies + CC Anthony | N/A — no returns yet (pass succeeded) |
|
||||
| iMessage start/done | Failed (infrastructure) |
|
||||
| Gatekeeper experience | **Poor mid-day** — silence, false starts, late intervene |
|
||||
| Learning loop | **Strong** — traces → lessons → rules → intervene |
|
||||
|
||||
**Bottom line:** The company work mostly completed; the *operating system* for CoS↔Hermes nearly failed Anthony’s trust. Paying attention to traces was correct. Default path forward is thin CoS, injected env, intervene early, chat as source of truth.
|
||||
@@ -0,0 +1,21 @@
|
||||
---
|
||||
tags: [video-stack, decisions]
|
||||
updated: 2026-10-16
|
||||
---
|
||||
# Decisions
|
||||
Back to [[Video Stack - Overview]]
|
||||
|
||||
| Date | Decision | Reason |
|
||||
|---|---|---|
|
||||
| 2026-10-15 | Business voice = **Gemini Kore** (B6), style "friendly, polished product explainer, conversational, upbeat but credible" | User picked B6 from B1-B10 ([[Test Scorecards]]) |
|
||||
| 2026-10-15 | Political voice = **Gemini Charon** (P1), newscaster style ("measured, authoritative newscaster, confident, serious") | User picked P1; authoritative male fits political ads |
|
||||
| 2026-10-15 | Local fallbacks: Chatterbox cloning the user's own recording (`C:\tts\voices\user_announcer_ref.wav`) and Qwen3-TTS | Free on the 3060; real human reference beats Kokoro refs |
|
||||
| 2026-10-15 | **HyperFrames** default renderer, Remotion fallback | Rendered Prop 1 first try in 31 s; one plain HTML file per scene is easier for local models |
|
||||
| 2026-10-15 | Music ~12 dB under the voice with ducking; final mix -16 LUFS | Voice clarity; consistent loudness |
|
||||
| 2026-10-15 | User samples and approves voice and music (and script) before any build | Avoid wasted builds; user taste drives choices |
|
||||
| 2026-10-15 | Political ads: serious civic style, no cheesy stings | User: "This is a political video. The music does not work." |
|
||||
| 2026-10-15 | Only ONE llama-server on TonyB (Qwen, port 1922) | 12 GB 3060 VRAM; TTS must fit alongside |
|
||||
| 2026-10-15 | Only tonyjbala Gitea credentials | User rule |
|
||||
| 2026-10-15 | Each video gets a unique design | Avoid template sameness |
|
||||
| 2026-10-15 | Political stack kept separate from business stack | Different rules (sourcing, disclaimers, tone) |
|
||||
| 2026-10-16 | Music = **ACE-Step 1.5** (local, MIT) as primary generator via `C:\tts\acestep_music.py`; code-synth `gen.py` fallback; ElevenLabs Music optional. Music approval gate (Gate 3) unchanged | User: include it in the stack; code-synth music "didn't really do anything for the feel"; ACE-Step is free on the 3060 (~25 s per 45 s track) |
|
||||
@@ -0,0 +1,353 @@
|
||||
---
|
||||
tags: [video-stack, spec]
|
||||
updated: 2026-10-16
|
||||
source: explainer-video-stack/STACK.md
|
||||
---
|
||||
# STACK.md — Everything Hermes needs to produce Grok-Bot-quality explainer videos
|
||||
|
||||
Audience: the Hermes **`explainer`** profile on **TonyB** (Windows 11, RTX 3060 12 GB), running a cheap model (e.g. `gpt-6-luna`).
|
||||
This is the single source of truth. When another doc disagrees with this one, **this one wins**.
|
||||
Repos: `tonyjbala/explainer-video-stack` (business explainers) and `tonyjbala/political-explainer-stack` (ballot/political ads).
|
||||
Worked example to copy: `C:\video\examples\prop1ad` (HyperFrames, 45 s, 9:16).
|
||||
Last updated: 2026-10-15.
|
||||
|
||||
---
|
||||
## 1. Overview and the quality bar
|
||||
|
||||
A "Grok-Bot-quality" video is a **30–90 s explainer or ad (default 45 s, 9:16, 1080x1920)** that passes all six tests:
|
||||
|
||||
| # | Bar | What it means in practice |
|
||||
|---|---|---|
|
||||
| 1 | **Verified facts** | Every number and claim has an opened, primary source (LAO, official voter guide, SOS, agency data, product docs). Opinion is labelled or phrased as opinion. A short source line sits under every stat on screen. |
|
||||
| 2 | **Natural VO** | Written for the ear (Section 5.1). No misreads (Whisper-checked), no odd pauses, no read-aloud stage directions. Political = authoritative male (Gemini **Charon**); business = firm, friendly female (Gemini **Kore**). |
|
||||
| 3 | **Music that adds feel** | Timed to the beats: pulse/tension under the problem or cost, a swell under the CTA, resolves on the cut. A generic bed that "doesn't do anything" is a fail (Prop 1 lesson). |
|
||||
| 4 | **Kinetic but clean design** | One idea per scene, huge numbers, staggered entrances, a palette made for the topic, nothing inside the platform UI safe zones, no dead half-screens. |
|
||||
| 5 | **Perfect captions** | Burned-in, 3-word groups, active word highlighted, timed from Whisper word timestamps, spelled correctly ("$500M", "Prop 1", not "Prop.", not "$5"). |
|
||||
| 6 | **Broadcast loudness** | Music ~12 dB under voice with sidechain ducking; final **-16 LUFS integrated, true peak ≤ -1.5 dBTP**, AAC 192k 48 kHz; last word ends ~2 s before the cut. |
|
||||
|
||||
Plus: three approval gates (script, voice, music) **before** any build, and nothing is ever posted or sent by you.
|
||||
|
||||
---
|
||||
## 2. Hardware and software inventory (verified on TonyB 2026-10-15 unless marked)
|
||||
|
||||
| Item | Version / path | Notes |
|
||||
|---|---|---|
|
||||
| GPU | NVIDIA GeForce RTX 3060, **12288 MiB**, driver 610.88 | Qwen alone uses ~6.4 GB baseline (≈11.5 GB with KV in use). TTS models need 3.9–5.0 GB. |
|
||||
| LLM | `C:\llama\llama-server.exe`, **Qwen3.8-35B-A3B-Q4_K_M on 127.0.0.1:1922** | The only model the video work assumes. Health: `http://127.0.0.1:1922/health`. |
|
||||
| Node | **v24.18.0** (HyperFrames needs Node ≥ 22) | |
|
||||
| HyperFrames | `npx --yes hyperframes@0.8.162 <cmd>` (pinned in `hf\ad\package.json`) | Default renderer. Set `$env:HYPERFRAMES_SKIP_SKILLS="1"` so npx doesn't install its bundled agent skills. Render sends anonymous telemetry: run `npx hyperframes telemetry disable` once. Never run `hyperframes publish` (it uploads). |
|
||||
| Remotion | `C:\video\template` (16:9 `Main` + 9:16 `MainVertical`, 30 fps) | Fallback renderer only. Copy per project with `xcopy /E /I`. |
|
||||
| ffmpeg | **9.0.1 essentials (gyan.dev)** on PATH | Mixing, loudness, muxing. |
|
||||
| Chatterbox | venv **`C:\tts\chatterbox`** (uv, Python 3.11, torch 2.6 cu124) → `C:\tts\chatterbox\Scripts\python.exe`; driver `C:\tts\tts.py` | Local fallback voice; also runs Whisper check (`--check-only`). ~1x realtime, 3.9 GB. |
|
||||
| Qwen3-TTS | venv **`C:\tts\qwen3tts`** (uv, torch 2.6 cu124, `qwen-tts`) → `C:\tts\qwen3tts\Scripts\python.exe`; 1.7B VoiceDesign + 1.7B Base | Second local option; voice-from-description. Slow (~2.5–5 min per 15–30 s), 4.3–5.0 GB, no flash-attn on Windows. Runner: `C:\tts\vt\vt.py` (env `VREF`/`VTAG`; engines `cb|qwen|qclone`). |
|
||||
| Whisper | **faster-whisper `small.en`, int8, CPU** | Built into `tts.py`; produces `words.json` `{"1": [[word,start,end],...]}`. |
|
||||
| Kokoro | kokoro-onnx (box/legacy) | Fast fallback voice for drafts only. **Never use a Kokoro clip as a clone reference** (refs in `C:\tts\voices\kokoro_*_ref.wav` are legacy; don't use them). |
|
||||
| Human voice ref | `C:\tts\voices\user_announcer_ref.wav` (Anthony, recorded with consent; 20.65 s mono 24 kHz) | The only approved clone reference. |
|
||||
| Gemini TTS | `C:\tts\gemini_tts.py` (stdlib only; repo copy `scripts/tts/gemini_tts.py`) | **Default voice engine.** Section 2.1. |
|
||||
| **ACE-Step 1.5** | `C:\Users\fight\Projects\ACE-Step-1.5` (v1.5.0, commit ca1e85f, uv `.venv`, torch 2.7.1 cu128). Checkpoints: `acestep-v15-turbo` (DiT), `acestep-5Hz-lm-0.6B` (+1.7B present, not used on 12 GB), `Qwen3-Embedding-0.6B`. Server: schtasks **`ACEStepServer`** → `start_acestep_api.bat` → Gradio UI + REST API on **http://127.0.0.1:7860** (log `acestep_server.log`). Wrapper **`C:\tts\acestep_music.py`** (repo copy `scripts/music/acestep_music.py`) | **Primary music generator (local, free, MIT).** ~22–30 s per 45 s instrumental track on the 3060. Section 2.2. |
|
||||
| Music (code, fallback) | `scripts/music/gen.py` (options A–C), `gen2.py` (D–F) in explainer-video-stack; `compose.py` presets in `C:\video\template` | numpy synth, royalty-free. Use only if ACE-Step is down or misses twice. Writes full track + 20 s preview. |
|
||||
| Music (paid, optional) | `C:\video\template\el_music.py "<prompt>" <seconds> out.mp3` | ElevenLabs Music, needs `ELEVENLABS_API_KEY`; instrumental only. Optional third choice. |
|
||||
|
||||
### 2.1 Gemini TTS (`gemini_tts.py`)
|
||||
```
|
||||
python C:\tts\gemini_tts.py script.json out.wav [--voice Charon] [--style "..."] [--gap 0.2] [--each DIR] [--whole] [--model gemini-3.8-flash-tts] [--fallback-model gemini-3.8-flash-lite-tts]
|
||||
```
|
||||
- `script.json` = JSON list of lines (or a .txt, one sentence per line). Default: one call per sentence, joined with 200 ms gaps. `--each DIR` also saves `DIR\s01.wav…`.
|
||||
- `--whole` = **one API call for the whole script** (use this by default; it saves quota).
|
||||
- On a 429 quota error it retries automatically on `gemini-3.8-flash-lite-tts` (`--fallback-model ""` disables).
|
||||
- **Presets (user-approved Oct 2026):**
|
||||
- Political (sample P1): `--voice Charon` (default style "measured, authoritative newscaster, confident, serious").
|
||||
- Business (sample B6): `--voice Kore --style "friendly, polished product explainer, conversational, upbeat but credible" --whole`.
|
||||
- **Quota (free tier):** flash-tts = **10 requests/day**; flash-lite-tts = 3 requests/minute. A per-sentence political ad can eat a whole day. Paid tier costs about $0.01–0.03/min.
|
||||
- **Key:** `GEMINI_API_KEY` from the environment. Put `GEMINI_API_KEY=...` in `C:\Users\fight\AppData\Local\hermes\profiles\explainer\.env` (or `setx GEMINI_API_KEY "..."`). Never print it, echo it, or commit it.
|
||||
- Output: 24 kHz mono 16-bit WAV.
|
||||
|
||||
---
|
||||
### 2.2 ACE-Step 1.5 music (`acestep_music.py`)
|
||||
- **What:** open-source text-to-music model (ACE-Step 1.5, turbo DiT + 0.6B planning LM). Code and weights are **MIT licensed** (`LICENSE`, `pyproject.toml`, checkpoint model card) → commercial use OK, output is ours. Still: never prompt "in the style of <artist>", never upload copyrighted reference audio.
|
||||
- **Runs as:** a persistent local server (Gradio UI for humans + REST API for scripts) on `127.0.0.1:7860`, launched with `--enable-api --init_service true --config_path acestep-v15-turbo --lm_model_path acestep-5Hz-lm-0.6B --offload_to_cpu true`. Check: `curl http://127.0.0.1:7860/health` → `"status":"ok"`. If down: `schtasks /Run /TN ACEStepServer` (models load in ~80 s). Do not start a second copy.
|
||||
- **VRAM coexistence:** it shares the 3060 with the one Qwen llama-server (port 1922). Idle ~8.5–10 GB total, **peak ~11.7 / 12 GB during generation** (CPU offload is on). It is not an LLM, so it does not break the one-model rule, but: never run ACE-Step generation at the same time as Qwen3-TTS or Chatterbox (OOM risk) — generate music, then voice, one job at a time. Never start Gemma or a second llama-server.
|
||||
- **Command (one track = full + 20 s preview, -18 LUFS mp3):**
|
||||
```powershell
|
||||
$AP="C:\Users\fight\Projects\ACE-Step-1.5\.venv\Scripts\python.exe"
|
||||
& $AP C:\tts\acestep_music.py --prompt "serious civic political ad underscore, cinematic orchestral, low strings, steady timpani pulse, building to a resolute swell, no vocals" --duration 47 --bpm 80 --key "D minor" --out C:\video\myproj\music\optA
|
||||
# -> optA.mp3 (full, -18 LUFS two-pass loudnorm), optA_preview.mp3 (last 20 s; --preview-start N to choose), optA_raw.wav (use raw/full in the mix)
|
||||
# options: --seed N (repeatable), --steps 8 (turbo default), --thinking (LM planning; slower), --lufs -18
|
||||
```
|
||||
Prints JSON with `gen_seconds`. Always instrumental (sends `lyrics="[Instrumental]"`).
|
||||
- **Prompt tips (ACE-Step expects comma-separated tags, not sentences):** genre/use, mood, instruments, tempo feel, arc, then `no vocals`. e.g. business: `modern, warm, upbeat corporate tech, light synth pulse, piano, no vocals` (`--bpm 110`); political: as above (`--bpm 70-90`, minor key). Put BPM/key in the flags, not the prompt. Keep it to 6–12 tags. For 3 options at Gate 3, vary the tags (e.g. strings vs piano vs brass) or seeds. It doesn't follow exact timestamps, so pick the take whose swell lands near the CTA, or trim/offset the start so it does. Seconds-level timing still comes from the edit.
|
||||
- **Measured (2026-10-16):** 45 s track in 22–30 s wall time; output 48 kHz stereo.
|
||||
|
||||
## 3. The end-to-end pipeline (with gates)
|
||||
|
||||
```
|
||||
Brief → Research/fact-check → Script (2 variants) → [GATE 1 script]
|
||||
→ Voice sample → [GATE 2 voice] → Music previews x3 → [GATE 3 music]
|
||||
→ Full VO + Whisper check → Scene design → HyperFrames build + lint + stills
|
||||
→ Mix → Final QA → Deliver (9:16 mp4, plus 16:9 re-layout if asked) → [GATE 4 final approval]
|
||||
```
|
||||
Project folder: `C:\video\<project>\` with `brief.md`, `vo_review.md`, `script.json`, `vo\`, `music\`, `hf\ad\`, `out\`, `snaps\`.
|
||||
|
||||
### Step 1 — Brief → `brief.md`
|
||||
Topic, audience, length (default 45 s), aspect (9:16 first), sponsor / "Paid for by" name, stance (political), CTA, tone, deadline.
|
||||
Political: use `political-explainer-stack\templates\brief.md`.
|
||||
**Done when:** every field filled or explicitly "ask user".
|
||||
|
||||
### Step 2 — Research and fact-check → `vo_review.md`
|
||||
- Open every source you cite. Prefer primary: LAO ballot analysis, CA SOS Quick Guide / Official Voter Guide, UC Berkeley IGS, agency data, the product's own docs.
|
||||
- Table columns: beat | original line | suggested accurate line | status (✅/⚠️) + why | source URL. Then a "Sources" list.
|
||||
- Watch for the Prop 1 traps: a figure that applies to only part of the measure ($500–600M/yr is for the $10B part only), "inflation-adjusted" qualifiers, endorsements you cannot verify (EDC GOP's own slate was not online; attribute only to a guide you actually have).
|
||||
- Label opinion lines ("doesn't change the rules that limit supply").
|
||||
**Done when:** every number has a URL, every ⚠️ has a fix.
|
||||
|
||||
### Step 3 — Script → `script.json` (2 variants)
|
||||
Rules in Section 5.1. Structure (political, 5 beats): **hook (0–7 s) → breakdown → cost/stakes → concerns/proof → vote call**. Business: hook → problem → how it works → proof/benefit → CTA.
|
||||
~**105 words max for 45 s** (≤150 wpm, ~2 s spare). One JSON string per beat/scene. Numbers spelled as spoken.
|
||||
**GATE 1:** send both variants (plain text, with word counts and estimated seconds). Stop until the user picks or edits.
|
||||
|
||||
### Step 4 — Voice sample → **GATE 2**
|
||||
Render line 1 (or the first ~15 s) with the preset voice (Section 4.4). If the user wants options, render 2–3 voices of the same line.
|
||||
**Done when:** the user approves one voice by name.
|
||||
|
||||
### Step 5 — Music previews → **GATE 3**
|
||||
Write a timed music brief first (e.g. "0–6 s sparse tension; 15–30 s driving low pulse under the cost; 38–45 s resolute swell, resolve on the cut"). Make **3 distinct options** with **ACE-Step** (code synth only as fallback), 20 s previews of the most important section, mp3. One-line description each.
|
||||
**Done when:** the user picks one (or asks for changes; iterate).
|
||||
|
||||
### Step 6 — Full VO + Whisper check → `vo\v1..vN.wav`, `vo\words.json`
|
||||
Render the whole script with the approved voice, one file per line (or `--whole` then Whisper). Check transcript vs script; re-roll any misread or awkward line.
|
||||
**Done when:** every line matches (digit-vs-word differences are fine), no pause > ~0.6 s inside a sentence, total VO ≤ length − 2 s.
|
||||
|
||||
### Step 7 — Scene design
|
||||
Pick palette, type, motion for **this topic** (Section 5). Plan one scene per VO line, with start = line start − 0.1 s.
|
||||
Safe zones 9:16: **top 250 px, bottom 430 px, right 90 px** clear. Content box x 80–910, y 380–1260. Captions at y≈1350.
|
||||
|
||||
### Step 8 — HyperFrames build
|
||||
Scene-per-`<div>` pattern from `prop1ad\build.py`: one Python script writes `hf\ad\index.html` from `words.json` + scene HTML. Lint, snapshot stills at key timestamps, look at them, fix, then render silent.
|
||||
**Done when:** lint clean, stills at hook / each scene / final 2 s look right, `out\silent.mp4` is exactly the target length.
|
||||
|
||||
### Step 9 — Mix
|
||||
Exact ffmpeg in Section 4.9. **Done when:** I = -16 ±1 LUFS, TP ≤ -1.5 dBTP, voice clearly on top, music audible in gaps and swelling at the CTA.
|
||||
|
||||
### Step 10 — Final QA → deliver → **GATE 4**
|
||||
Run the checklist (Section 4.10). Deliver `out\<project>_45s_vertical.mp4` (H.264 + AAC). If a 16:9 version is wanted, **re-lay out** the scenes for 1920x1080 (new positions, side-by-side panels); never just crop or pad the vertical. Send the path + report to the user; never post it.
|
||||
|
||||
---
|
||||
## 4. Exact commands (PowerShell on TonyB)
|
||||
|
||||
### 4.0 Setup per project
|
||||
```powershell
|
||||
$P = "C:\video\myproj"
|
||||
New-Item -ItemType Directory -Force $P,"$P\vo","$P\music","$P\out","$P\snaps" | Out-Null
|
||||
Copy-Item -Recurse C:\video\examples\prop1ad\hf "$P\hf" # HyperFrames project skeleton
|
||||
Copy-Item C:\video\examples\prop1ad\build.py "$P\build.py" # then edit scenes/palette
|
||||
$env:HYPERFRAMES_SKIP_SKILLS = "1"
|
||||
# Preflight: exactly one llama-server, on 1922
|
||||
Get-CimInstance Win32_Process -Filter "Name='llama-server.exe'" | % { "$($_.ProcessId) $($_.CommandLine)" }
|
||||
nvidia-smi --query-gpu=memory.used,memory.total --format=csv
|
||||
```
|
||||
Done: one llama-server line (port 1922). If you see more, **report it to the user; don't stop anything yourself.**
|
||||
|
||||
### 4.1 Long jobs (anything > ~2 min: Qwen3-TTS, Chatterbox full runs, renders)
|
||||
Remote-shell children die on disconnect, so use a one-time scheduled task:
|
||||
```powershell
|
||||
schtasks /create /tn OneTime_VO /tr "cmd /c C:\video\myproj\run_vo.cmd > C:\video\myproj\vo\log.txt 2>&1" /sc once /st 23:59 /it /f
|
||||
schtasks /run /tn OneTime_VO
|
||||
# poll: Get-Content C:\video\myproj\vo\log.txt -Tail 5 (until it prints DONE)
|
||||
schtasks /delete /tn OneTime_VO /f
|
||||
```
|
||||
|
||||
### 4.4 Voice
|
||||
```powershell
|
||||
cd $P
|
||||
# Political sample (Gemini Charon), line 1 only:
|
||||
python C:\tts\gemini_tts.py sample.json vo\sample.wav --voice Charon --whole
|
||||
# Business sample (Gemini Kore):
|
||||
python C:\tts\gemini_tts.py sample.json vo\sample.wav --voice Kore --style "friendly, polished product explainer, conversational, upbeat but credible" --whole
|
||||
# Full VO, one file per line (political; costs N requests):
|
||||
python C:\tts\gemini_tts.py script.json vo\voice.wav --voice Charon --each vo
|
||||
# Local fallback (no key / quota gone): Chatterbox with the consented human ref, one line per call
|
||||
C:\tts\chatterbox\Scripts\python.exe C:\tts\tts.py script.json vo --ref C:\tts\voices\user_announcer_ref.wav --ex 0.55 --cfg 0.4
|
||||
# Re-roll one bad line:
|
||||
C:\tts\chatterbox\Scripts\python.exe C:\tts\tts.py script.json vo --ref C:\tts\voices\user_announcer_ref.wav --ex 0.55 --cfg 0.4 --only 3 --seed 22
|
||||
```
|
||||
Whisper check / words.json (works on any WAVs named `v1.wav..vN.wav`; rename Gemini's `s01.wav` → `v1.wav` first):
|
||||
```powershell
|
||||
Get-ChildItem vo\s*.wav | % { $n=[int]($_.BaseName.Substring(1)); Copy-Item $_.FullName "vo\v$n.wav" }
|
||||
C:\tts\chatterbox\Scripts\python.exe C:\tts\tts.py script.json vo --check-only
|
||||
```
|
||||
Done: every HEARD line matches WANT (digits vs words OK). Join lines with 200 ms silence for `vo\voice.wav` if you rendered per line (gemini_tts.py does this already).
|
||||
|
||||
### 4.5 Music previews
|
||||
```powershell
|
||||
# PRIMARY: ACE-Step (Section 2.2), 3 options with different tags/seeds
|
||||
$AP="C:\Users\fight\Projects\ACE-Step-1.5\.venv\Scripts\python.exe"
|
||||
foreach($o in @(@("A","<tags A>, no vocals"),@("B","<tags B>, no vocals"),@("C","<tags C>, no vocals"))){ & $AP C:\tts\acestep_music.py --prompt $o[1] --duration 47 --out C:\video\myproj\music\opt$($o[0]) }
|
||||
# FALLBACK code-synth options (edit the arc/section times to the brief first)
|
||||
cd C:\video\myproj\music
|
||||
python <explainer-video-stack>\scripts\music\gen.py # A–C
|
||||
python <explainer-video-stack>\scripts\music\gen2.py # D–F
|
||||
# or presets: $env:MUSIC_SECONDS="47"; C:\tts\chatterbox\Scripts\python.exe C:\video\template\compose.py civic_tension cinematic_build
|
||||
# or ElevenLabs (if key set): C:\tts\chatterbox\Scripts\python.exe C:\video\template\el_music.py "dark low strings, slow heartbeat pulse, builds to resolute swell at 38s" 47 optA.mp3
|
||||
ffmpeg -y -i optionA_full.wav -ss 25 -t 20 -af "afade=t=in:d=0.5,afade=t=out:st=19:d=1" -b:a 160k optionA_preview.mp3
|
||||
```
|
||||
Music length = video length + ~2 s.
|
||||
|
||||
### 4.8 Build, lint, stills, render (HyperFrames)
|
||||
```powershell
|
||||
cd $P
|
||||
python build.py # writes hf\ad\index.html
|
||||
cd hf\ad
|
||||
npx --yes hyperframes@0.8.162 lint
|
||||
npx --yes hyperframes@0.8.162 snapshot --at 1,5,12,24,35,43 -o ..\..\snaps
|
||||
# LOOK at every PNG in snaps\ (Read tool). Fix, rebuild, re-snapshot only what changed.
|
||||
npx --yes hyperframes@0.8.162 render -o ..\..\out\silent.mp4 -q delivery
|
||||
ffprobe -v error -show_entries format=duration:stream=width,height -of csv=p=0 ..\..\out\silent.mp4
|
||||
```
|
||||
Done: lint 0 errors; stills OK; 1080x1920; duration = target (e.g. 45.0).
|
||||
|
||||
### 4.9 Mix (exact command; same as `scripts/mix.sh`)
|
||||
```powershell
|
||||
cd $P
|
||||
ffmpeg -v error -y -i vo\voice.wav -i music\approved.wav -filter_complex "[1:a]volume=-12dB[m];[0:a]asplit=2[v][sc];[m][sc]sidechaincompress=threshold=0.05:ratio=6:attack=20:release=300[md];[v][md]amix=inputs=2:duration=longest:normalize=0,loudnorm=I=-16:TP=-1.5:LRA=11[a]" -map "[a]" -ar 48000 out\mix.wav
|
||||
ffmpeg -v error -y -i out\silent.mp4 -i out\mix.wav -map 0:v -map 1:a -c:v copy -c:a aac -b:a 192k -ar 48000 -shortest out\myproj_45s_vertical.mp4
|
||||
ffmpeg -hide_banner -i out\myproj_45s_vertical.mp4 -af ebur128=peak=true -f null - 2>&1 | Select-String "I:|LRA:|Peak:" | Select-Object -Last 3
|
||||
```
|
||||
If the VO doesn't start at 0 s, delay it first: `-af "adelay=200|200"` on the voice or build `voice.wav` with the exact line offsets used in `build.py` (`ST` list).
|
||||
Done: I -16 ±1, Peak ≤ -1.5, listen once end to end.
|
||||
|
||||
### 4.10 Final QA checklist
|
||||
- [ ] Every fact sourced; source line on screen; "Paid for by …" + "Demo, not official" if political/demo.
|
||||
- [ ] Gates 1–3 approved (script, voice, music).
|
||||
- [ ] Whisper matches script; listened end to end; no awkward pauses.
|
||||
- [ ] Captions match audio word for word, 3-word groups, active word highlighted, inside safe zones.
|
||||
- [ ] Exact duration; last word ends ≥ ~2 s before the cut; music resolves on the cut.
|
||||
- [ ] -16 LUFS ±1, TP ≤ -1.5 dBTP; music audible but never masking VO.
|
||||
- [ ] H.264 + AAC, 1080x1920, file name ends in `.mp4`, plays on a phone.
|
||||
- [ ] Exactly one llama-server (1922) still running; no leftover scheduled tasks.
|
||||
- [ ] Score the run in `scorecard.md` (copy, voice, music, visuals, speed, cost; 1–5).
|
||||
|
||||
---
|
||||
## 5. Design system
|
||||
|
||||
### 5.1 Copywriting (write for the ear)
|
||||
1. Sentences ≤ 12 words. One idea per line; one line = one breath.
|
||||
2. Numbers as spoken: "ten billion dollars", "twenty twenty-six", "Prop One". Never two numbers in one sentence if avoidable (Prop 1 line 3 had four → awkward).
|
||||
3. No comma chains or read-aloud lists; split them.
|
||||
4. Contractions (it's, don't, you'll). Lead with the stake in the first 2 s.
|
||||
5. 1 s breath before the CTA. ≤150 wpm; ~105 words per 45 s.
|
||||
6. Two variants every time. Read aloud (TTS) and time against words.json before designing.
|
||||
|
||||
### 5.2 Palette, type, motion — chosen per topic
|
||||
- Start from the topic's emotion: civic/serious (navy, red, cream), local/warm (greens, sand), tech/fintech (deep ink + one electric accent). One dark base, one light text, **one accent**, one highlight color for the active caption word. Check contrast (white on base ≥ 7:1).
|
||||
- Type: one heavy sans (`"Liberation Sans", Arial` weight 800–900 for numbers/headlines, 700 for subs). Sizes for 1080x1920: kicker 30 px letter-spaced caps; sub 46 px; hero numbers **128–280 px**; notes/sources 26–28 px (≥26 px).
|
||||
- Motion (GSAP): each scene has `a1/a2/a3` elements entering at **+0.2 / +0.7 / +1.2 s**, `opacity 0→1, y 40→0, 0.5 s, power2.out`. No bounces or spins for political. Business can add a count-up or slide, still short (≤0.6 s).
|
||||
- Never reuse Prop 1's look unchanged for another topic.
|
||||
|
||||
### 5.3 Captions
|
||||
Built from Whisper `words.json`: group **3 words**, show from first word start to min(next group start, last word end + 0.4 s); active word tweens to the highlight color (`#FFD34D` in Prop 1) over 0.08 s at its timestamp. 60 px, weight 800, centered, `text-shadow:0 4px 12px #000`, box x 80–910 at y 1350. Clean tokens: "Prop." → "Prop", fix currency splits ("$5"+"00" → "$500"), attach "%"/"." to the previous token.
|
||||
|
||||
### 5.4 Kinetic numbers and data callouts
|
||||
One number per beat, hero size; proportional bars (`$10B` 820 px vs `$1.25B` 103 px); `stat` blocks with a 10 px accent rule; "✕" concern cards (max 3); a source note under each.
|
||||
|
||||
### 5.5 End cards
|
||||
Held ≥ 5 s. Political: big **NO/YES** block in the accent color, "ON PROP N", sponsor, close line, bordered box "Paid for by <exact committee name> · Demo, not official / Source: …". Business: logo/name, one-line value, CTA (URL), no clutter.
|
||||
|
||||
### 5.6 Political vs business
|
||||
| | Political | Business |
|
||||
|---|---|---|
|
||||
| Voice | Gemini **Charon**, authoritative male | Gemini **Kore**, firm friendly female |
|
||||
| Music | dark/urgent or resolute; pulse under cost, swell under vote call | warm/tech pulse, upbeat but not cheesy |
|
||||
| Look | civic navy/red/cream, flag stripe, no playful motion | topic-fitted brand palette, livelier motion OK |
|
||||
| Compliance | "Paid for by", demo label, FPPC check before any real use | product claims sourced |
|
||||
| Repo | political-explainer-stack | explainer-video-stack |
|
||||
|
||||
---
|
||||
## 6. Hard rules
|
||||
1. **One llama-server only: Qwen on 1922.** Never start Gemma (1923) or any second model alongside. Don't stop or start models without asking the user. (Don't run `C:\llama\restore_llama_servers.bat` — the TonyB copy still starts Gemma too.)
|
||||
2. **Long runs via one-time `schtasks`**, then delete the task.
|
||||
3. **Never post, send, publish, upload or share** without explicit approval. No `hyperframes publish`.
|
||||
4. **Gitea: tonyjbala account only.** Never use Ty / Ty_Tech credentials.
|
||||
5. **No Kokoro-made clone references.** Clone only from `C:\tts\voices\user_announcer_ref.wav` (consented) or documented public-domain audio. Never clone an identifiable person without written consent.
|
||||
6. **Keep tokens low:** reuse templates, read files only as needed, stills instead of test renders, regenerate only the broken line.
|
||||
7. Never print or commit keys (`GEMINI_API_KEY`, `ELEVENLABS_API_KEY`); no media files (mp4/wav/mp3) in git.
|
||||
|
||||
---
|
||||
## 7. Known pitfalls and fixes
|
||||
| Pitfall | Fix |
|
||||
|---|---|
|
||||
| **Blank HyperFrames scenes** — scenes render empty because the GSAP selector doesn't match (e.g. targeting `.a1` without the scene id, or an id that doesn't exist), so elements stay at the CSS start state / are never animated in. | Every scene div needs a unique `id="sN"` + `class="scene clip"` + `data-start`/`data-duration`; animate with `"#sN .a1"`. Register `window.__timelines["main"]=tl` and `tl.set({},{},<duration>)`. Run `lint`, then `snapshot` mid-scene to confirm content is visible. |
|
||||
| **Gemini reads directions aloud** ("Say in a calm voice: …" gets spoken) | Text = only words to be spoken. Delivery goes in `--style` (sent as `speech_metadata.style`). systemInstruction is rejected. |
|
||||
| **Gemini quota (429)** — 10/day on flash-tts | Use `--whole`; script auto-falls back to Flash-Lite (3/min). The daily quota resets once a day (observed around 4:50 PM PT). Fallback: Chatterbox with human ref. |
|
||||
| **Whisper number formatting** ("350,000" vs "three hundred fifty thousand") | Not a misread. Compare normalized; for captions show digits (they read better on screen). |
|
||||
| **Awkward VO** (odd pauses, flat delivery) | Shorter sentences, fewer numbers per line; one sentence per call; re-roll seed; never a Kokoro reference. |
|
||||
| **Tight ending** (slower voice runs into the cut) | Budget ~2 s spare; trim words, or speed VO ≤3% (`atempo=1.03`); never cut the last word. |
|
||||
| **Windows mp4 extension** — Explorer hides extensions, so a file can end up `name.mp4.mp4` or without `.mp4` and won't play on the phone | Always pass an explicit `-o ...\name.mp4`; verify with `Get-ChildItem out` (shows full names) and `ffprobe`. |
|
||||
| HyperFrames npx installs skills / telemetry | `$env:HYPERFRAMES_SKIP_SKILLS="1"`; `npx hyperframes telemetry disable`. |
|
||||
| Remote shell jobs die on disconnect | schtasks (Rule 2). |
|
||||
|
||||
---
|
||||
## 8. How a cheap model should work
|
||||
1. **One small step at a time.** Do one numbered step from Section 3, write its output to a file, then verify it (read the file back, run the check command) before the next.
|
||||
2. **Use the "Done when" lines and the QA checklist literally.** If a check fails, fix only that thing.
|
||||
3. **Copy, don't invent.** Start from `prop1ad\build.py`, the templates, and the exact commands above; change values, not structure.
|
||||
4. **Stop at every GATE** and ask the user in one short message with the files/paths. Don't build ahead of approvals.
|
||||
5. **Small context.** Don't paste whole files or logs; use `-Tail`, `Select-String`, and stills.
|
||||
6. **When unsure, ask.** If a source can't be opened, a tool errors twice, or VRAM is short: report it and ask; don't improvise around a rule.
|
||||
7. **Report at the end:** mp4 path, duration, LUFS/TP, voice + settings, final script, sources, claims softened, scorecard.
|
||||
|
||||
---
|
||||
## Appendix — file map
|
||||
- `C:\tts\` — `gemini_tts.py`, `tts.py` (Chatterbox + Whisper), `gen3060.py`, `chatterbox\`, `qwen3tts\`, `vt\vt.py`, `voices\user_announcer_ref.wav`
|
||||
- `C:\video\` — `examples\prop1ad`, `template` (Remotion), `voice_tests` (B1–B10, P1–P4, NOTES.md), `COPYWRITING.md`, `HERMES_PROMPT.md`, `STACK.md` (this file)
|
||||
- Hermes profile — `C:\Users\fight\AppData\Local\hermes\profiles\explainer\` (`SOUL.md`, `STACK.md`, `.env`, `skills\media\explainer-video\SKILL.md`)
|
||||
- Obsidian — `C:\Users\fight\Documents\Obsidian Vault\Video Stack\Full Stack Spec.md`
|
||||
|
||||
|
||||
## Prop 39 critique: lessons (2026-10-16)
|
||||
|
||||
Source: Hermes's "Yes on 39" 1:38 16:9 draft (`C:\video\prop39`). Length 1:38 was intended — not a defect.
|
||||
|
||||
### 1. Captions lost every space ("areyou?Many", "governmentissuedID")
|
||||
**Root cause:** `C:\video\prop39\build_hf.py` line 35 built word spans separated by a space, but the `.cap` CSS rule (line 171) was `display:flex;justify-content:center`. In a flex container each `<span>` becomes a flex item and whitespace-only text between items is discarded, so the words render glued together.
|
||||
- [ ] `.cap` is `display:block` (never flex/grid). Center with `text-align:center`.
|
||||
- [ ] Word spans joined with `' '.join(...)` — real space text nodes.
|
||||
- [ ] Run `python scripts/qa/check_captions.py hf/ad/index.html vo/words.json` — must print `CAPTION CHECK OK`.
|
||||
- [ ] Look at 3 stills with captions on screen and read them aloud.
|
||||
|
||||
### 2. Safe zones
|
||||
- 16:9 1920x1080: title-safe 5% → 96px left/right, 54px top/bottom. Nothing (DRAFT tag, logos, header) outside it.
|
||||
- Captions bottom edge ≥ 12% from the bottom (≥130px at 1080p), so player bars don't cover them.
|
||||
- 9:16 1080x1920: top 250px, bottom 430px, right 90px are UI zones. Content and captions stay inside.
|
||||
- [ ] Export stills of first scene, a caption-heavy scene and end card; check against the zones.
|
||||
|
||||
### 3. Voice was flat (uniform pauses)
|
||||
- Political = Gemini **Charon**; business = **Kore**. Steer delivery in `--style`, never in the text.
|
||||
- Natural punctuation, contractions ("it's", "don't"), mix short and long sentences.
|
||||
- Vary gaps between sentences 150–400ms (longer before the big point, shorter in lists). Never one fixed gap.
|
||||
- [ ] Listen to the whole VO once, start to finish, before building visuals.
|
||||
|
||||
### 4. Persuasive copy — pick the mode first
|
||||
- Decide: **neutral explainer** or **advocacy ad**. Write it in the brief and stick to it.
|
||||
- Advocacy ("Yes on X") argues from line 1. No hedges like "other access concerns remain debated" or "laws vary" — those belong only in neutral explainers.
|
||||
- Lead with the strongest lines (e.g. "When the process feels loose, people stop trusting it", "A secure election is a fair election").
|
||||
- Cut procedural scenes (e.g. "best-efforts" data/reporting) that kill momentum.
|
||||
|
||||
### 5. Pacing
|
||||
- 10–12s static slides feel like a deck. Something visible changes every 3–5s: a build, kinetic number, highlight, camera push, or cut.
|
||||
- [ ] No scene element is static >5s (check the timeline).
|
||||
|
||||
### 6. Facts and disclosure
|
||||
- Every stat (e.g. "36 states", Gallup/Pew numbers) has a source URL in `vo_review.md` / `sources.md`.
|
||||
- No chart labeled "illustrative" presented as data: use real numbers with citation, or remove.
|
||||
- A real "Paid for by ..." line replaces any "sponsor disclosure pending" placeholder before publishing.
|
||||
|
||||
## Pre-render QA checklist (Hermes must run all, every video)
|
||||
1. [ ] **Captions:** `check_captions.py` passes; 3 stills read correctly with spaces.
|
||||
2. [ ] **Safe zones:** stills checked (16:9: 96/54px, captions ≥12% up; 9:16: 250/430/90).
|
||||
3. [ ] **Voice:** full listen; varied pauses; Whisper check has no misreads.
|
||||
4. [ ] **Facts:** every number/claim has a source in `vo_review.md`; no "illustrative" data.
|
||||
5. [ ] **Mode:** brief says neutral or advocacy; no copy contradicts it.
|
||||
6. [ ] **Pacing:** visual change every ≤5s.
|
||||
7. [ ] **Disclosure:** real "Paid for by" end card (political); DRAFT tag inside safe zone or removed for final.
|
||||
Report each item's result to the user before delivering.
|
||||
|
||||
@@ -0,0 +1,65 @@
|
||||
---
|
||||
tags: [video-stack, lessons]
|
||||
updated: 2026-10-15
|
||||
---
|
||||
# Lessons Learned
|
||||
Back to [[Video Stack - Overview]]
|
||||
|
||||
- A Kokoro-made reference makes Chatterbox clones flat with odd pauses. Use a real human reference (the user's recording worked well; leave more headroom, it peaked at -0.1 dBFS).
|
||||
- Generate one sentence at a time (best of several seeds, ~200 ms gaps) to avoid strange pauses.
|
||||
- Copywriting: short spoken sentences, numbers written as spoken words, ~105 words per 45 s, leave ~2 s spare. Number-heavy long lines made Prop 1 sound awkward.
|
||||
- Gemini reads directions aloud unless they go in the style field.
|
||||
- Music must add feel: civic_tension and piano B were rejected; option A (dark low strings, slow heartbeat pulse) chosen for Prop 1. If code-composed music misses twice, use a richer generator.
|
||||
- ElevenLabs free tier was blocked for TTS ("unusual activity"); not retried.
|
||||
- HyperFrames is easier for local models than Remotion (plain HTML per scene).
|
||||
- Pick an authoritative male voice for political ads.
|
||||
|
||||
|
||||
## Prop 39 critique: lessons (2026-10-16)
|
||||
|
||||
Source: Hermes's "Yes on 39" 1:38 16:9 draft (`C:\video\prop39`). Length 1:38 was intended — not a defect.
|
||||
|
||||
### 1. Captions lost every space ("areyou?Many", "governmentissuedID")
|
||||
**Root cause:** `C:\video\prop39\build_hf.py` line 35 built word spans separated by a space, but the `.cap` CSS rule (line 171) was `display:flex;justify-content:center`. In a flex container each `<span>` becomes a flex item and whitespace-only text between items is discarded, so the words render glued together.
|
||||
- [ ] `.cap` is `display:block` (never flex/grid). Center with `text-align:center`.
|
||||
- [ ] Word spans joined with `' '.join(...)` — real space text nodes.
|
||||
- [ ] Run `python scripts/qa/check_captions.py hf/ad/index.html vo/words.json` — must print `CAPTION CHECK OK`.
|
||||
- [ ] Look at 3 stills with captions on screen and read them aloud.
|
||||
|
||||
### 2. Safe zones
|
||||
- 16:9 1920x1080: title-safe 5% → 96px left/right, 54px top/bottom. Nothing (DRAFT tag, logos, header) outside it.
|
||||
- Captions bottom edge ≥ 12% from the bottom (≥130px at 1080p), so player bars don't cover them.
|
||||
- 9:16 1080x1920: top 250px, bottom 430px, right 90px are UI zones. Content and captions stay inside.
|
||||
- [ ] Export stills of first scene, a caption-heavy scene and end card; check against the zones.
|
||||
|
||||
### 3. Voice was flat (uniform pauses)
|
||||
- Political = Gemini **Charon**; business = **Kore**. Steer delivery in `--style`, never in the text.
|
||||
- Natural punctuation, contractions ("it's", "don't"), mix short and long sentences.
|
||||
- Vary gaps between sentences 150–400ms (longer before the big point, shorter in lists). Never one fixed gap.
|
||||
- [ ] Listen to the whole VO once, start to finish, before building visuals.
|
||||
|
||||
### 4. Persuasive copy — pick the mode first
|
||||
- Decide: **neutral explainer** or **advocacy ad**. Write it in the brief and stick to it.
|
||||
- Advocacy ("Yes on X") argues from line 1. No hedges like "other access concerns remain debated" or "laws vary" — those belong only in neutral explainers.
|
||||
- Lead with the strongest lines (e.g. "When the process feels loose, people stop trusting it", "A secure election is a fair election").
|
||||
- Cut procedural scenes (e.g. "best-efforts" data/reporting) that kill momentum.
|
||||
|
||||
### 5. Pacing
|
||||
- 10–12s static slides feel like a deck. Something visible changes every 3–5s: a build, kinetic number, highlight, camera push, or cut.
|
||||
- [ ] No scene element is static >5s (check the timeline).
|
||||
|
||||
### 6. Facts and disclosure
|
||||
- Every stat (e.g. "36 states", Gallup/Pew numbers) has a source URL in `vo_review.md` / `sources.md`.
|
||||
- No chart labeled "illustrative" presented as data: use real numbers with citation, or remove.
|
||||
- A real "Paid for by ..." line replaces any "sponsor disclosure pending" placeholder before publishing.
|
||||
|
||||
## Pre-render QA checklist (Hermes must run all, every video)
|
||||
1. [ ] **Captions:** `check_captions.py` passes; 3 stills read correctly with spaces.
|
||||
2. [ ] **Safe zones:** stills checked (16:9: 96/54px, captions ≥12% up; 9:16: 250/430/90).
|
||||
3. [ ] **Voice:** full listen; varied pauses; Whisper check has no misreads.
|
||||
4. [ ] **Facts:** every number/claim has a source in `vo_review.md`; no "illustrative" data.
|
||||
5. [ ] **Mode:** brief says neutral or advocacy; no copy contradicts it.
|
||||
6. [ ] **Pacing:** visual change every ≤5s.
|
||||
7. [ ] **Disclosure:** real "Paid for by" end card (political); DRAFT tag inside safe zone or removed for final.
|
||||
Report each item's result to the user before delivering.
|
||||
|
||||
@@ -0,0 +1,10 @@
|
||||
---
|
||||
tags: [video-stack, project]
|
||||
updated: 2026-10-15
|
||||
---
|
||||
# GeoLocal Explainer
|
||||
Back to [[Video Stack - Overview]]
|
||||
|
||||
Status: final, landscape (16:9) + vertical (9:16).
|
||||
- Music: option 6 "arc" (ElevenLabs Music), ducked, final -16.1 LUFS.
|
||||
- Remotion composition, 6 scenes. Example in explainer-video-stack `examples/geolocal`.
|
||||
@@ -0,0 +1,8 @@
|
||||
---
|
||||
tags: [video-stack, project, stub]
|
||||
updated: 2026-10-15
|
||||
---
|
||||
# MiArc
|
||||
Back to [[Video Stack - Overview]]
|
||||
|
||||
Example subject for business explainers. Repo: https://gitea.vps1.afterthedemo.com/Ty_Tech/MiArc. Details TBD.
|
||||
@@ -0,0 +1,8 @@
|
||||
---
|
||||
tags: [video-stack, project, stub]
|
||||
updated: 2026-10-15
|
||||
---
|
||||
# Pocket Change
|
||||
Back to [[Video Stack - Overview]]
|
||||
|
||||
Example subject for business explainers. Details TBD.
|
||||
@@ -0,0 +1,12 @@
|
||||
---
|
||||
tags: [video-stack, project, political]
|
||||
updated: 2026-10-15
|
||||
---
|
||||
# Prop 1 Ad
|
||||
Back to [[Video Stack - Overview]]
|
||||
|
||||
Status: v2, 45 s vertical, HyperFrames. EDC GOP "Vote No on Prop 1".
|
||||
- Voice: male am_michael-based voice; music A (dark low strings, heartbeat pulse).
|
||||
- Feedback: visuals good; voice awkward with the script; copy needs refinement.
|
||||
- Next: re-voice with Gemini Charon and tighter copy (~105 words).
|
||||
- Example: `C:\video\examples\prop1ad`.
|
||||
@@ -0,0 +1,11 @@
|
||||
---
|
||||
tags: [video-stack, project, political]
|
||||
updated: 2026-10-15
|
||||
---
|
||||
# Prop 38 Ad
|
||||
Back to [[Video Stack - Overview]]
|
||||
|
||||
Status: v2 + vertical, EDC GOP demo (not official).
|
||||
- Voice: Chatterbox with Kokoro am_michael ref (speed 1.05).
|
||||
- Music: ElevenLabs Music, 45 s; ~11.6 dB under voice; final -16.1 LUFS, 45.1 s.
|
||||
- Example: `examples/prop38ad` in both repos.
|
||||
@@ -0,0 +1,8 @@
|
||||
---
|
||||
tags: [video-stack, project, political]
|
||||
updated: 2026-10-15
|
||||
---
|
||||
# Prop 38 Explainer
|
||||
Back to [[Video Stack - Overview]]
|
||||
|
||||
Status: done (earlier Voting Guide Series explainer). Brief kept as an example in the repos. Details TBD.
|
||||
@@ -0,0 +1,16 @@
|
||||
# Prop 39 Ad (Yes on 39, voter ID)
|
||||
|
||||
- **Made by:** Hermes (explainer profile), Oct 2026
|
||||
- **Files:** `C:\video\prop39` (HyperFrames build `build_hf.py` -> `hf\ad\index.html`; deliveries in `out\delivery`)
|
||||
- **Format:** 16:9, 1:38 (intended length), also vertical variants
|
||||
- **Status:** draft - needs fixes before publishing
|
||||
|
||||
## Critique (2026-10-16)
|
||||
- Captions missing spaces - root cause `.cap` was `display:flex` (build_hf.py line 171). Fixed in project (backup `build_hf.py.bak-20261016`) and stack templates; needs rebuild + re-render.
|
||||
- DRAFT tag at frame edge; captions too low (bottom:40px -> now 130px).
|
||||
- Flat voice with uniform pauses.
|
||||
- Copy reads neutral until the end; drop hedges ("other access concerns remain debated", "laws vary"); cut scene 5 (best-efforts data/reporting); build around "When the process feels loose, people stop trusting it" and "A secure election is a fair election."
|
||||
- Source "36 states" and Gallup/Pew; drop "illustrative" chart or cite real data; replace "sponsor disclosure pending" with a real Paid for by line.
|
||||
|
||||
See [[Lessons Learned]] -> Prop 39 critique: lessons.
|
||||
|
||||
@@ -0,0 +1,35 @@
|
||||
---
|
||||
tags: [video-stack, scorecard]
|
||||
updated: 2026-10-15
|
||||
---
|
||||
# Test Scorecards
|
||||
Back to [[Video Stack - Overview]] - details in `C:\video\voice_tests` / NOTES.md. All clips ~-16 LUFS, Whisper-checked with no misreads.
|
||||
|
||||
## Political (Prop 1 opening) - pick: **P1**
|
||||
| ID | Engine / voice | Gen time |
|
||||
|---|---|---|
|
||||
| **P1** | Gemini flash-tts, Charon, newscaster style | 6.4 s |
|
||||
| P2 | Qwen3-TTS VoiceDesign, "deep, authoritative American male political ad narrator" | 145 s |
|
||||
| P3 | Chatterbox, user ref | 86 s |
|
||||
| P4 | Qwen3-TTS clone, user ref | 161 s |
|
||||
|
||||
## Business (GeoLocal paragraph) - pick: **B6**
|
||||
| ID | Engine / voice | Gen time |
|
||||
|---|---|---|
|
||||
| B1 | Chatterbox, user ref | 130 s |
|
||||
| B2 | Qwen3-TTS VoiceDesign, warm American male narrator | 229 s |
|
||||
| B3 | Kokoro am_michael | ~7 s (CPU) |
|
||||
| B4 | Qwen3-TTS clone, user ref | 304 s |
|
||||
| B5 | Gemini Flash-Lite, Achird (male) | API |
|
||||
| **B6** | Gemini Flash-Lite, Kore (female, "Firm") | API |
|
||||
| B7 | Gemini Flash-Lite, Algieba (male) | API |
|
||||
| B8 | Gemini Flash-Lite, Zephyr (female) | API |
|
||||
| B9 | Qwen3-TTS VoiceDesign, young female tech narrator | 280 s |
|
||||
| B10 | Qwen3-TTS VoiceDesign, relaxed male podcast host | 266 s |
|
||||
|
||||
## Stack comparison template (score 1-5)
|
||||
| Date | Video | Producer | Copy | Voice | Music | Visuals | Speed | Cost | Notes |
|
||||
|---|---|---|---|---|---|---|---|---|---|
|
||||
| | | Grok Bot | | | | | | | |
|
||||
| | | Hermes explainer | | | | | | | |
|
||||
| | | Regis | | | | | | | |
|
||||
@@ -0,0 +1,39 @@
|
||||
---
|
||||
tags: [video-stack, setup]
|
||||
updated: 2026-10-16
|
||||
---
|
||||
# Tools and Setup
|
||||
Back to [[Video Stack - Overview]]
|
||||
|
||||
## Paths on TonyB
|
||||
- `C:\tts\tts.py` - Chatterbox voice script (`--ref <wav>` for cloning)
|
||||
- `C:\tts\gemini_tts.py` - Gemini TTS; `--style` for directions, `--whole` (whole script in one request), `--fallback-model` (switch to Lite when the daily limit is hit)
|
||||
- `C:\tts\qwen3tts` - Qwen3-TTS venv (1.7B VoiceDesign / Base clone)
|
||||
- `C:\tts\chatterbox` - Chatterbox venv
|
||||
- `C:\tts\voices\user_announcer_ref.wav` - user's own announcer recording (20.65 s, mono 24 kHz)
|
||||
- `C:\tts\vt\vt.py` - voice-test runner
|
||||
- `C:\video\template` - video template
|
||||
- `C:\video\HERMES_PROMPT.md`, `C:\video\COPYWRITING.md`
|
||||
- `C:\video\examples\prop1ad` - worked example
|
||||
- `C:\video\voice_tests` - bake-off samples
|
||||
- Hermes profile: `C:\Users\fight\AppData\Local\hermes\profiles\explainer` - start with `explainer chat`
|
||||
|
||||
## GPU (RTX 3060, 12 GB)
|
||||
- Qwen llama-server on :1922 uses ~6.4 GB baseline. Never start Gemma (:1923) alongside it.
|
||||
- Peak VRAM seen: Chatterbox 3.9 GB, Qwen3-TTS VoiceDesign ~4.3-4.5 GB, Qwen3-TTS clone 5.0 GB. Run TTS models one at a time.
|
||||
- Long runs: one-time scheduled tasks (`schtasks /create ... /sc once`, `/run`), delete afterwards.
|
||||
|
||||
## Gemini
|
||||
- Free tier: 10 requests/day on flash-tts (resets ~4:50 PM PT); flash-lite-tts 3/min. Script auto-falls back to Lite.
|
||||
- Style must go in `speech_metadata.style` (interactions endpoint), never in the text.
|
||||
- `GEMINI_API_KEY` goes in the explainer profile `.env` (user step; not confirmed done).
|
||||
|
||||
|
||||
## Music: ACE-Step 1.5 (primary, added 2026-10-16)
|
||||
- Install: `C:\Users\fight\Projects\ACE-Step-1.5` (v1.5.0, commit ca1e85f, `.venv`). License **MIT** (code + weights): commercial use OK.
|
||||
- Models: `acestep-v15-turbo` DiT + `acestep-5Hz-lm-0.6B` (1.7B LM also on disk, unused).
|
||||
- Server: scheduled task `ACEStepServer` -> `start_acestep_api.bat` -> Gradio UI + REST API at http://127.0.0.1:7860 (`/health`, `/release_task`, `/query_result`). Log: `acestep_server.log`. Restart: `schtasks /Run /TN ACEStepServer` (~80 s to load). Does not start at boot.
|
||||
- Wrapper: `C:\Users\fight\Projects\ACE-Step-1.5\.venv\Scripts\python.exe C:\tts\acestep_music.py --prompt "<tags>, no vocals" --duration 47 [--bpm N --key "D minor" --seed N] --out <path-no-ext>` -> full mp3 + 20 s preview at -18 LUFS.
|
||||
- Speed: 22-30 s per 45 s instrumental. VRAM: shares the 3060 with Qwen (1922); peak ~11.7/12 GB during generation (CPU offload on). Don't run it at the same time as Qwen3-TTS/Chatterbox.
|
||||
- Test tracks: `C:\video\music\acestep\acestep_business.mp3`, `acestep_political.mp3`.
|
||||
- Fallbacks: code-synth `scripts/music/gen.py`/`gen2.py`; ElevenLabs `el_music.py` (optional, paid).
|
||||
@@ -0,0 +1,28 @@
|
||||
---
|
||||
tags: [video-stack, moc]
|
||||
updated: 2026-10-15
|
||||
---
|
||||
# Video Stack - Overview
|
||||
|
||||
## Goals
|
||||
- Polished explainer and ad videos, with 9:16 vertical shorts as the main format (16:9 masters when needed).
|
||||
- Political **Voting Guide Series** (EDC GOP) kept separate from business explainers.
|
||||
- Find the best stack per purpose by scored test runs (see [[Test Scorecards]]).
|
||||
|
||||
## Split workflow
|
||||
- **Grok Bot** designs the workflow, templates, and prompts.
|
||||
- The **Hermes "explainer" profile** on TonyB (`explainer chat`) does the production work on cheap inference (`gpt-6-luna`).
|
||||
- Regis (Meta Muse agent) can be given the same brief for head-to-head comparisons.
|
||||
|
||||
## Stacks
|
||||
- Business: [explainer-video-stack](https://gitea.vps1.afterthedemo.com/tonyjbala/explainer-video-stack) (GeoLocal, MiArc, Pocket Change type subjects)
|
||||
- Political: [political-explainer-stack](https://gitea.vps1.afterthedemo.com/tonyjbala/political-explainer-stack) (templated brief/script, sourcing, "Paid for by" card)
|
||||
- Older: [video-stack](https://gitea.vps1.afterthedemo.com/tonyjbala/video-stack) (first version, holds the original HERMES_PROMPT)
|
||||
|
||||
## Notes
|
||||
- [[Full Stack Spec]] — the complete STACK.md (tools, pipeline, commands, rules)
|
||||
- [[Decisions]]
|
||||
- [[Tools and Setup]]
|
||||
- [[Lessons Learned]]
|
||||
- [[Test Scorecards]]
|
||||
- Projects: [[GeoLocal Explainer]], [[Prop 38 Explainer]], [[Prop 38 Ad]], [[Prop 1 Ad]], [[MiArc]], [[Pocket Change]]
|
||||
@@ -0,0 +1,35 @@
|
||||
---
|
||||
type: project
|
||||
status: active
|
||||
date: "2026-10-18"
|
||||
tags: [project, video-stack]
|
||||
---
|
||||
|
||||
# Video Stack — Status
|
||||
|
||||
## What it is
|
||||
Tony's video production program: 9:16 explainer/ad shorts (16:9 masters on demand), split
|
||||
into two separate stacks — a **business** explainer stack (GeoLocal, MiArc, Pocket Change
|
||||
type subjects) and a **political** "Voting Guide Series" (EDC GOP). Relocated from a
|
||||
root-level `Video Stack/` folder into `04_projects/` during the 2026-10-18 defrag.
|
||||
|
||||
## Current state
|
||||
- **Outputs:** 6 short projects — [[GeoLocal Explainer]], [[Prop 38 Explainer]], [[Prop 38 Ad]], [[Prop 1 Ad]], [[MiArc]], [[Pocket Change]]
|
||||
- **Voice picks (2026-10-15):** business = Gemini **Kore** (B6); political = Gemini **Charon** (P1). See [[Test Scorecards]].
|
||||
- **Renderer:** HyperFrames default, Remotion fallback.
|
||||
- **Music:** ACE-Step 1.5 (local, MIT) primary; voice and music gates before build.
|
||||
- **Workflow split:** Grok Bot designs workflow/templates/prompts; Hermes "explainer" profile produces on cheap inference; Regis for head-to-head comparisons.
|
||||
|
||||
## Key notes
|
||||
- [[Video Stack - Overview]] — program MOC
|
||||
- [[Full Stack Spec]] — complete STACK.md (tools, pipeline, commands, rules)
|
||||
- [[Decisions]] — dated decisions with rationale
|
||||
- [[Tools and Setup]]
|
||||
- [[Lessons Learned]]
|
||||
- [[Test Scorecards]] — scored stack/voice comparisons
|
||||
|
||||
## Next actions
|
||||
- _TBD — confirm with Tony whether Video Stack stays a project or becomes its own area._
|
||||
|
||||
## Related
|
||||
- [[MOC/Index|Master Index]] · [[index|Vault Index]]
|
||||
Reference in New Issue
Block a user