Cost monitoring, PTT fix, Devon tuning, WEIRD pool expansion, YT thumbnails, LLM SEO, publish ep37

- Add real-time LLM/TTS cost tracking with live status bar display and post-show reports
- Fix PTT bug where Devon suggestion layout shift stopped recording via mouseleave
- Devon: facts-only during calls, full personality between calls
- Double WEIRD topic pool (109→203), bump weight to 14-25%
- Auto-generate YouTube thumbnails with bold hook text in publish pipeline
- LLM SEO: llms.txt, robots.txt for LLM crawlers, structured data, BreadcrumbList schemas
- Publish episode 37

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-03-15 05:33:27 -06:00
parent 3329cf9ac2
commit c70f83d04a
35 changed files with 4781 additions and 875 deletions

View File

@@ -328,7 +328,7 @@ class InternService:
# --- Main interface ---
async def ask(self, question: str, conversation_context: list[dict] | None = None) -> dict:
async def ask(self, question: str, conversation_context: list[dict] | None = None, caller_active: bool = False) -> dict:
"""Host asks intern a direct question. Returns {text, sources, tool_calls}."""
messages = []
@@ -343,6 +343,13 @@ class InternService:
"content": f"CURRENT ON-AIR CONVERSATION:\n{context_text}"
})
# When a caller is on the line, Devon should focus on facts not personal stories
if caller_active:
messages.append({
"role": "system",
"content": "A caller is on the line right now. Focus on delivering useful facts, context, and information. Skip personal stories and anecdotes — save those for when it's just you and Luke talking between calls."
})
# Include Devon's own recent conversation history
if self._devon_history:
messages.extend(self._devon_history[-10:])
@@ -357,6 +364,7 @@ class InternService:
model=self.model,
max_tokens=300,
max_tool_rounds=3,
category="devon_ask",
)
# Clean up for TTS
@@ -388,7 +396,7 @@ class InternService:
"tool_calls": tool_calls,
}
async def interject(self, conversation: list[dict]) -> dict | None:
async def interject(self, conversation: list[dict], caller_active: bool = False) -> dict | None:
"""Intern looks at conversation and decides if there's something worth adding.
Returns {text, sources, tool_calls} or None if nothing to add."""
if not conversation or len(conversation) < 2:
@@ -399,9 +407,16 @@ class InternService:
for msg in conversation[-8:]
)
messages = [{
"role": "user",
"content": (
if caller_active:
interjection_prompt = (
f"You're listening to this conversation on the show:\n\n{context_text}\n\n"
"A caller is on the line. Is there a useful fact, context, or piece of information "
"you can add to this conversation? Use your tools to look something up if needed. "
"Keep it focused — facts and context only, no personal stories or anecdotes right now. "
"If you truly have nothing useful to add, say exactly: NOTHING_TO_ADD"
)
else:
interjection_prompt = (
f"You're listening to this conversation on the show:\n\n{context_text}\n\n"
"You've been listening to this. Is there ANYTHING you want to jump in about? "
"Could be a fact you want to look up, a personal story this reminds you of, "
@@ -409,7 +424,11 @@ class InternService:
"or something you just have to say. You're Devon — you always have something. "
"Use your tools if you want to look something up, or just riff. "
"If you truly have absolutely nothing, say exactly: NOTHING_TO_ADD"
),
)
messages = [{
"role": "user",
"content": interjection_prompt,
}]
text, tool_calls = await llm_service.generate_with_tools(
@@ -420,6 +439,7 @@ class InternService:
model=self.model,
max_tokens=300,
max_tool_rounds=2,
category="devon_monitor",
)
text = self._clean_for_tts(text)
@@ -443,7 +463,7 @@ class InternService:
"tool_calls": tool_calls,
}
async def monitor_conversation(self, get_conversation: callable, on_suggestion: callable):
async def monitor_conversation(self, get_conversation: callable, on_suggestion: callable, get_caller_active: callable = None):
"""Background task that watches conversation and buffers suggestions.
get_conversation() should return the current conversation list.
on_suggestion(text, sources) is called when a suggestion is ready."""
@@ -465,7 +485,8 @@ class InternService:
last_checked_len = len(conversation)
try:
result = await self.interject(conversation)
caller_active = get_caller_active() if get_caller_active else False
result = await self.interject(conversation, caller_active=caller_active)
if result:
self.pending_interjection = result["text"]
self.pending_sources = result.get("tool_calls", [])
@@ -474,12 +495,12 @@ class InternService:
except Exception as e:
print(f"[Intern] Monitor error: {e}")
def start_monitoring(self, get_conversation: callable, on_suggestion: callable):
def start_monitoring(self, get_conversation: callable, on_suggestion: callable, get_caller_active: callable = None):
if self.monitoring:
return
self.monitoring = True
self._monitor_task = asyncio.create_task(
self.monitor_conversation(get_conversation, on_suggestion)
self.monitor_conversation(get_conversation, on_suggestion, get_caller_active)
)
print("[Intern] Monitoring started")