fix: wire DigestConfig through SDK and system for end-to-end digest

- Inject DigestConfig (persona, sections, TTS backend, voice_id) into
  MorningDigestAgent from both JarvisSystem._run_agent and Jarvis SDK
- Always inject digest_collect + text_to_speech tools for digest agent
- Fix TTS tool: import speech backends before checking TTSRegistry
- Fix Cartesia: default to British Butler voice when voice_id is empty
- Fix DigestStore: allow cross-thread SQLite access for FastAPI
- Propagate agent metadata (audio_path) through system return dict
- Add music section to default source map in MorningDigestAgent

Tested end-to-end: Qwen3.5 9B (Ollama) + Cartesia TTS + real data
from Oura, Gmail, Calendar, Tasks, Spotify, Apple Music.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
Jon Saad-Falcon
2026-04-02 14:25:55 -07:00
co-authored by Claude Opus 4.6
parent fd354e229c
commit bff925a679
5 changed files with 58 additions and 1 deletions
+2 -1
View File
@@ -69,7 +69,8 @@ class MorningDigestAgent(ToolUsingAgent):
default_source_map = {
"messages": ["gmail", "slack", "google_tasks"],
"calendar": ["gcalendar"],
"health": ["oura"],
"health": ["oura", "apple_health"],
"music": ["spotify", "apple_music"],
"world": [], # Handled by web_search, not connectors
}
sources = set()
+24
View File
@@ -482,6 +482,30 @@ class Jarvis:
if self._capability_policy is not None:
agent_kwargs["capability_policy"] = self._capability_policy
# Inject DigestConfig for morning_digest agent
if agent_name == "morning_digest" and hasattr(self._config, "digest"):
dc = self._config.digest
section_sources: Dict[str, Any] = {}
for s in dc.sections:
sc = getattr(dc, s, None)
if sc and hasattr(sc, "sources"):
section_sources[s] = sc.sources
agent_kwargs.update({
"persona": dc.persona,
"sections": dc.sections,
"section_sources": section_sources,
"timezone": dc.timezone,
"voice_id": dc.voice_id,
"tts_backend": dc.tts_backend,
})
# Ensure digest agent always has its required tools
from openjarvis.tools.digest_collect import DigestCollectTool
from openjarvis.tools.text_to_speech import TextToSpeechTool
digest_tools = [DigestCollectTool(), TextToSpeechTool()]
existing = agent_kwargs.get("tools", [])
agent_kwargs["tools"] = digest_tools + list(existing)
agent_obj = agent_cls(self._engine, model_name, **agent_kwargs)
ctx = AgentContext()
+4
View File
@@ -70,6 +70,10 @@ class CartesiaTTSBackend(TTSBackend):
if not self._api_key:
raise RuntimeError("CARTESIA_API_KEY not set")
# Default to "British Butler" voice — warm, authoritative, Jarvis-like
if not voice_id:
voice_id = "a0e99841-438c-4a64-b679-ae501e7d6091"
audio = _cartesia_synthesize(
self._api_key,
text,
+25
View File
@@ -211,6 +211,30 @@ class JarvisSystem:
agent_kwargs["session_store"] = self.session_store
agent_kwargs["memory_backend"] = self.memory_backend
# Inject DigestConfig when instantiating the morning_digest agent
if agent_name == "morning_digest" and hasattr(self.config, "digest"):
dc = self.config.digest
section_sources = {}
for s in dc.sections:
sc = getattr(dc, s, None)
if sc and hasattr(sc, "sources"):
section_sources[s] = sc.sources
agent_kwargs.update({
"persona": dc.persona,
"sections": dc.sections,
"section_sources": section_sources,
"timezone": dc.timezone,
"voice_id": dc.voice_id,
"tts_backend": dc.tts_backend,
})
# Ensure digest agent always has its required tools
from openjarvis.tools.digest_collect import DigestCollectTool
from openjarvis.tools.text_to_speech import TextToSpeechTool
digest_tools = [DigestCollectTool(), TextToSpeechTool()]
existing = agent_kwargs.get("tools", [])
agent_kwargs["tools"] = digest_tools + list(existing)
try:
ag = agent_cls(self.engine, self.model, **agent_kwargs)
except TypeError:
@@ -304,6 +328,7 @@ class JarvisSystem:
for tr in getattr(result, "tool_results", [])
],
"turns": getattr(result, "turns", 1),
"metadata": getattr(result, "metadata", {}),
"model": self.model,
"engine": self.engine_key,
"_telemetry": _telemetry,
+3
View File
@@ -53,6 +53,9 @@ class TextToSpeechTool(BaseTool):
)
def execute(self, **params: Any) -> ToolResult:
# Ensure TTS backends are registered
import openjarvis.speech # noqa: F401
text = params.get("text", "")
voice_id = params.get("voice_id", "")
backend_key = params.get("backend", "cartesia")