Commit Graph

21 Commits

Author SHA1 Message Date
Praxis CI 376fddf18e feat(P01-04-03,P01-04-04): wire store into pipeline + per-session cost logging
server/cost.py — derive_cost() counts LLM input/output tokens (gemma4 +
deepseek-v4-flash), Deepgram audio minutes, Cartesia/Piper characters;
derives estimated cents from scenarios/cost_rates.yaml. No enforced ceiling
(D-012 pilot). CostBreakdown dataclass carries the breakdown dict stored in
sessions.cost_breakdown_json. Per G-005: v0.1 logged costs are pilot-config
(Ollama tier + cloud), NOT at-scale /learner economics — that requires
self-hosted gemma4:e4b + Piper (post-pilot).

server/session_recorder.py — SessionRecorder wires the SQLite store into the
pipeline lifecycle: start() creates a session row, log_turn() writes turns
with ASR/TTS text + latency + accumulates cost inputs, set_branch_path(),
end() derives cost + writes outcome + debrief + updates progress. No auth —
learner_id is the hardcoded learner-1 (D-007).

7 tests pass (derive_cost basic/piper-zero/breakdown-dict, load_rates yaml,
no-enforced-ceiling, recorder full lifecycle with DB assertions, progress
updated).

---ci---
phase: 1
milestone: v0.1
plan: 04
task: 04-03,04-04
status: execute
persona: backend-engineer,data-engineer
requirements:
  covered: [REQ-STATE-01, REQ-NFR-COST-01]
---/ci---
2026-08-01 13:16:00 +00:00
Praxis CI 73b583342b feat(P01-04-01,P01-04-02): SQLite schema + async store (D-007)
db/schema.sql + db/migrations/0001_init.sql — four tables: learner (single
hardcoded 'learner-1'/'Alex' row, D-007 no auth), sessions (id, learner_id,
scenario_id, started_at, ended_at, branch_path_json, outcome,
cost_estimated_cents, debrief_text, cost_breakdown_json), turns (id,
session_id, seq, role, asr_text, tts_text, latency_ms), progress (learner_id,
scenario_id, attempts, last_outcome). db/migrate.py applies migrations
idempotently via a _migrations tracking table.

db/store.py — PraxisStore async access layer (aiosqlite): start_session,
log_turn, end_session (branch_path + outcome + cost + debrief),
update_progress (increment attempts + last_outcome), get_session, get_turns,
get_learner. Type-annotated SessionRow/TurnRow dataclasses. 6 tests pass
(migration creates all tables, hardcoded learner exists, idempotent
migrations, start→log→end→query full session, update_progress, get_learner).

---ci---
phase: 1
milestone: v0.1
plan: 04
task: 04-01,04-02
status: execute
persona: data-engineer
requirements:
  covered: [REQ-STATE-01]
---/ci---
2026-08-01 13:14:57 +00:00
Praxis CI 9108b07de1 feat(P01-03-05,P01-03-06): interruptibility harness + LLM-as-judge branch classifier
server/interruptibility.py — programmatic check that the pipeline task is
configured with allow_interruptions=True (D-008 abort-and-yield). The manual
test (speaking during AI speech cuts it off) is documented in
docs/latency-report.md; the automated test confirms the flag is set.

server/scenarios/classifier.py — classify_branch() uses
deepseek-v4-flash:cloud no-think (D-020) as LLM-as-judge to classify the
learner's turn transcripts into accept_resolution or escalate at session end,
OFFLINE from the voice loop (D-P1-05, G-002 post-hoc classification). Parses
the LLM's JSON response leniently (code fences, unknown-id fallback, malformed
JSON scan). classify_branch_sync_heuristic() is a rule-based fallback for
tests/e2e when no API key is present. 11 tests pass (interruptibility flag
on/off/missing; classifier accept/escalate scripted transcripts; JSON parse
valid/code-fenced/unknown-id/malformed; fake-LLM async classify; offline
structural assertion).

---ci---
phase: 1
milestone: v0.1
plan: 03
task: 03-05,03-06
status: execute
persona: backend-engineer
requirements:
  covered: [REQ-VOICE-04, REQ-SCEN-01]
