Files
praxis/.ciagent/GRILL.md
T
Praxis CI 2999c5163c docs(grill): adversarial review — 15 challenges, 2 MUST, 4 FIX, 7 ACCEPT
G-101 MUST: GITEA_TOKEN injection chain broken (hookscript on host, token in CT env)
  → Fix: bake token into snippet at staging time
G-102 MUST: PRAXIS_DB_PATH never read by server (db/store.py, db/migrate.py hardcode path)
  → Fix: add os.environ.get('PRAXIS_DB_PATH', 'praxis.db') in 2 files
G-103 FIX: 5 missing env vars in injection list (OLLAMA_CHAT_URL, CARTESIA_VOICE_ID, etc.)
G-104 FIX: health-check timeout 300s→600s (zero margin vs 5min build)
G-105 FIX: Dockerfile pip install runs before source copy (build fails)
G-106 FIX: bats test count 9→10 (MH-26 + Makefile)

Verdict: APPROVE_WITH_NOTES — plan sound with 6 fixes applied in EXECUTE

---ci---
project: praxis
phase: 0
milestone: v0.2
status: grill
---/ci---
2026-08-01 14:10:15 +00:00

62 KiB

Praxis — v0.1 Foundation Grill (Red-Team Review)

Grill date: 2026-08-01 Griller: CIAgent (adversarial executive review) Mode: mechanical (autonomy full, no user interaction) Branch: phase/00-pre-execution Artifacts reviewed: PROJECT.md (D-001..D-020), ROADMAP.md, REQUIREMENTS.md, ARCHITECTURE.md, PERSONAS.md, RESEARCH.md (R1-R10), PLAN.md (D-P1-01..06), config.json, CHECKPOINT.json, git log (5 commits) Codebase state: planning artifacts only — no src/, server/, or client/ exists yet (expected at Phase 1 EXECUTE)


Verdict

Verdict PROCEED
Confidence 0.72
Binding decisions 8 (G-001..G-008)
Escalations 0 (all axes resolved with confidence ≥ 0.60)
Challenges posed 28 forcing questions across 10 axes; 9 produced material findings

One-line summary: v0.1 is a genuinely well-prepared foundation milestone with research-grounded, swappable architecture and front-loaded risk spikes. It is not, however, what its "pilot" framing implies: it is a tech-validation harness with no real learners, no timeline, no budget, no named sponsor, and all three thesis-defining constraints (2G, $100 phone, $3/learner) explicitly relaxed. The binding decisions below correct the framing and require two concrete refinements before EXECUTE (no-go action definition, recruitment-plan deferral). None block execution.


Per-Axis Findings

Axis 1 — The Business Case Itself — confidence 0.72

Forcing question Evidence Finding
What problem does this solve, and is it still top priority? PROJECT.md L9-15 (voice-first apprenticeship for resource-constrained environments); D-001 overrides PRD's Kenya → Canada The PRD thesis is "apprenticeship for resource-constrained environments on $100 Android over 2G." v0.1 relaxes both defining constraints (C-2 relaxed per REQUIREMENTS L116, C-3 relaxed per D-012). v0.1 validates the easy version of the problem on Canadian cloud infrastructure. The hard version (the actual moat per RESEARCH L279) remains unproven.
Is the Canada pilot a business case or tech validation? D-012 (PROJECT L82): "Canada pilot is a foundation/tech-validation milestone, not a unit-economics milestone" It is tech validation. D-012 admits it. This is honest but the surrounding "pilot" language (ROADMAP L33, PLAN L15) oversells it. No market entry is occurring.
What happens if R4 latency fails 600ms? ARCHITECTURE L76-78 (Piper mitigation ~550ms); PLAN SLICE-01 "go/no-go gate" A mitigation path exists (Piper, then self-hosted gemma4:e4b). But the go/no-go gate defines no explicit no-go actions — see G-003. If both mitigations fail, the project has no documented kill/scope-reduce trigger.
ROI against counterfactual? No ROI document exists; success metrics (PROJECT L107-117) are "Year-1 targets, post-v0.1" No counterfactual. Acceptable for a foundation milestone; would be a blocker for a funded market-entry pilot.

Axis verdict: Sound for a foundation milestone. The business case is tech-validation, honestly admitted in D-012. The risk is that v0.1's success could be misread as thesis validation when it validates only the voice loop. → G-001.


Axis 2 — Scope and Requirements — confidence 0.78

Forcing question Evidence Finding
Scope expanding, contracting, or stable? D-002 (v0.1 frozen), D-006..D-012 (7 ambiguities resolved), REQUIREMENTS L124-135 (explicit out-of-scope) Stable and frozen. Out-of-scope is comprehensive (14 items). This is well-handled — rare for a project in flux.
Is v0.1 SO thin it doesn't validate the thesis? PLAN L15 (one scenario, one branch, one voice, debrief, no mastery) v0.1 validates the daily loop (speak → AI responds → debrief). It does NOT validate apprenticeship (no mastery gates, no progression, no multi-scenario). Acceptable: the daily loop is the load-bearing wall; mastery is a later floor.
Does "one branch point" actually prove branching works? D-010 (PROJECT L80): one branch (escalate vs accept); PLAN TASK-03-06: branch classifier runs at session end via LLM-as-judge, offline from voice loop; D-P1-05 confirms "offline at session end" No. The "branch" is a post-hoc outcome label, not a runtime conversation fork. The conversation is linear; the branch is classified after the fact. Pipecat Flows is wired (TASK-03-03) but the branch does not change the conversation in-flight. The claim "exercises branching" (D-010 rationale) is overstated.
Hidden requirements disclosed late? None found — guardrails (D-019), data residency (R10), PIPEDA all surfaced in research Clean. No hidden regulatory/security requirements lurking.

Axis verdict: Scope is honest and frozen. The one overstatement is the branching claim. → G-002.


Axis 3 — Architecture and Technical Feasibility — confidence 0.75

