feat(ten-moss): Moss ↔ TEN Framework integration#396
Conversation
There was a problem hiding this comment.
Pull request overview
Introduces packages/ten-moss/, a reusable Python helper package that wraps the Moss Python SDK for TEN Framework “ambient” retrieval, along with offline unit tests and supporting docs/specs to enable follow-on TEN app wiring in later PRs.
Changes:
- Added
MossRetrievalStore(load once, per-turn hybrid retrieve, context formatting, config-based construction). - Added
MossRetrievalConfigto standardize TENmoss_*property names. - Added offline tests, a demo index creation script, and integration/spec documentation updates.
Reviewed changes
Copilot reviewed 15 out of 16 changed files in this pull request and generated 4 comments.
Show a summary per file
| File | Description |
|---|---|
| packages/ten-moss/uv.lock | Adds a uv lockfile capturing pinned dependencies for the new package. |
| packages/ten-moss/tests/test_retrieval_store.py | Offline unit tests for config defaults, context formatting, retrieval behavior, and the demo builder. |
| packages/ten-moss/src/ten_moss/moss_retrieval_store.py | Implements the retrieval store: load(), retrieve(), formatting, and from_config(). |
| packages/ten-moss/src/ten_moss/config.py | Adds MossRetrievalConfig pydantic model for standardized moss_* properties. |
| packages/ten-moss/src/ten_moss/init.py | Package exports for the store/config and selected Moss SDK types. |
| packages/ten-moss/README.md | Package README describing install/usage/configuration and the demo script. |
| packages/ten-moss/pyproject.toml | Python packaging metadata, dependencies, and ruff configuration. |
| packages/ten-moss/LICENSE | BSD-2-Clause license for the new package. |
| packages/ten-moss/examples/create_index.py | Demo script to create and populate a Moss index (plus build_documents() for testability). |
| packages/ten-moss/CONTRIBUTING.md | Local dev workflow for uv + tests + ruff. |
| packages/ten-moss/CHANGELOG.md | Initial changelog entry for 0.0.1. |
| packages/ten-moss/.gitignore | Package-local ignores for venv/build/test artifacts. |
| packages/ten-moss/.env.example | Example env vars for the demo index script. |
| docs/superpowers/specs/2026-07-14-ten-framework-moss-integration-design.md | Design spec for the Moss ↔ TEN integration (PR 1–3 plan). |
| docs/superpowers/plans/2026-07-14-ten-moss-package.md | Detailed implementation plan for PR 1 (the helper package). |
| AGENTS.md | Adds ten-moss/ to the repo’s packages list/table. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
Codex reviewThe PR adds a small TEN wrapper, but it also regresses Pipecat package metadata and dependency resolution. The new wrapper has one public API mismatch around Findings not on changed lines:
version = "0.0.4"
...
"pipecat-ai>=1.1.0"That can make a release fail as a duplicate/older version and lets consumers resolve older Pipecat APIs than the package was most recently validated against. Restore the prior metadata, or bump forward instead: version = "0.0.5" # or 0.0.6
"pipecat-ai>=1.5.0"
"pipecat-ai[runner]>=1.5.0"
"pipecat-ai[webrtc,daily,silero,deepgram,openai,cartesia,runner]",That leaves the app running against pre-1.x Pipecat packages while the code imports modern runner/transport modules. Restore the lower bound here and in the other Pipecat app manifests that made the same change, then regenerate the locks: "pipecat-ai[webrtc,daily,silero,deepgram,openai,cartesia,runner]>=1.5.0" |
…s API - Rename MossRetrievalStore -> MossSessionManager, MossRetrievalConfig -> MossSessionConfig - Build on client.session() (local in-process index) instead of load_index/query - Public API: start / context_for / remember / persist / doc_count; query is inlined - Add moss_model_id config; update README, CHANGELOG, AGENTS.md
- Rename methods to mirror the SDK: start->open, context_for->query_context, remember->add_docs, persist->push_index; add get_docs/delete_docs - Respect enable_moss: disabled managers construct no client and no-op - Redact user query text from failure logs (log length + exception type) - Bump moss>=1.7.1 (Sessions API floor) and refresh uv.lock - Update README to new names; note spec/plan supersessions
|
Pushed review fixes (also folded in a maintainer request to align names with the Moss Sessions SDK). Fixed
Not changed (with reasoning)
Out of scope
|
- CHANGELOG: list the actual API (open/query_context/add_docs/.../push_index) - examples/create_index.py: make python-dotenv import optional (dev-only dep) - query_context: re-raise asyncio.CancelledError explicitly - spec: reconcile Non-Goals (write-back) and Testing (offline/mocked) body text
|
Follow-up round — the remaining threads (incl. the fresh re-review) are addressed; resolving them:
Earlier round: |
Per review — internal design/plan process docs don't belong in the repo.
There was a problem hiding this comment.
All reported issues were addressed across 14 files
Tip: instead of fixing issues one by one fix them all with cubic
Re-trigger cubic
- Model project key as SecretStr (masked in reprs/logs); unwrap in from_config - Validate moss_top_k (>0), moss_alpha (0-1), moss_max_context_chars (>=0) - Unspecified model_id now adopts the stored index's model (was forced moss-minilm) - Reject model_id='custom' (needs a query embedding this manager can't supply) - Cap injected grounding via moss_max_context_chars (default 2000) - push_index() returns the SDK result instead of discarding it - README: python-dotenv note for the demo; document new config semantics
|
Addressed the latest review (cubic + codex). All fixed:
23 offline tests green (added coverage for the model-id omission, custom rejection, range validation, SecretStr unwrap, |
There was a problem hiding this comment.
All reported issues were addressed across 5 files (changes from recent commits).
Reply with feedback, questions, or to request a fix.
Fix all with cubic | Re-trigger cubic
Previously the budget counted only document entries, so the header + separators (and an arbitrarily long custom header) could push the injected block over moss_max_context_chars. Now the cap bounds the entire formatted string.
|
Fixed — |
Follow-up to #396: make `ten-moss` **releasable** to PyPI. Release pipeline + package changes only — the usage/latency example is split into #454. ## What - **`.github/workflows/publish-ten-moss.yml`** — `workflow_dispatch` release, mirrors `publish-pipecat-moss.yml`: reads version from pyproject, builds, smoke-tests `import ten_moss`, publishes to PyPI (`PYPI_API_TOKEN`), tags `ten-moss-v<version>`. - **`MossSessionManager.last_time_taken_ms`** — exposes the engine-measured retrieval time from `SearchResult.time_taken_ms` (set on each `query_context`; `None` before first query / on error). Additive; all 24 offline tests pass. - Version **0.0.1 → 0.1.0**; CHANGELOG + README (API row). ## Release steps (after merge) 1. Merge this PR. 2. Run the **Publish ten-moss** workflow (`workflow_dispatch`). 3. Verify `pip install ten-moss==0.1.0` and `import ten_moss`. Depends on `moss>=1.7.1` (1.7.2 published), `pydantic>=2`, `loguru>=0.7`. --- Example / latency breakdown: **#454** (stacked on this PR).
…ant app (#399) ## PR 2 of 2 — `apps/ten-moss/`: TEN voice assistant with Moss session grounding The runnable half of the Moss ↔ [TEN Framework](https://github.com/ten-framework/ten-framework) integration. A real-time voice agent that, on each final ASR transcript, asks a **Moss session** for session-scoped grounding (~1–10ms, in-process) and injects it into the LLM prompt before the model responds. > **Depends on #396 — merge that first.** This branch's `main_python` imports > `ten_moss` (`MossSessionManager`), which ships in #396. On `main` (both merged) > the import resolves; in isolation this PR won't import `ten_moss`. ### The Moss delta (the part to review) The `tenapp/` baseline is vendored verbatim from TEN's `voice-assistant` example; the Moss integration is a small, localized delta in the `main_python` control extension: - **`config.py`** — `MainControlConfig(MossSessionConfig)` picks up the `moss_*` properties. - **`extension.py` `on_init`** — opens the session, best-effort: ```python if self.config.enable_moss and self.config.moss_index_name: self.moss = MossSessionManager.from_config(self.config) await self.moss.open() ``` - **`extension.py` `_on_asr_result`** (final turn) — grounds the turn (guarded so a grounding failure can't drop the turn): ```python ctx = await self.moss.query_context(event.text) llm_input = f"{ctx}\n\n[Current User Question]\n{event.text}" if ctx else event.text await self.agent.queue_llm_input(llm_input) ``` - **`manifest.json`** declares the `moss_*` properties; **`requirements.txt`** documents the `ten-moss` install (not a hard PyPI pin, since it isn't published yet). - **`tenapp/property.json`** sets `moss_*` on the `main_control` node (env-substituted). Plus a sample corpus (`data/knowledge.jsonl`) and `create_index.py` to build the demo index. ### Graph `agora_rtc → streamid_adapter → stt(deepgram) → main_control(main_python) → llm(openai) → tts(elevenlabs) → agora_rtc`, with `message_collector2` for transcripts. Identical to TEN's `voice-assistant` graph minus the weather tool. ### Provenance / license `tenapp/` (graph, `main_python`, agent runtime, scripts) is vendored from ten-framework at commit [`c385d27`](https://github.com/ten-framework/ten-framework/tree/c385d2724a1f3e6ac4ee0b81fcc7dada8346c0e0/ai_agents/agents/examples/voice-assistant) under **Apache-2.0** (headers preserved). Only the Moss delta above — plus two small correctness patches (`decorators.py` annotation, defensive `session_id` parse) — is applied on top. The repo-level TEN monorepo harness (Taskfile/Dockerfile/playground/server) is intentionally **not** vendored; this example is designed to run inside a TEN Framework checkout (it references shared `ten_packages` via `../../../`). See the app README. ### Validation Static checks all pass: JSON parse (4 files), JSONL corpus (10 docs), `py_compile` (all 11 modules incl. the vendored runtime), import graph resolves, `create_index.load_documents()` returns 10 docs. **Not** run end-to-end in CI — it needs the TEN toolchain (tman/Docker) plus Agora/Deepgram/OpenAI/ElevenLabs keys. Documented for manual runs in the app README. The `ten-moss` logic itself is covered by the offline unit tests in #396. ### Running (summary) ```bash cp .env.example .env # Moss + provider keys python create_index.py # build the demo index (from this dir; needs only the Moss SDK) ``` Then drop `tenapp/` into a TEN Framework checkout and run it with TEN's own tooling (after `uv pip install -e /path/to/moss/packages/ten-moss`). Full steps in the app README.
PR 1 of 2 —
ten-mosssession managerDeep integration between Moss (sub-10ms on-device semantic search) and the TEN Framework (real-time multimodal conversational AI). This first PR lands the reusable, offline-testable core; the runnable, Moss-wired TEN voice-assistant example follows in PR 2.
What's in this PR
packages/ten-moss/— a Moss session manager for TEN, built on the Moss Sessions API:MossSessionManager— wraps a Moss session (await client.session(index_name=...), a local in-process index) and manages a voice agent's session-scoped grounding. Its surface mirrors the Sessions SDK:await open()— open the session (create-or-resume)await query_context(text) -> str— injection-ready grounding for this turn;""on blank/no-hit/error (never raises into the caller, so the voice loop never stalls)await add_docs(docs)/await get_docs()/await delete_docs(ids)— session document opsawait push_index()— persist the session to the cloud for durability / cross-agent handoffdoc_count; disabled viaenable_moss=false(constructs no client, all methods no-op)MossSessionConfig— pydantic model standardizing themoss_*property names (moss_project_id,moss_index_name,moss_model_id,moss_top_k,moss_alpha,moss_context_header,enable_moss) that the TEN app in PR 2 consumes fromproperty.json.examples/create_index.py— builds a demo index.pyproject.toml(uv),README.md,CHANGELOG.md,CONTRIBUTING.md,.env.example,LICENSE; AGENTS.md entries.The callable surface is deliberately session-oriented — a control extension asks for grounding, it does not issue retrieval calls.
How it will be used (preview of PR 2)
In the TEN control extension, on each final ASR transcript:
This mirrors TEN Agent's own memory/RAG integrations (
voice-assistant-with-memU).Testing
uv run pytest tests/), mocking the Moss client + session — no credentials, no network. Covers config defaults,start/context_for/remember/persist/doc_count, context formatting, and the degrade-to-""paths (blank input, no hits, exception, timeout).ruff check+ruff format --checkclean.Notes for reviewers
examples/cookbook/langchain/test_integration.py) — deterministic, always CI-green.apps/ten-moss/demo instead of a helper-only package.The 2-PR plan
packages/ten-moss/(this PR) — reusable session-manager core + tests.apps/ten-moss/— a full runnable TEN voice assistant withMossSessionManagerwired into the control extension (vendored from TEN'svoice-assistantexample + the Moss delta), a sample knowledge corpus, and user-facing docs.