---/ci---
2026-08-01 13:13:28 +00:00
Praxis CI 012992c44d feat(P01-03-04): CustomerServiceGuardrail — pluggable ruleset (D-019)
server/guardrails/customer_service.py implements the Guardrail interface with
the v0.1 Customer Service ruleset (RESEARCH.md safety baseline): blocks
legal/financial/medical advice, real-company impersonation, and a debrief
output filter that blocks recommendations the learner advise legal action
('tell the customer to sue them' → blocked_legal with a coaching redirect).
Session-start disclaimer text defined ('This is an AI practice session…').
Selected via PRAXIS_GUARDRAIL=customer_service (default); swaps in for
NoOpGuardrail with no pipeline change (D-019). server/__main__.py loads the
guardrail and logs the disclaimer as the first AI utterance. 9 unit tests
pass (disclaimer, blocks legal/financial/medical, allows normal coaching,
debrief filter blocks 'sue them', debrief allows coaching, swappable with
NoOp).

---ci---
phase: 1
milestone: v0.1
plan: 03
task: 03-04
status: execute
persona: backend-engineer
requirements:
  covered: [REQ-ORCH-02, REQ-NFR-SAFE-01]
---/ci---
2026-08-01 13:12:31 +00:00
Praxis CI 30051fdfd6 feat(P01-03-03,P01-03-07): Pipecat Flows wiring + scenario-driven prompt
server/scenarios/runtime.py — ScenarioRuntime maps a Scenario to a Pipecat
Flows state-machine spec: initial 'conversation' state with the scenario's
system prompt + opening line, branch metadata carried in the spec (v0.1
has no in-flight transitions per G-002 — branching is post-hoc; Phase 2+
can fork without schema change). set_branch() resolves the branch outcome
from the classifier; debrief_focus() returns the per-branch focus.

TASK-03-07: server/pipeline.py build_pipeline() now accepts a scenario_id,
loads the runtime, and uses scenario.setup.system_prompt instead of the
SLICE-02 hardcoded walking-skeleton prompt. Falls back gracefully if the
scenario can't load. server/__main__.py passes
PRAXIS_SCENARIO=customer_service_refund_ca_v01 by default. 7 runtime tests
pass (system prompt, set_branch accept/escalate, unknown-branch error,
debrief_focus per branch, flows spec branches, debrief model config).

---ci---
phase: 1
milestone: v0.1
plan: 03
task: 03-03,03-07
status: execute
persona: backend-engineer
requirements:
  covered: [REQ-SCEN-01, REQ-SCEN-FMT-01, REQ-ORCH-02]
---/ci---
2026-08-01 13:11:30 +00:00
Praxis CI be3df525d8 feat(P01-03-01,P01-03-02): Pydantic scenario schema + refund YAML
server/scenarios/schema.py defines the typed model: Scenario (id, path,
market, language, title, difficulty, failure_mode, persona, setup,
success_criteria, common_mistakes, branches[], debrief) + Branch (id,
trigger.learner_signals, outcome, failure_mode, debrief_focus) +
ScenarioDebrief (model=deepseek-v4-flash:cloud, mode=no_think, D-020).
failure_mode field present per D-009. server/scenarios/loader.py loads
YAML → Pydantic, validates at load time, raises typed ValidationError on
bad input.

scenarios/customer_service_refund_ca_v01.yaml — the v0.1 Canada Customer
Service scenario (D-010): 'Angry customer requesting refund on a damaged
product', one branch point (accept_resolution vs escalate),
failure_mode=escalates_unresolved, success criteria, common mistakes,
debrief config. Matches the RESEARCH.md example.

5 unit tests pass (valid parse, invalid raises typed error, branch
outcome Literal, branch_by_id, real YAML load). load() returns a valid
Scenario with both branches.

---ci---
phase: 1
milestone: v0.1
plan: 03
task: 03-01,03-02
status: execute
persona: data-engineer
requirements:
  covered: [REQ-SCEN-01, REQ-SCEN-FMT-01]
---/ci---
2026-08-01 13:10:00 +00:00
Praxis CI 7b1b296430 feat(P01-02-05,P01-02-06): React client + latency readout
client/ — React + Vite + TypeScript scaffolded with the Pipecat client SDK
(@pipecat-ai/client-js) and SmallWebRTCTransport
(@pipecat-ai/small-webrtc-transport). useVoiceSession.ts hook manages mic
permission, WebRTC connect, audio playback, live transcript, and a latency
readout (captures the e2e_latency_ms metric the server emits). App.tsx is a
minimal one-page session UI: disclaimer, Start/End buttons, status badge,
latency readout (within/over 600ms budget), live transcript. vite.config.ts
proxies /pipecat + /health to the Python server (port 8789). npm run
typecheck + npm run build pass.