Forcing question Evidence Finding
Has the architecture been validated by builders, not just sellers? RESEARCH L314 ("Measure, don't assume"); R1-R4 all "measure in Phase 1"; PLAN SLICE-01 is the measurement Architecture is research-grounded (web-verified, not vendor-pitched) but not yet builder-validated. SLICE-01 is the validation. Correct sequencing.
Integration surface — where does cost double? Three cloud hops (Deepgram + Ollama Cloud + Cartesia) + WebRTC + Pipecat + React SDK + SQLite Six integration points. Each is a place where latency or cost can surprise. The plan puts all swappable services behind interfaces from SLICE-02 (TASK-02-01..02-03) — correct risk management.
Is the ~670ms budget real? ARCHITECTURE L75 (all-cloud ~670ms, over 600ms); L76 (Piper ~550ms, 50ms margin); all numbers vendor-claimed, unmeasured The all-cloud path fails the target by 70ms on paper. The Piper mitigation has 50ms margin — and that's vendor-claimed, not measured. This is genuinely tight. SLICE-01 measures it. The risk is real but correctly front-loaded.
Is Piper fallback a real mitigation or hand-wave? D-014 (TTS behind interface, Piper pre-staged); R8 (Piper maintainer gap — OHF seeking maintainers); RESEARCH L135 Real but thin. Piper is a first-class Pipecat TTS service and is fast on CPU. But: (a) 50ms margin is slim, (b) R8 flags a maintainer sustainability risk, (c) Piper prosody is "good but not Cartesia-tier" — quality regression. It's a legitimate mitigation, not a hand-wave, but it trades quality for latency and has a dependency-health caveat.
Is Pipecat a safe foundation? D-017 (13.8k★, 11k+ commits, active); R6 (Ollama direct-API integration depth unverified) Yes for v0.1. Active, well-adopted, native integrations for all three services. R6 (unverified Ollama direct-API integration) is a real risk mitigated by SLICE-02 TASK-02-03 (thin adapter if Pipecat's Ollama service rejects custom host+bearer). Long-term: if Pipecat stagnates, Praxis can fork — but that's a future-milestone concern.
Is Ollama Cloud direct API a SPOF? D-020 (single vendor, US-hosted); R10 (PIPEDA data residency); R5 (tier throttling) Yes. Single vendor, single region (US), tier-based throttling. Mitigations: swappable LLM interface (D-020), self-host gemma4:e4b post-pilot path. For v0.1 single-learner, acceptable. R10 (PIPEDA) is low-medium and unresolved — flagged for monitoring, not blocking.

Axis verdict: Architecture is the strongest part of this project. Research-grounded, swappable, risk-front-loaded. The 670ms budget is the tightest constraint and has no margin, but SLICE-01 addresses it correctly. The one gap: the go/no-go gate has no defined no-go actions. → G-003.


Axis 4 — People, Skills, and Organization — confidence 0.70

Forcing question Evidence Finding
Key-person dependency? PERSONAS.md: 4 active personas; backend-engineer owns majority surface (Pipecat + all service integrations per PERSONAS L145) backend-engineer is the critical persona. It owns Pipecat server, Ollama/Deepgram/Cartesia/Piper adapters, guardrails, scenario runtime. If backend-engineer capacity is constrained, the critical path stalls. This is a concentration risk.
Resources allocated at claimed percentages? config.json: max_concurrent_agents 5; PLAN D-P1-04 (SLICE-03
Product owner with authority? D-001 ("user-directed" Canada override); no named PO The human "user" makes high-level decisions; the CI orchestrator handles execution prioritization. No named PO for day-to-day. Acceptable for an autonomous CI project but means prioritization is algorithmic, not market-informed.
Building capability they don't have? R6 (Pipecat + Ollama direct-API integration unverified); personas have no prior Pipecat track record Yes — first Pipecat integration. Mitigated by SLICE-02 verification task. Acceptable for a foundation milestone (learning-as-you-go is fine for prototypes/tech-validation; the plan treats it as such with early spikes).

Axis verdict: Thin but appropriate for an autonomous agent project. backend-engineer concentration is the structural risk. No binding decision — noted as a monitoring item.


Axis 5 — Timeline and Estimates — confidence 0.65

Forcing question Evidence Finding
Was the deadline set before or after scope? No deadline exists anywhere. ROADMAP.md: phases with no dates. PLAN.md: 5 slices, 3 waves, no duration estimates. There is no timeline. This is itself a grill finding.
Is missing timeline a blocker? CHECKPOINT.json (stage: plan); autonomy: full (no external deadline) For Phase 0 pre-execution in an autonomous CI project with no external deadline, the absence of a calendar timeline is defensible — you plan first, estimate later. But Phase 1 EXECUTE has no per-slice effort estimate either, which means no burn-rate tracking is possible.
Critical path + 3-month push risk? PLAN §3: SLICE-01 → SLICE-02 → (SLICE-03 ‖ SLICE-04) → SLICE-05 The single thing that would push by 3+ months: R4 latency failing even with Piper, forcing a self-hosted-LLM/edge architecture rethink. SLICE-01 is the de facto time-box on this risk.
Definition of done? PLAN §4: 10 explicit Phase 1 exit criteria Well-handled. 10 concrete, testable exit criteria. This compensates partially for the missing timeline — "done" is unambiguous even if "when" is not.
Estimates evidence-based? None exist No estimates at all. The wave structure is a sequencing estimate but not a duration estimate.

Axis verdict: Missing timeline is a finding but not a blocker for pre-execution. The 10 exit criteria provide a strong definition of done. → G-004 (add per-slice estimates at EXECUTE).


Axis 6 — Budget and Financial Realism — confidence 0.70

Forcing question Evidence Finding
Budget spent vs. remaining? No budget exists. No dollar amount, no token budget, no compute allocation defined anywhere. There is no budget to track. For an autonomous CI pilot, the "budget" is tokens/compute — and no token budget is defined.
Predictable cost drivers? RESEARCH L60 (Ollama tier pricing, not unit-economics-friendly at scale); Deepgram $0.0043/min; Cartesia per-char; WebRTC TURN/STUN if behind NAT Cost drivers are identified in research but no aggregate estimate exists. The Ollama tier model (Pro $20/Max $100) means pilot cost is plan-tier-based, not per-session — so logged per-session cost (TASK-04-04) will not map to at-scale unit economics.
Is v0.1 measuring things that inform $3/learner? SLICE-04 TASK-04-04 (per-session cost logging: tokens, minutes, chars, derived cents) Yes — the measurement infrastructure is correct. It logs the right inputs. But the outputs won't be representative: Canada + cloud + Ollama-tier pricing is the most expensive configuration, not the $3/learner target configuration (which requires self-hosted gemma4:e4b + Piper).
Burn rate / runway? No budget → no burn rate → no runway calculation Ungoverned. Acceptable for a pilot; would be a blocker for a funded delivery.
Budget contingent on something? D-004 (monetization deferred to Phase 1); D-012 (no enforced ceiling) No contingencies — because there's no budget to be contingent.

Axis verdict: Budget is hand-waved but honestly so (D-012 admits it's not a unit-economics milestone). The cost-logging infrastructure is the right v0.1 contribution. The gap: v0.1 logged costs will mislead if read as representative of at-scale economics. → G-005.


Axis 7 — Risks, Assumptions, and Dependencies — confidence 0.72

Forcing question Evidence Finding
Is R4 actually the biggest risk? RESEARCH L358 (R4: all-cloud ~670ms); PLAN SLICE-01 go/no-go R4 is the biggest technical risk and is well-handled. But it has a mitigation path (Piper, self-host). The risks below are less mitigated.
Accent robustness on real Canadian speech? D-013 (Deepgram "accent-robust" — vendor claim); R9 (French-Canadian code-switching, logged as low-risk) Unmeasurable in v0.1 — there are no real learners (D-007: hardcoded profile). Deepgram's accent robustness is vendor-claimed, not tested on real Canadian speech. This is arguably a bigger risk than R4 because it has no quick fix (retrain or switch ASR) and can't be validated until real learners exist.
Is the branch point too trivial? D-010 (one binary branch); TASK-03-06 (post-hoc LLM-as-judge) The branch is post-hoc, not runtime (see Axis 2). It proves the data model (branch field exists) but not the branching runtime (conversation forks in-flight).
LLM hallucinating outside Customer Service role? D-019 (guardrail ruleset); TASK-03-04 (unit test: "sue them" blocked) Guardrails are system-prompt + output filter. TASK-03-04 tests one case ("sue them"). No adversarial/jailbreak test of the guardrail. For Customer Service (low-risk domain), this is acceptable — but the guardrail layer's pluggability for high-risk domains (health/electrical) is untested under adversarial pressure.
Top 3 assumptions? (1) Pipecat integrates with Ollama direct API (R6); (2) Deepgram Nova-3 handles Canadian English (R1/R9); (3) Cartesia/Piper hits latency targets (R2/R4) All three are "measure in Phase 1" — correctly front-loaded. The fourth unstated assumption: that real learners will use this. No evidence.
Single killing risk? No recruitment plan; PERSONAS.md is personas, not recruitment No real learners. The entire "pilot" depends on ~50 real Canadian learners (implied by success metrics context) and there is no recruitment plan, no recruitment channel, no recruitment budget. v0.1 will produce a dev-harness demo, not a pilot. This is the biggest unflagged risk.
Pre-mortem (12 months, failed — why?) Inferred Most likely causes: (a) R4 can't hit 600ms even with Piper → architecture rethink; (b) voice loop works but debrief is generic → doesn't validate apprenticeship; (c) no real learners ever use it — dev demo that never reaches a population. (c) is the most likely.

Axis verdict: R4 is well-handled. The bigger risks are (1) no real-learner recruitment plan, (2) accent robustness unmeasurable without learners, (3) guardrail not adversarially tested, (4) post-hoc branching. → G-006.


Axis 8 — Governance, Decision-Making, and Communication — confidence 0.68

Forcing question Evidence Finding
Decision-maker when executives disagree? D-001 ("user-directed"); no governance body, no named sponsor The human "user" is the sole decision-maker. No sponsor, no committee. For an autonomous CI project, the orchestrator + user play this role. No disagreement-resolution mechanism exists — but with one decision-maker, none is needed yet.
Governance cadence / escalation pattern? config.json escalation_hooks (deploy, delete_data, merge_to_main); escalation_timeout 300s Escalation hooks exist for operational actions (deploy/delete/merge) but not for project-level risks (R4 failure, scope drift, recruitment failure). No cadence — the pipeline stages are the cadence.
Omissions from status reports? .ciagent artifacts are the status report Thorough on architecture/requirements/risks. Omit: timeline, budget, sponsor, recruitment plan, real-learner validation, no-go actions. These omissions are the grill findings.
Stop-the-project trigger? PLAN SLICE-01 "go/no-go gate" — but no-go actions undefined No explicit stop trigger. The SLICE-01 gate is the closest but its no-go branch is a blank. No pre-agreed kill criteria.

Axis verdict: Governance is minimal — appropriate for an autonomous CI project but with two gaps: no-go actions undefined, no project-level escalation for non-operational risks. → G-007 (ties to G-003).


Axis 9 — Change, Adoption, and Operational Readiness — confidence 0.80

Forcing question Evidence Finding
Who uses v0.1, how does their work change? D-007 (single hardcoded learner "Alex", no auth); PERSONAS.md (Aspiring Alex persona) No real users. v0.1's "learner" is a hardcoded SQLite row (learner-1, "Alex"). No real human will use v0.1. This is a dev harness, not a pilot.
Plan to get 50 real learners? None. No recruitment plan, no channel, no budget, no timeline for recruitment. PERSONAS.md L103 describes "Aspiring Alex" as a persona, not a recruitment target. Missing entirely. This is the most serious finding. The "pilot" framing (ROADMAP, PLAN) implies learners; the reality (D-007) is a hardcoded profile.
Ops/support involved now or handed finished product? No ops team; single pilot host (ARCHITECTURE L88-95) N/A for a dev harness. No production operations to hand off. Acceptable.
Rollback plan? Greenfield — no production system to roll back to N/A. Acceptable.
Success criteria validated with judges? PLAN §1.2 (10 tech exit criteria); no adoption/success criteria validated with learners Exit criteria are all technical (latency, DB rows, guardrail unit tests). No adoption criteria. No one has validated that "a learner completes a session" = success with actual learners.

Axis verdict: v0.1 has no real learners and no plan to get them. It is a tech-validation harness, not a pilot. This is the most serious finding — not because it blocks execution, but because the "pilot" framing is misleading. → G-008.


Meta — Closing Review — confidence 0.75

Forcing question Finding
What would the auditor flag? (1) No timeline; (2) no budget; (3) no named sponsor; (4) no recruitment plan; (5) "pilot" framing overstated; (6) branching is post-hoc not runtime; (7) go/no-go no-go actions undefined; (8) guardrail not adversarially tested; (9) thesis-critical constraints (C-2, C-3) all deferred.
What is the project NOT doing that it should? Recruiting real learners. Adversarially testing guardrails. Estimating timeline/budget. Defining no-go actions. Testing debrief quality (not just existence).
Simplest 80%-of-value version? v0.1 is already the simplest version. One scenario, one voice, no mastery. Correctly scoped. The over-scoping risk is low; the under-scoping risk (doesn't validate thesis) is real but acknowledged by design (D-002).
What must be true for success in 90 days? (a) R4 latency is measurable and has a viable path to <600ms — likely (SLICE-01); (b) voice loop works end-to-end — likely (SLICE-02); (c) debrief generates meaningful, non-generic coaching — unverified (no quality test in plan); (d) real learners use it — false today (no recruitment plan). (c) and (d) are the gaps.

Binding Decisions

ID Decision Rationale Confidence Alternatives
G-001 v0.1 is explicitly a tech-validation milestone, not market validation. The thesis-critical constraints (C-2: $100 Android/2G, C-3: $3/learner) are deferred and unmeasured. v0.1 success must not be reported as product-market-fit or thesis validation. D-012 admits "tech-validation, not unit-economics"; C-2/C-3 both relaxed per REQUIREMENTS L116-117. v0.1 validates the voice loop on the least hard configuration (Canada, cloud, high bandwidth). The moat (low-bandwidth/mobile/B2C-apprentice per RESEARCH L279) is unproven. 0.78 Claim thesis validation at v0.1 (false); enforce C-2/C-3 in v0.1 (premature, wrong milestone)
G-002 v0.1's branch point is a post-hoc outcome classification (LLM-as-judge at session end, offline), not a runtime conversation fork. The claim "exercises branching" (D-010 rationale) is overstated. Phase 2+ must validate true in-flight branching before claiming the scenario engine works. PLAN TASK-03-06 + D-P1-05 confirm classifier runs "offline at session end"; conversation is linear; Pipecat Flows is wired but the branch does not change in-flight behavior. 0.80 Redefine v0.1 branching as runtime (adds latency + complexity); drop the branch entirely (loses data-model validation)
G-003 The SLICE-01 go/no-go gate must define explicit no-go actions before EXECUTE: (a) if e2e >600ms with Cartesia but ≤600ms with Piper → swap TTS to Piper (SLICE-02 pre-stage); (b) if e2e >600ms even with Piper → evaluate self-hosted gemma4:e4b for LLM hop; (c) if e2e >600ms with both mitigations → escalate: reduce latency target for v0.1 or rethink architecture. "Measure and decide" without defined decisions is not a gate. PLAN L44/L227 call SLICE-01 a "go/no-go gate" but define no no-go branch. ARCHITECTURE L78 says "must be spiked" but not what failure triggers. A gate with no defined failure action is a measurement, not a gate. 0.75 Leave no-go undefined (current state — not a real gate); define a hard kill (too aggressive for a foundation milestone)
G-004 No calendar timeline is acceptable for v0.1 Phase 0 (pre-execution, autonomous project, no external deadline). Phase 1 EXECUTE should add per-slice rough effort estimates (even token-budget-order) to enable burn-rate tracking and parallelism planning. The 10 Phase 1 exit criteria (PLAN §4) compensate for the missing timeline by providing an unambiguous definition of done. No timeline in any document (ROADMAP, PLAN, CHECKPOINT). Defensible for pre-execution; not defensible for EXECUTE where parallelism (D-P1-04) and burn-rate need sizing. Exit criteria are strong (10 testable items). 0.65 Add full Gantt timeline now (premature for autonomous project); proceed with no estimates at EXECUTE (no burn-rate visibility)
G-005 v0.1 cost logging (SLICE-04 TASK-04-04) is the correct measurement infrastructure, but v0.1 logged costs will NOT be representative of at-scale per-learner cost. Ollama tier-based pricing (Pro/Max plan, not per-token) + Canada cloud + low volume = the most expensive configuration. The $3/learner target requires self-hosted gemma4:e4b + Piper (post-pilot path). Cost representativeness must be re-measured in a later milestone with self-hosted models before making unit-economics claims. RESEARCH L60 ("usage-tier pricing is not unit-economics-friendly at scale"); D-012 (no enforced ceiling); D-020 (self-host e4b is post-pilot path). The logged cost informs the measurement method, not the number. 0.72 Treat v0.1 logged cost as representative (false); enforce $3 ceiling in v0.1 (premature, D-012 rejects)
G-006 The single biggest unflagged v0.1 risk is the absence of a real-learner recruitment plan. v0.1 as scoped will produce a dev-harness demo (hardcoded learner-1 "Alex"), not a pilot with learners. This does not block tech validation (which can proceed without learners) but blocks any "pilot" claim. Accent robustness (R9) and adoption cannot be validated without real learners. Recruitment is deferred to a later milestone. D-007 (hardcoded profile, no auth); PERSONAS.md (persona roster, not recruitment plan); no recruitment plan/budget/channel in any document. The "pilot" language in ROADMAP/PLAN implies learners; the reality is a dev harness. 0.80 Block v0.1 until recruitment plan exists (too conservative for tech validation); claim pilot status at v0.1 (false)
G-007 The SLICE-01 go/no-go gate is the de facto stop-the-project trigger, but its no-go branch actions are currently undefined (ties to G-003). Additionally, no project-level escalation path exists for non-operational risks (R4 failure, scope drift, recruitment failure) — only operational hooks (deploy/delete/merge per config.json). Define no-go actions per G-003 before EXECUTE. config.json escalation_hooks cover operational actions only; PLAN L227 gate has no no-go definition; no stop trigger in any document. 0.70 Add a governance committee (overhead for autonomous project); proceed with no stop trigger (high-risk by definition)
G-008 v0.1 must be explicitly understood as a tech-validation harness, not a learner pilot. The "pilot" framing in ROADMAP L33 and PLAN L15 should be read as "tech pilot," not "learner pilot." Real-learner recruitment, adoption validation, and accent robustness on real speech are deferred to a later milestone. This is a framing correction, not a scope change — v0.1's technical scope is correct. D-007 (pilot harness, not production multi-user); D-012 (tech-validation milestone); G-006 (no recruitment plan). The technical scope (one scenario, one voice, debrief, SQLite) is right; the labeling oversells it. 0.82 Relabel as "v0.1 tech-validation" formally (would modify PROJECT/ROADMAP — grill surfaces, doesn't rewrite); proceed with "pilot" framing as-is (misleading)

Escalations

None. All nine axes plus meta resolved with confidence ≥ 0.60. The two findings closest to escalation threshold:

  1. No-go action definition (G-003/G-007, confidence 0.70-0.75): resolvable with evidence — the go/no-go gate exists, it just needs its no-go branch specified. Not an escalation; a binding pre-EXECUTE refinement.
  2. Real-learner recruitment (G-006/G-008, confidence 0.80): resolvable with evidence — D-007 and D-012 already admit v0.1 is a tech-validation harness. The binding decision makes the implication explicit and defers recruitment. Not an escalation; a framing correction.

Summary of Most Serious Findings

  1. "Pilot" is a misnomer (G-006, G-008). v0.1 has no real learners, no recruitment plan, no recruitment budget. It is a tech-validation harness with a hardcoded SQLite row ("Alex"). The technical scope is correct; the framing oversells it. Accent robustness and adoption are unvalidatable without learners.

  2. All thesis-defining constraints are deferred (G-001). The Praxis moat is "$100 Android on 2G at $3/learner" (RESEARCH L279). v0.1 relaxes C-2 (2G/device) and C-3 ($3/learner). It validates the voice loop on the easiest, most expensive configuration (Canada, cloud, high bandwidth, Ollama tier pricing). v0.1 success must not be reported as thesis validation.

  3. "Branching scenario" is post-hoc, not runtime (G-002). The branch is an LLM-as-judge classification at session end, offline from the voice loop. The conversation is linear. The data model (branch field) is validated; the branching runtime is not.

  4. Go/no-go gate has no no-go actions (G-003, G-007). SLICE-01 is called a "go/no-go gate" but defines no failure actions. A gate with no defined no-go branch is a measurement, not a gate. Must be specified before EXECUTE.

  5. No timeline, no budget, no sponsor (G-004, G-005). Defensible for Phase 0 pre-execution in an autonomous project, but EXECUTE needs per-slice estimates for burn-rate tracking. v0.1 cost logging is methodologically correct but its numbers won't represent at-scale economics (Ollama tier pricing ≠ per-token unit economics).

What's done well (to be clear-eyed): Research grounding (D-013..D-020 are web-verified, not vendor-pitched), swappable interfaces (TTS/LLM/guardrail all behind abstractions from SLICE-02), risk front-loading (SLICE-01 spike before building), explicit out-of-scope (14 items), 10 testable exit criteria, vertical-slice discipline (5 slices, each demoable). This is a well-prepared foundation. The findings above are framing corrections and pre-EXECUTE refinements, not structural rework.


End of grill report. Verdict: PROCEED at confidence 0.72. 8 binding decisions (G-001..G-008), 0 escalations. Escalations visible via ciagent audit. This grill surfaces findings; it does not rewrite PROJECT.md, ROADMAP.md, or REQUIREMENTS.md. Binding decisions that warrant spec changes must be promoted explicitly by the user (e.g., via ciagent-clarify or a follow-up CLARIFY stage).


Praxis — v0.2 Proxmox LXC Deployment Grill (Red-Team Review)

Grill date: 2026-08-01 Griller: CI Griller (adversarial red-team) Mode: full autonomy (auto-decide all; 0 escalations expected) Target: .ciagent/PLAN.md — 10 slices, 4 waves, 34 tasks, 20 REQ-IDs (REQ-DEPLOY-01..16, REQ-NFR-DEPLOY-01..04) Artifacts reviewed: PROJECT.md (D-021..D-030), REQUIREMENTS.md, RESEARCH.md (10 questions, 6 risks), ARCHITECTURE.md, PERSONAS.md (5 active, frontend deactivated), PLAN.md, config.json, coreci source (/root/coreci/scripts/proxmox/), praxis codebase (server/__main__.py, db/store.py, pyproject.toml, .gitignore, .env.example, client/package.json) Confidence threshold: 0.60 (binding); < 0.60 = escalate


Method

Assumed the plan is unfeasible, over-scoped, and too costly. Cross-referenced every plan claim against coreci source and the praxis codebase. Found where the plan is wrong.


Challenges

C-01: GITEA_TOKEN not available to the firstboot hookscript — secret injection chain is broken

Axis: Feasibility / Dependency risk / Security Confidence: 0.85 Evidence:

  • PLAN.md TASK-05-01 step 3 (line 368): pct exec "$vmid" -- sh -c 'git clone https://${GITEA_TOKEN}@git.cloudinit.dev/.../praxis.git /opt/praxis'
  • PLAN.md TASK-05-01 (line 372): "GITEA_TOKEN is available via lxc.environment (set by lxc-config.sh in SLICE-03)"
  • RESEARCH.md Q5 (line 23): "GITEA_TOKEN is passed via lxc.environment and available inside the CT"
  • coreci firstboot-hook.sh lines 19-27 comment: "Environment (set on the PVE host when the hookscript runs; for a fully-automated deploy, stage a version of this snippet with the secrets baked in)"
  • coreci lxc-config.sh line 59-61: lxc.environment: GITEA_TOKEN=... — writes to /etc/pve/lxc/<vmid>.conf, injecting into the CT's systemd environment, NOT the PVE host's environment

The problem: The hookscript runs on the PVE host (not inside the CT). lxc.environment injects vars into the CT's init process (systemd PID 1 inside the CT), NOT into the PVE host's environment. The hookscript executing on the host does NOT have GITEA_TOKEN in its environment. Coreci's design acknowledges this: it says to "stage a version of this snippet with the secrets baked in" — i.e., the snippet file itself is generated with the token embedded. Praxis's stage-snippet.sh (TASK-03-06) fetches the raw file from Gitea (no baking), so the token is NOT in the hookscript.

Secondary issue — pct exec env inheritance: Even if the hookscript had GITEA_TOKEN on the host and passed it via pct exec -- sh -c '...${GITEA_TOKEN}...', the single-quoted sh -c body passes ${GITEA_TOKEN} literally to the CT's shell. The CT's shell would need GITEA_TOKEN in its environment. pct exec in Proxmox 8 does NOT reliably inherit lxc.environment vars — it spawns a process in the CT namespace but starts with a fresh environment, not systemd's inherited env. The plan's claim that lxc.environmentpct exec inheritance works is unvalidated and contradicts coreci's own design (which fetches on the host and pct pushes, specifically to avoid needing the token inside the CT).

Impact: The firstboot hook's git clone will fail with authentication error → the CT never gets the praxis repo → install-service.sh never runs → health-check times out at 300s → rollback fires → deploy fails every time. This is a ship blocker.

C-02: PRAXIS_DB_PATH env var is never read by the server — SQLite volume mount is a no-op

Axis: Feasibility / Operability / Completeness Confidence: 0.90 Evidence:

  • PLAN.md TASK-01-03 (line 117): PRAXIS_DB_PATH=/app/data/praxis.db in docker-compose.yml environment
  • PLAN.md TASK-03-04 (line 237): lxc.environment: PRAXIS_DB_PATH=/app/data/praxis.db in lxc-config.sh
  • PLAN.md TASK-06-02 (line 456): PRAXIS_DB_PATH=${PRAXIS_DB_PATH:-/app/data/praxis.db} in server.env
  • PLAN.md MH-06 (line 898): "SQLite persists across docker compose restart via named volume praxis-db"
  • praxis db/store.py line 25: _DEFAULT_DB_PATH = "praxis.db" (hardcoded, no env read)
  • praxis db/migrate.py line 8: _DEFAULT_DB_PATH = Path("praxis.db") (hardcoded, no env read)
  • grep -rn "PRAXIS_DB_PATH" /root/praxis/server/ /root/praxis/db/0 matches (only in .env.example)
  • PraxisStore.__init__ (store.py:70) takes db_path param defaulting to _DEFAULT_DB_PATH, but PraxisStore is never instantiated in the server code (grep -rn "PraxisStore(" /root/praxis/server/ → 0 matches). SessionRecorder takes a store: PraxisStore param but is never instantiated in pipeline.py.

The problem: The plan sets PRAXIS_DB_PATH=/app/data/praxis.db in three places (compose env, lxc.environment, server.env), but the server code never reads PRAXIS_DB_PATH. The DB defaults to ./praxis.db (CWD-relative, which is /app in the container). The Docker volume praxis-db is mounted at /app/data. The server writes to /app/praxis.db (container writable layer), NOT /app/data/praxis.db (the volume). Data is NOT persisted across container recreation — it's lost on docker compose down && docker compose up. The volume mount is dead weight.

Additionally, PraxisStore and SessionRecorder appear to be defined but never wired into the pipeline — the recorder is not instantiated in pipeline.py. This may be a v0.1 gap (recorder defined but not yet connected), but the plan's MH-06 (SQLite persistence verification) will fail because there's no code writing to the DB at the volume path.

Impact: Data loss on container restart/recreate. The persistence NFR is claimed but not delivered. MH-06 acceptance criterion will fail.

C-03: Missing env vars in lxc-config.sh / server.env — server will misconfigure at runtime

Axis: Consistency / Completeness Confidence: 0.85 Evidence:

  • The praxis server reads these env vars (verified by grep):
    • OLLAMA_CHAT_URL (server/llm/ollama_cloud.py:41) — used for the direct API chat endpoint
    • CARTESIA_VOICE_ID (server/pipeline.py:127, server/tts/cartesia_tts.py:40) — TTS voice selection
    • DEEPGRAM_REGION, DEEPGRAM_LANGUAGE — referenced in .env.example (lines 36-37), may be read by pipeline
    • PRAXIS_SCENARIO (server/__main__.py:83) — scenario ID selection
  • PLAN.md TASK-03-04 (lines 235-247) lxc-config.sh env var list does NOT include: OLLAMA_CHAT_URL, CARTESIA_VOICE_ID, DEEPGRAM_REGION, DEEPGRAM_LANGUAGE, PRAXIS_SCENARIO
  • PLAN.md TASK-06-02 (lines 453-467) install-service.sh server.env does NOT include the same vars
  • praxis .env.example (lines 21-40) documents all of these as server config

The problem: The plan's env var injection list (TASK-03-04, TASK-06-02) is incomplete. OLLAMA_CHAT_URL defaults to https://ollama.com/api/chat in code, so it may work without injection — but CARTESIA_VOICE_ID and PRAXIS_SCENARIO have defaults too. The issue is that the plan claims to wire "all praxis env vars" but the list is missing vars that .env.example documents and the code reads. If any of these need to be overridden per-deployment (e.g., a different scenario, a different voice), they can't be without editing the compose file.

Impact: Server runs with defaults (may be acceptable for pilot), but the env injection chain is incomplete vs. what the code actually reads. Inconsistency between plan claims and reality.

C-04: systemd TimeoutStartSec=300 may be insufficient for first-boot build — R-DEPLOY-02 unresolved

Axis: Feasibility / Timeline / Operability Confidence: 0.65 Evidence:

  • RESEARCH.md R-DEPLOY-02 (line 636): "systemd TimeoutStartSec applies to ExecStartPre+ExecStart combined → 300s insufficient for build+up" — confidence 0.65
  • RESEARCH.md Q8 (line 278): "the ExecStartPre=docker compose build pattern needs validation (build may exceed systemd's default timeout, may need TimeoutStartSec=300)"
  • PLAN.md D-036 (line 974): confidence 0.75, mitigation = "if insufficient, split into praxis-build.service"
  • PLAN.md TASK-06-01 (line 424): TimeoutStartSec=300
  • RESEARCH.md Q2/Q9 estimates: Docker build inside CT = npm ci (~400MB peak) + pip install (~1.2GB peak) + compose up. Estimated 3-5 min total.
  • REQ-NFR-DEPLOY-03 target: < 5 min first-boot

The problem: TimeoutStartSec=300 (5 min) is the NFR target ceiling, but it's also the timeout. If the build takes exactly 4.5 min + compose up takes 30s, the total is 5 min — right at the timeout boundary. If TimeoutStartSec applies to ExecStartPre + ExecStart combined (which systemd does in some configurations), 300s is too tight. The plan acknowledges the risk (D-036) but defers mitigation to "monitor and split if needed" — which means the first deploy may fail with a timeout, triggering rollback, and the team discovers the problem only at E2E time (SLICE-10).

Impact: First deploy may fail with systemd timeout → rollback → no working CT. Not a design flaw but an estimate risk that should be mitigated proactively, not reactively.

C-05: Health-check timeout (300s) vs first-boot build time (3-5 min) — zero margin

Axis: Feasibility / Timeline Confidence: 0.70 Evidence:

  • PLAN.md TASK-04-01 (line 333): timeout default 300s
  • RESEARCH.md Q7 (line 383): "Docker build inside CT + compose up may take 3-5 min; the default 180s timeout is insufficient. Use PRAXIS_HEALTH_TIMEOUT=300"
  • RESEARCH.md Q7 (line 390): "Total: ~3-5 min from CT start to health. 300s timeout covers this with margin" — but 3-5 min = 180-300s, so the upper bound (5 min = 300s) equals the timeout. Zero margin.
  • The build includes: apt install Docker (~90s) + git clone (~10s) + docker compose build (~120s) + compose up (~10s) = ~230s best case. But apt install can be slower on a fresh CT, pip install can spike if wheels are missing (R-DEPLOY-01), and network latency adds time.

The problem: The health-check timeout (300s) equals the worst-case estimate (5 min). There is no margin. If anything is slower than estimated (network, disk I/O, pip compilation fallback), the health-check fires before the service is up → rollback → deploy fails. The research says "covers this with margin" but 300s = 300s is zero margin.

Impact: Intermittent deploy failures under load or slow network conditions. The NFR (REQ-NFR-DEPLOY-03: < 5 min) is set at the same value as the timeout — a deployment that takes 4m59s passes the NFR but leaves 1s of health-check margin.

C-06: CT internet access is assumed but unvalidated — R-DEPLOY-03

Axis: Dependency risk / Feasibility Confidence: 0.60 Evidence:

  • RESEARCH.md R-DEPLOY-03 (line 637): "CT network can't reach Gitea or apt mirrors (coreci's original concern)" — confidence 0.60
  • RESEARCH.md Q2 (line 103): "D-028/D-029 explicitly chose apt-install-inside-CT and clone-from-Gitea, implying the CT DOES have internet in this deployment — different from coreci's original assumption"
  • coreci firstboot-hook.sh lines 9-14: "The CT's network may not route to the internet (upstream often only routes the host's IP). The PVE host has internet, so this hookscript fetches... on the host... then pushes them into the CT"
  • D-029 (PROJECT.md line 98): "CT fetches its own source + builds" — assumes CT has internet
  • D-030 (PROJECT.md line 99): "vmbr0 DHCP only" — DHCP gives an IP, but doesn't guarantee internet routing

The problem: The entire build-inside-CT approach (D-029) rests on the CT having internet access to reach Debian apt mirrors and git.cloudinit.dev. Coreci's original design explicitly assumes the opposite ("CT's network may not route to the internet") and works around it by host-fetching + pct push. Praxis reverses this assumption without validation. If the CT's vmbr0 DHCP gives an IP but no default route or no DNS resolution to external hosts, the apt install + git clone both fail. The plan's mitigation (RESEARCH.md: "fallback to host-clone + pct push") is the coreci pattern — but no task in the plan implements this fallback. It's a noted risk with no task.

Impact: If CT has no internet, the entire firstboot sequence fails at step 1 (apt install). Deploy is impossible until the network issue is resolved or the fallback is implemented.

C-07: Docker-in-LXC on ZFS rootfs storage — R-DEPLOY-04 unvalidated

Axis: Dependency risk / Feasibility Confidence: 0.55 Evidence:

  • RESEARCH.md R-DEPLOY-04 (line 638): "Docker-in-LXC on ZFS rootfs storage → overlay2 conflict" — confidence 0.50
  • RESEARCH.md Q1 (line 55): "If the PVE host uses ZFS for CT rootfs, Docker's overlay2 may have issues (ZFS CoW + overlay CoW conflict). The coreci .env shows PROXMOX_STORAGE=local which is typically directory/LVM-thin, not ZFS. Verify at deploy time"
  • PLAN.md: no task validates the storage type before deploy

The problem: If PROXMOX_STORAGE=local maps to a ZFS pool (not directory/LVM-thin), Docker's overlay2 driver may fail inside the LXC. The research says "verify at deploy time" but no plan task performs this verification. This is a 0.50 confidence risk (below the binding threshold), but it's a known unknown that could block the deploy with no mitigation task.

Impact: Potential build failure if storage is ZFS. Unlikely (coreci uses the same cluster), but unverified.

C-08: Bats test suite claims 9 unit/integration files but PLAN lists 11 test tasks

Axis: Testability / Consistency Confidence: 0.75 Evidence:

  • PLAN.md SLICE-09 (line 667): 11 tasks (TASK-09-01 through TASK-09-11)
  • PLAN.md MH-26 (line 928): "make test-proxmox-scripts passes — 9 unit/integration bats files"
  • PLAN.md Verification SLICE-09 (line 807): "9 unit/integration bats files"
  • TASK-09-10 is docker-build.bats (praxis-specific, not from coreci)
  • TASK-09-11 is test_helper.bash + Makefile (not a bats file)

The problem: The plan says "9 unit/integration bats files" but SLICE-09 has 11 tasks. TASK-09-10 (docker-build.bats) is the 10th bats file. TASK-09-11 is a helper + Makefile (not a bats file). So there are 10 bats files (9 coreci-derived + 1 docker-build), not 9. The MH-26 and verification claims of "9" are wrong.

Impact: Minor — test suite is slightly larger than documented. docker-build.bats may not be included in make test-proxmox-scripts if the target only lists 9 files.

C-09: No task implements the repo update path (code changes after first deploy)

Axis: Operability / Completeness Confidence: 0.70 Evidence:

  • RESEARCH.md Q5 open question 3 (line 648): "Repo update path: When praxis code changes, how is the CT updated? Options: (a) pct exec git pull && systemctl restart praxis, (b) --reconfigure flag, (c) separate lxc-update.sh. Not a v0.2 blocker (first deploy only) but should be designed for"
  • PLAN.md: no task creates an update/redeploy script
  • PLAN.md SLICE-07 lxc-deploy.sh has --reconfigure (re-PUTs config + restarts CT) but this re-runs the firstboot hook which checks systemctl is-active praxis → if active, skips. So --reconfigure does NOT update the code — it just restarts the CT. The code update path is undefined.

The problem: After the first successful deploy, if the praxis code changes (bug fix, v0.2.1), there's no way to update the running CT. --recreate destroys + redeploys (works but slow — full rebuild). --reconfigure restarts the CT but doesn't pull new code (the hook's idempotency check skips if praxis is active). There's no git pull && systemctl restart praxis task or script. The research flags this as "not a v0.2 blocker" but it makes the deployed system a one-shot static snapshot with no update path short of full rebuild.

Impact: No code update path without full CT destruction + rebuild. Acceptable for a pilot's first deploy, but operability gap for any post-deploy fix.

C-10: Pipecat wheel availability for cp312/linux-amd64 — R-DEPLOY-01 untested until SLICE-01

Axis: Feasibility / Dependency risk Confidence: 0.60 Evidence:

  • RESEARCH.md R-DEPLOY-01 (line 635): "Pipecat native-ext wheel missing for cp312/linux-amd64 → source compilation OOMs at 4GB" — confidence 0.70
  • RESEARCH.md Q2 (line 101): "Python 3.12 wheels exist for all pipecat-ai extras on linux/amd64 (high probability — pipecat targets CPython 3.11+ and ships manylinux wheels)"
  • PLAN.md TASK-01-01 (line 83): Dockerfile uses python:3.12-slim + pip install --no-cache-dir .
  • PLAN.md R-DEPLOY-01 mitigation (line 994): "Pre-test docker build locally (SLICE-01 verification); if compilation needed, bump to 8GB or use --only-binary :all:"

The problem: The entire build-inside-CT approach assumes all Pipecat extras (deepgram, cartesia, piper, webrtc) ship cp312 linux/amd64 wheels. If any don't (e.g., aiortc Cython extensions, sounddevice), pip falls back to source compilation which needs gcc + libasound2-dev (included in the Dockerfile) and may spike memory > 4GB (OOM at the CT's memory limit). The 4GB memory allocation may be insufficient. This is only discoverable at SLICE-01 verification time.

Impact: Build may fail if wheels are missing. Mitigation exists (bump to 8GB, --only-binary :all:) but is reactive. Caught early at SLICE-01.

C-11: scripts/ excluded in .dockerignore but install-service.sh runs from repo clone — consistent

Axis: Consistency Confidence: 0.80 Evidence:

  • PLAN.md TASK-01-02 (line 95): .dockerignore excludes scripts/
  • PLAN.md TASK-05-01 step 4 (line 369): pct exec "$vmid" -- sh -c 'cd /opt/praxis && sh scripts/install-service.sh'
  • The .dockerignore controls the Docker build context (the image won't contain scripts/). install-service.sh runs from the git clone at /opt/praxis, NOT from inside the Docker image. No conflict.

Not a bug — design is correct. The .dockerignore rationale is confusingly worded but the design is sound.

C-12: OLLAMA_BASE_URL injected but OLLAMA_CHAT_URL (a different endpoint) is not

Axis: Consistency Confidence: 0.70 Evidence:

  • PLAN.md TASK-03-04 (line 243): lxc.environment: OLLAMA_BASE_URL=https://ollama.com/v1
  • praxis server/llm/ollama_cloud.py:41: reads OLLAMA_CHAT_URL (default https://ollama.com/api/chat)
  • praxis server/pipeline.py:99: reads OLLAMA_BASE_URL (default https://ollama.com/v1)
  • PLAN.md env var lists do NOT include OLLAMA_CHAT_URL

The problem: The server has TWO Ollama env vars: OLLAMA_BASE_URL (OpenAI-compatible Pipecat path) and OLLAMA_CHAT_URL (direct chat API). The plan injects OLLAMA_BASE_URL but not OLLAMA_CHAT_URL. Code defaults work, but the injection list is incomplete.

C-13: No rollback verification for the Docker volume — data loss on rollback

Axis: Operability Confidence: 0.65 Evidence:

  • rollback.sh destroys the CT (DELETE /nodes/{node}/lxc/{vmid}), which destroys the CT's rootfs including Docker volumes.
  • PLAN.md MH-06: "SQLite persists across docker compose restart" — restart ≠ recreate ≠ CT destruction

The problem: The Docker named volume praxis-db lives inside the CT's Docker daemon. When rollback.sh destroys the CT, all Docker volumes are destroyed with it. No volume backup/export step exists in rollback. Data loss on rollback.

Impact: Acceptable for pilot (no real users yet), but should be documented.

C-14: E2E test (SLICE-10) against live cluster — autonomy boundary unclear

Axis: Testability / Operability Confidence: 0.60 Evidence:

  • PLAN.md TASK-10-01: "Requires PROXMOX_* + GITEA_TOKEN + DEEPGRAM_API_KEY env vars"
  • config.json: escalate_external_integration: true — but E2E is the project's own deployment target

The problem: The E2E test creates a real CT on the live cluster, deploys, verifies, and destroys. At full autonomy, this runs without human approval. If the test fails mid-way, a zombie CT may be left. The autonomy/escalation boundary for live-cluster E2E is unclear.

C-15: Dockerfile pip install . runs before source is copied — build will fail

Axis: Feasibility / Consistency Confidence: 0.75 Evidence:

  • PLAN.md TASK-01-01 (line 83): COPY pyproject.toml, RUN pip install --no-cache-dir ., then COPY server/ scenarios/ db/
  • pip install . installs the PROJECT package, which requires source directories (server/, db/, scenarios/) to exist
  • pyproject.toml line 9: readme = "README.md" — README.md is not copied in the Dockerfile spec
  • RESEARCH.md Q4 (line 183): same ordering issue

The problem: The Dockerfile copies pyproject.toml then runs pip install . BEFORE copying server/, scenarios/, db/. With only pyproject.toml present, pip install . will fail because the packages to install don't exist yet. The standard dep-caching pattern requires either installing deps separately or copying source before project install.

Impact: Docker build fails at the pip install . step. Spec error in the plan.


Binding Decisions

G-101: GITEA_TOKEN secret injection chain is broken — MUST fix before execute

  • Challenge: C-01
  • Axis: Feasibility / Dependency risk / Security
  • Confidence: 0.85
  • Verdict: MUST (blocks ship)
  • Rationale: The firstboot hookscript runs on the PVE host, but GITEA_TOKEN is injected via lxc.environment into the CT, not the host. The hook's git clone will fail with auth error every time. Coreci's own design acknowledges this ("stage a version of this snippet with the secrets baked in"). The plan's stage-snippet.sh fetches a raw file without baking secrets. Additionally, pct exec does not reliably inherit lxc.environment vars in the CT's exec'd process.
  • Action: Choose one of:
    1. (Recommended) Bake GITEA_TOKEN into the snippet at staging time: Modify stage-snippet.sh to fetch the hookscript template, sed/envsubst the GITEA_TOKEN into it, then upload the rendered snippet. This matches coreci's documented approach. The token is in the snippet file (stored in Proxmox snippet storage, not git). Minimal change.
    2. Host-side git clone + pct push: Clone the repo on the PVE host (where GITEA_TOKEN can be exported by lxc-deploy.sh), then pct push the tarball into the CT. This is coreci's original pattern. Reverts D-029's "clone inside CT" but is proven.
    3. Pass GITEA_TOKEN via pct exec explicitly: pct exec "$vmid" -- sh -c 'GITEA_TOKEN='"$GITEA_TOKEN"' git clone ...' — requires GITEA_TOKEN in the host env (the hookscript env), which still has the "lxc.environment doesn't reach the host" problem. Doesn't work without baking.
    • Option 1 is the minimal change. Update TASK-03-06 (stage-snippet.sh) to render the snippet with GITEA_TOKEN baked in. Update TASK-05-01 to use the baked-in token. Update RESEARCH.md Q5/Q6.

G-102: PRAXIS_DB_PATH is never read by the server — MUST fix the code

  • Challenge: C-02
  • Axis: Feasibility / Operability / Completeness
  • Confidence: 0.90
  • Verdict: MUST (blocks ship)
  • Rationale: The plan sets PRAXIS_DB_PATH=/app/data/praxis.db in 3 places and claims SQLite persistence via Docker volume (MH-06). But db/store.py and db/migrate.py hardcode _DEFAULT_DB_PATH = "praxis.db" with no env read. The server writes to /app/praxis.db (container writable layer), NOT the volume at /app/data/praxis.db. Data is lost on container recreation. MH-06 will fail.
  • Action: Add PRAXIS_DB_PATH env var reading to db/store.py and db/migrate.py:
    _DEFAULT_DB_PATH = os.environ.get("PRAXIS_DB_PATH", "praxis.db")
    
    2-line code change in 2 files. Add as a new task in SLICE-01 or SLICE-02 (data-engineer / backend-engineer territory). Also verify PraxisStore is instantiated in the pipeline (if not, recorder is dead code — v0.1 gap, but env var fix is still needed).

G-103: Incomplete env var injection list — FIX before execute

  • Challenge: C-03, C-12
  • Axis: Consistency / Completeness
  • Confidence: 0.85
  • Verdict: FIX (must address before execute)
  • Rationale: The plan's env var injection list (TASK-03-04, TASK-06-02) is missing OLLAMA_CHAT_URL, CARTESIA_VOICE_ID, DEEPGRAM_REGION, DEEPGRAM_LANGUAGE, PRAXIS_SCENARIO — all of which the server reads from env. Defaults exist, but the plan claims to wire "all praxis env vars" and the list is incomplete.
  • Action: Add the missing env vars to both TASK-03-04 (lxc-config.sh lxc.environment lines) and TASK-06-02 (install-service.sh server.env heredoc):
    • OLLAMA_CHAT_URL=https://ollama.com/api/chat
    • CARTESIA_VOICE_ID=a3536a36-1d18-4efb-a95a-7e44b7b5e384
    • DEEPGRAM_LANGUAGE=en
    • DEEPGRAM_REGION=na
    • PRAXIS_SCENARIO=customer_service_refund_ca_v01

G-104: Health-check timeout has zero margin — FIX by bumping to 600s

  • Challenge: C-04, C-05
  • Axis: Feasibility / Timeline
  • Confidence: 0.70
  • Verdict: FIX (must address before execute)
  • Rationale: PRAXIS_HEALTH_TIMEOUT=300 (5 min) equals the worst-case build estimate (5 min). Zero margin. Any slowdown causes timeout → rollback → deploy failure. The NFR target (< 5 min) is a measurement, not a timeout — the timeout should be 2x the target.
  • Action: Bump PRAXIS_HEALTH_TIMEOUT default to 600 (10 min) in TASK-04-01 (health-check.sh) and TASK-08-02 (.env.example). Bump TimeoutStartSec in praxis.service (TASK-06-01) to 600 to match (addresses C-04). NFR target stays at < 5 min (measured by timing wrappers).

G-105: Dockerfile pip install ordering is broken — FIX before execute

  • Challenge: C-15
  • Axis: Feasibility / Consistency
  • Confidence: 0.75
  • Verdict: FIX (must address before execute)
  • Rationale: The Dockerfile spec copies pyproject.toml then runs pip install --no-cache-dir . BEFORE copying server/, scenarios/, db/. pip install . installs the project package, which requires source directories. With only pyproject.toml present, the install fails. Also README.md (referenced by pyproject.toml) is not copied.
  • Action: Fix the Dockerfile in TASK-01-01 to copy source before pip install ., OR split into dep install + project install. Add README.md to the COPY list. Example fix:
    COPY pyproject.toml README.md ./
    COPY server/ ./server/
    COPY scenarios/ ./scenarios/
    COPY db/ ./db/
    RUN pip install --no-cache-dir .
    COPY --from=client-builder /app/client/dist ./client/dist
    

G-106: Bats test count mismatch (9 vs 10) — FIX the count

  • Challenge: C-08
  • Axis: Testability / Consistency
  • Confidence: 0.75
  • Verdict: FIX (must address before execute)
  • Rationale: MH-26 and SLICE-09 verification claim "9 unit/integration bats files" but there are 10 (TASK-09-01 through TASK-09-10 are .bats files; TASK-09-11 is a helper + Makefile). The Makefile target must include docker-build.bats.
  • Action: Update MH-26 and SLICE-09 verification to "10 unit/integration bats files." Ensure the Makefile target in TASK-09-11 includes docker-build.bats.

G-107: No repo update path after first deploy — ACCEPT for v0.2

  • Challenge: C-09
  • Axis: Operability / Completeness
  • Confidence: 0.70
  • Verdict: ACCEPT (acknowledged, no action)
  • Rationale: No git pull && systemctl restart path for code updates. --reconfigure restarts but doesn't pull. --recreate works (full rebuild) but is slow. Research flags as "not a v0.2 blocker." For a pilot's first deploy, acceptable.
  • Action: None for v0.2. Document as known limitation: "No in-place code update path; use --recreate for code changes."

G-108: CT internet access unvalidated (R-DEPLOY-03) — ACCEPT with deploy-time check

  • Challenge: C-06
  • Axis: Dependency risk / Feasibility
  • Confidence: 0.60
  • Verdict: ACCEPT (acknowledged, verify at E2E)
  • Rationale: Build-inside-CT assumes internet access. Coreci assumed the opposite. At 0.60 confidence, at the binding threshold. E2E test (SLICE-10) will discover this immediately — no silent failure.
  • Action: No plan change. Add note to SLICE-10: "If firstboot fails at apt install, check CT internet routing. Fallback: host-clone + pct push (D-025 hybrid)."

G-109: Docker volume data loss on rollback — ACCEPT for pilot

  • Challenge: C-13
  • Axis: Operability
  • Confidence: 0.65
  • Verdict: ACCEPT (acknowledged, no action)
  • Rationale: Docker volume destroyed with CT on rollback. Acceptable for pilot (no persistent user data). Should be documented.
  • Action: Add note to executor notes: "Rollback destroys CT including Docker volumes — all SQLite data lost. Acceptable for pilot."

G-110: E2E against live cluster — ACCEPT

  • Challenge: C-14
  • Axis: Testability / Operability
  • Confidence: 0.60
  • Verdict: ACCEPT (acknowledged, no action)
  • Rationale: E2E runs against live Proxmox at full autonomy. Gated by PROXMOX_API_URL (skips if absent). This is the project's own deployment target, not a third-party integration. Consistent with full autonomy.
  • Action: None. The E2E skip condition handles the no-secrets case.

G-111: Pipecat wheel risk (R-DEPLOY-01) — ACCEPT with early detection

  • Challenge: C-10
  • Axis: Feasibility / Dependency risk
  • Confidence: 0.60
  • Verdict: ACCEPT (early detection at SLICE-01)
  • Rationale: If wheels missing, Docker build fails at SLICE-01 (first task, earliest detection). Mitigation documented (bump to 8GB, --only-binary :all:). No silent failure.
  • Action: None. Executor runs docker build locally first.

G-112: ZFS storage risk (R-DEPLOY-04) — ACCEPT (below threshold)

  • Challenge: C-07
  • Axis: Dependency risk
  • Confidence: 0.55
  • Verdict: ACCEPT (below binding threshold)
  • Rationale: At 0.55, below 0.60 threshold. Coreci uses same cluster/storage and works. E2E catches it if it manifests.
  • Action: None. Informational only.

G-113: .dockerignore scripts/ exclusion is correct — ACCEPT

  • Challenge: C-11
  • Axis: Consistency
  • Confidence: 0.80
  • Verdict: ACCEPT (no action)
  • Rationale: .dockerignore excludes scripts/ from the Docker image. install-service.sh runs from the repo clone at /opt/praxis, not from the container. Design is correct.
  • Action: None. Optionally clarify TASK-01-02 rationale.

Escalations

None. All 15 challenges are resolved with confidence >= 0.60 (13 binding decisions) or explicitly accepted at full autonomy. No challenge requires human input.


Summary

Overall assessment: APPROVE_WITH_NOTES

The v0.2 plan is fundamentally sound — it reuses a battle-tested deployment toolkit (coreci), adapts it with well-researched parameters (4GB/16GB CT sizing, /health:8789 endpoint), and covers all 20 REQ-IDs across 10 coherent slices. The research is thorough (10 questions, 6 risks). The architecture is well-documented. The persona allocation is reasonable.

However, the grill found 2 MUST-fix blockers and 4 FIX-before-execute issues:

  1. G-101 (MUST): GITEA_TOKEN secret injection chain is broken — hookscript runs on PVE host but token is in CT env. Every deploy fails at git clone. Fix: bake token into snippet at staging time.
  2. G-102 (MUST): PRAXIS_DB_PATH is never read by server code — Docker volume mount is a no-op, data lost on container recreation. MH-06 fails. Fix: 2-line code change in db/store.py + db/migrate.py.
  3. G-103 (FIX): Env var injection list missing 5 vars the server reads.
  4. G-104 (FIX): Health-check timeout (300s) = worst-case build (5 min) = zero margin. Bump to 600s.
  5. G-105 (FIX): Dockerfile pip install . runs before source copied — build fails. Fix copy ordering.
  6. G-106 (FIX): Bats test count is 10, not 9 — MH-26 and Makefile need updating.

The remaining 7 challenges (G-107 through G-113) are accepted — known risks with mitigations or pilot-acceptable limitations.

Verdict: The plan CANNOT ship as-is. G-101 and G-102 are ship blockers. G-103 through G-106 must be fixed before execute. With these 6 fixes applied, the plan is sound and should proceed.

Metric Count
Total challenges 15
Binding decisions 13
MUST (blocks ship) 2 (G-101, G-102)
FIX (before execute) 4 (G-103, G-104, G-105, G-106)
ACCEPT (no action) 7 (G-107 through G-113)
Escalations 0
Overall APPROVE_WITH_NOTES — proceed after MUST/FIX addressed

Per-Axis Scorecard

Axis Score Notes
1. Feasibility ⚠️ 2 blockers (G-101 secret chain, G-102 DB path) + Dockerfile ordering (G-105). Fixable.
2. Scope 20 REQ-IDs, all mapped. Scope is tight (infra-only). Frontend deactivation justified.
3. Cost/effort Reusing coreci verbatim where possible. 34 tasks proportional to a deploy milestone.
4. Dependency risk ⚠️ CT internet unvalidated (G-108), Pipecat wheel risk (G-111), ZFS risk (G-112). All have early-detection gates.
5. Security ⚠️ Secret chain broken (G-101). .gitignore coverage correct. Secrets never committed.
6. Operability ⚠️ No update path (G-107, accepted). Data loss on rollback (G-109, accepted). Timeout zero margin (G-104, fix).
7. Testability Bats suite mirrors coreci (10 files). E2E with skip condition. Count mismatch (G-106, fix).
8. Consistency ⚠️ Env var list incomplete (G-103). Test count wrong (G-106). Dockerfile spec error (G-105).
9. Completeness ⚠️ Missing env vars (G-103). Missing DB path wiring (G-102). No update script (G-107, accepted). REQ coverage 20/20.

End of v0.2 grill report. Verdict: APPROVE_WITH_NOTES. 13 binding decisions (G-101..G-113), 0 escalations. Escalations visible via ciagent audit. This grill surfaces findings; it does not rewrite PROJECT.md, ROADMAP.md, or REQUIREMENTS.md. Binding decisions that warrant spec changes must be promoted explicitly by the user (e.g., via ciagent-clarify or a follow-up CLARIFY stage).