server/latency.py — LatencyObserver (a Pipecat FrameProcessor) timestamps
transcript-ready, LLM-first-token, TTS-first-audio, and playback-start per
turn, computes ASR→TTS-first-audio (the v0.1 latency target), and logs it
to console with a within/over-budget verdict. Wired into the pipeline
between STT/LLM/TTS so it observes without altering the frame stream. 5
unit tests pass (LatencyRecord e2e math + observer construction +
reset_turn). Full server suite: 18 passed.

---ci---
phase: 1
milestone: v0.1
plan: 02
task: 02-05,02-06
status: execute
persona: frontend-engineer,backend-engineer
requirements:
  covered: [REQ-VOICE-01, REQ-VOICE-02, REQ-VOICE-03, REQ-NFR-LAT-01]
---/ci---
2026-08-01 13:08:42 +00:00
Praxis CI bba99418df feat(P01-02-04): Pipecat server pipeline — Silero VAD→Deepgram→Ollama→TTS→WebRTC
server/pipeline.py assembles the Pipecat pipeline (D-017): WebRTC audio in →
Deepgram Nova-3 STT → LLMContextAggregator(user) → OLLamaLLMService
(gemma4:cloud via https://ollama.com/v1 + bearer, R6) → Cartesia/Piper TTS
(selected via PRAXIS_TTS) → WebRTC audio out. Interruptibility via
allow_interruptions=True (D-008 abort-and-yield). Hardcoded single-turn
system prompt (SLICE-03 replaces with scenario YAML). All keys from env;
missing keys log a warning and the pipeline still starts (code structure is
the deliverable). server/__main__.py exposes a FastAPI app with /health
(reports key-provisioning status) and POST /pipecat/webrtc (accepts an SDP
offer, starts a pipeline task, returns the answer). Verified: imports
succeed, /health returns 200, routes wired.

---ci---
phase: 1
milestone: v0.1
plan: 02
task: 02-04
status: execute
persona: backend-engineer
requirements:
  covered: [REQ-VOICE-01, REQ-VOICE-02, REQ-VOICE-04, REQ-ORCH-01]
---/ci---
2026-08-01 13:04:17 +00:00
Praxis CI 733ba34f6d feat(P01-02-07): NoOpGuardrail stub — pluggable guardrail hook in place
server/guardrails/noop.py implements the Guardrail interface with an
always-allow stub so the Pipecat pipeline has the pluggable hook from
SLICE-02. Swapping to CustomerServiceGuardrail (SLICE-03 TASK-03-04)
requires no pipeline change (D-019). The session-start disclaimer text
(RESEARCH.md safety baseline) is defined here so the pipeline can play
it as the first AI utterance even before the real ruleset lands.

---ci---
phase: 1
milestone: v0.1
plan: 02
task: 02-07
status: execute
persona: backend-engineer
requirements:
  covered: [REQ-ORCH-02]
---/ci---
2026-08-01 13:01:58 +00:00
Praxis CI 2a9111c58c feat(P01-02-03): OllamaCloudLLM adapter behind LLMProvider (R6)
server/llm/ollama_cloud.py wraps the Ollama Cloud direct API
(https://ollama.com/api/chat + bearer, stream=True) behind LLMProvider.
chat() streams LLMStreamChunk (is_first flag for TTFT measurement);
chat_full() accumulates for the debrief / branch classifier (offline).
Two models: gemma4:cloud (roleplay_model) + deepseek-v4-flash:cloud
(debrief_model, no_think mode for latency, D-020). Resolves R6 — the
adapter confirms the direct API + bearer path; a live first-token
confirmation is pending the R3 probe with a real key. Graceful no-key
degradation (no chunks, no crash). 6 unit tests pass (mocked httpx
streaming response + env model selection + chat_full accumulation).

---ci---
phase: 1
milestone: v0.1
plan: 02
task: 02-03
status: execute
persona: backend-engineer
requirements:
  covered: [REQ-LLM-01, REQ-LLM-02]
---/ci---
2026-08-01 13:01:37 +00:00
Praxis CI f0afc8ef57 feat(P01-02-02): Cartesia + Piper TTS adapters behind TTSProvider
server/tts/cartesia_tts.py wraps the raw Cartesia Sonic WebSocket API
(wss://api.cartesia.ai/tts/websocket) — streaming PCM s16le, one voice
(CARTESIA_VOICE_ID, D-006). server/tts/piper_tts.py wraps piper-tts
self-hosted synthesis (R4 mitigation, open-weights). Both implement
TTSProvider (synthesize streaming + synthesize_all). Both degrade
gracefully (no audio, no crash) when the API key / voice model is absent.
PRAXIS_TTS=cartesia|piper selects the adapter via the registry with no
pipeline change (D-014). 7 unit tests pass (mock streams + env selection
+ graceful missing-key/model handling).

---ci---
phase: 1
milestone: v0.1
plan: 02
task: 02-02
status: execute
persona: backend-engineer
requirements:
  covered: [REQ-VOICE-02]
---/ci---
2026-08-01 13:00:54 +00:00
Praxis CI 47e24dbe59 feat(P01-02-01): service interfaces — TTSProvider, LLMProvider, Guardrail
server/services/base.py defines three abstract base classes with type
annotations: TTSProvider (async synthesize/synthesize_all, voice_id), LLMProvider
(async chat/chat_full, roleplay_model, debrief_model), Guardrail (async check,
session_start_disclaimer). GuardrailVerdict/Context/TTSResult/LLMStreamChunk
dataclasses carry typed metadata. server/services/registry.py resolves the
active adapter from env (PRAXIS_TTS, PRAXIS_GUARDRAIL) so the pipeline never
imports a concrete adapter directly — D-014/D-019/D-020 swap wiring. Verified:
'from server.services import TTSProvider, LLMProvider, Guardrail' succeeds.

---ci---
phase: 1
milestone: v0.1
plan: 02
task: 02-01
status: execute
persona: backend-engineer
requirements:
  covered: [REQ-VOICE-02, REQ-LLM-01, REQ-ORCH-02]
---/ci---
2026-08-01 13:00:00 +00:00
Praxis CI 80f070c60d feat(P01-01-06): latency report — R1-R4 spike documentation
docs/latency-report.md documents the SLICE-01 spike status: probe
infrastructure is built and ready; live measurements are pending API key
provisioning (only GITEA_TOKEN exists at EXECUTE time). Includes the latency
budget, the TTS decision matrix (Cartesia vs Piper, pending live R4 numbers),
the SLICE-01 go/no-go gate no-go actions per G-003, Piper pre-staging
instructions, and the R6 resolution (Pipecat OLLamaLLMService accepts custom
base_url + bearer). TTS decision recorded as 'pending live measurement —
Piper pre-staged as R4 mitigation per ARCHITECTURE.md'.

---ci---
phase: 1
milestone: v0.1
plan: 01
task: 01-06
status: execute
persona: lead-developer
requirements:
  covered: [REQ-VOICE-03, REQ-NFR-LAT-01, REQ-LLM-01, REQ-LLM-02]
---/ci---
2026-08-01 12:57:24 +00:00
Praxis CI 8ea0a2746f feat(P01-01-05): R4 probe — integrated three-hop e2e latency
scripts/probe_e2e.py runs the integrated path: ASR-transcript-in → Ollama
gemma4:cloud streaming → Cartesia TTS streaming; measures end-to-end
(transcript-in → first-audio-out) over N iterations (default 10). Prints
per-segment (LLM TTFT, TTS first-audio) + e2e summaries, and a budget
comparison vs the 600ms target. The Piper leg is optional (--piper) and
documents the pre-staged R4 mitigation if the voice model isn't provisioned.
If keys are missing, prints KEY_MISSING and exits 0.

---ci---
phase: 1
milestone: v0.1
plan: 01
task: 01-05
status: execute
persona: backend-engineer
requirements:
  covered: [REQ-VOICE-03, REQ-NFR-LAT-01, REQ-LLM-01, REQ-LLM-02]
---/ci---
2026-08-01 12:56:54 +00:00
Praxis CI a9d10656fa feat(P01-01-04): R3 probe — Ollama Cloud direct-API TTFT
scripts/probe_ollama.py calls https://ollama.com/api/chat with bearer auth,
stream=True, for gemma4:cloud (role-play) and deepseek-v4-flash:cloud
(no_think mode, D-020). Measures time-to-first-token over N iterations
(default 20), prints min/median/p95/mean tables for both models, and logs
any error/throttle events (R5). Also resolves R6 — a successful probe
confirms the direct API + bearer token path works. If OLLAMA_API_KEY is
missing, prints KEY_MISSING and exits 0.

---ci---
phase: 1
milestone: v0.1
plan: 01
task: 01-04
status: execute
persona: backend-engineer
requirements:
  covered: [REQ-LLM-01, REQ-LLM-02, REQ-VOICE-03, REQ-NFR-LAT-01]
---/ci---
2026-08-01 12:56:22 +00:00
Praxis CI b8e6bc83c5 feat(P01-01-03): R2 probe — Cartesia Sonic first-audio latency
scripts/probe_cartesia.py opens a WebSocket to Cartesia Sonic, requests TTS
for a sample customer-service utterance, and measures first-audio-byte
latency over N iterations (default 20). Prints min/median/p95/mean. Uses the
raw Cartesia WS API (no SDK coupling). If CARTESIA_API_KEY is missing, prints
KEY_MISSING and exits 0. Also fixes the R1 Deepgram probe to use the raw WS
API instead of the churn-prone SDK listen client.

---ci---
phase: 1
milestone: v0.1
plan: 01
task: 01-03
status: execute
persona: backend-engineer
requirements:
  covered: [REQ-VOICE-03, REQ-NFR-LAT-01]
---/ci---
2026-08-01 12:56:03 +00:00
Praxis CI bc7685b94f feat(P01-01-02): R1 probe — Deepgram Nova-3 first-partial latency
scripts/probe_deepgram.py opens a streaming WebSocket to Deepgram Nova-3,
sends synthesized 16kHz mono PCM in 100ms chunks, and measures
first-partial-transcript latency over N iterations (default 20). Prints
min/median/p95/mean. If DEEPGRAM_API_KEY is missing, prints a clear
KEY_MISSING banner and exits 0 — the probe infrastructure is the deliverable;
live numbers come when keys are provisioned.

---ci---
phase: 1
milestone: v0.1
plan: 01
task: 01-02
status: execute
persona: backend-engineer
requirements:
  covered: [REQ-VOICE-03, REQ-NFR-LAT-01]
---/ci---
2026-08-01 12:55:19 +00:00
Praxis CI ea1b77535e feat(P01-01-01): create repo skeleton for v0.1 minimal voice loop
server/, client/, scenarios/, db/, scripts/, tests/, docs/ dirs match
PERSONAS.md territory. pyproject.toml declares pipecat-ai[deepgram,cartesia,piper,webrtc]
+ openai + pydantic + pyyaml + aiosqlite + httpx + websockets. .env.example
documents DEEPGRAM_API_KEY, CARTESIA_API_KEY, OLLAMA_API_KEY and the TTS
selection (PRAXIS_TTS=cartesia|piper). Verified: python -c 'import pipecat'
succeeds (pipecat-ai 1.6.0 installed).

---ci---
phase: 1
milestone: v0.1
plan: 01
task: 01-01
status: execute
persona: lead-developer
requirements:
  covered: [REQ-ORCH-01]
---/ci---
2026-08-01 12:54:56 +00:00
Praxis CI 415c8ac8a6 docs(P00): phase 0 complete — v0.0.0 tagged, release pending
---ci---
phase: 0
milestone: v0.1
status: complete
escalation:
  resolution: auto
  type: release_pending
  reason: Gitea repo coreci/praxis does not exist (HTTP 404); tag+merge succeeded; release retries at milestone ship
---/ci---
2026-08-01 12:50:11 +00:00
Praxis CI 48cbd4a2b3 docs(P00): complete pre-execution phase
---ci---
phase: 0
milestone: v0.1
status: complete
requirements:
  covered: [REQ-VOICE-01, REQ-VOICE-02, REQ-VOICE-03, REQ-VOICE-04, REQ-SCEN-01, REQ-STATE-01, REQ-LLM-01, REQ-LLM-02, REQ-DEBRIEF-01, REQ-ORCH-01, REQ-ORCH-02, REQ-SCEN-FMT-01, REQ-NFR-LAT-01, REQ-NFR-SAFE-01, REQ-NFR-COST-01]
  partial: []
---/ci---
v0.0.0
2026-08-01 12:49:34 +00:00
Praxis CI bcb0118034 chore: seed .gitignore for env secrets 2026-08-01 12:38:44 +00:00