From 95b380f5e0d61f9deed2e77bf3971bf1f20b2001 Mon Sep 17 00:00:00 2001 From: leubeem Date: Mon, 15 Jun 2026 14:46:57 +0200 Subject: [PATCH] docs: align CLAUDE.md/AGENTS.md/README with the two-repo architecture; drop dead LLM routing MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The LLM decision routing moved to the external Python backend (OpenSquawk-LiveATC-api), but the core docs still described the old in-Nuxt path (routeDecision(), /api/llm/decide, a Nuxt /api/decision-flows/runtime route) — none of which exist. Every agent session started with a wrong mental model. - Rewrite CLAUDE.md to describe the real /pm flow (useRadioBackend -> Python backend owns authoritative state; Nuxt owns STT/TTS/audio/auth/editor) and fix stale commands (bun -> yarn). - Fix the two stale AGENTS.md lines (openai.ts is getOpenAIClient() only; decisionFlowService is editor-only, no Nuxt runtime route). - README: note the Python backend is required for /pm; correct server/ desc. - Remove dead shared/utils/openaiDecision.ts (called the non-existent /api/llm/decide) and the now-orphaned LLM decision contract types in shared/types/llm.ts. Trace types used by pm.vue are kept. Co-Authored-By: Claude Opus 4.8 --- AGENTS.md | 4 +-- CLAUDE.md | 63 ++++++++++++++++++++++++++-------- README.md | 3 +- shared/types/llm.ts | 36 ------------------- shared/utils/openaiDecision.ts | 10 ------ 5 files changed, 52 insertions(+), 64 deletions(-) delete mode 100644 shared/utils/openaiDecision.ts diff --git a/AGENTS.md b/AGENTS.md index ce1e4dc..9181129 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -10,8 +10,8 @@ ## Key Files - `/shared/utils/communicationsEngine.ts` — Core state machine composable (used by `/pm` live ATC). Drives local cursor and TTS; Python backend owns the authoritative state. - `/app/composables/useRadioBackend.ts` — Typed wrapper around the Python backend REST API (`createSession`, `transmit`, `deleteSession`, `fetchFlows`) -- `/server/utils/openai.ts` — Legacy LLM decision router (`routeDecision()`). No longer called by `/pm`; may still be used by other routes. -- `/server/services/decisionFlowService.ts` — Builds runtime decision trees from MongoDB (used by Nuxt `/api/decision-flows/runtime`; `/pm` now fetches directly from the Python backend) +- `/server/utils/openai.ts` — `getOpenAIClient()` only (TTS via `/api/atc/say`, STT via `/api/atc/ptt`). The old `routeDecision()` LLM router has been removed; routing lives in the Python backend. +- `/server/services/decisionFlowService.ts` — Builds/edits MongoDB-backed decision trees for the flow editor (`/server/api/editor/flows*`). There is no Nuxt `/api/decision-flows/runtime` route — `/pm` fetches the runtime flow directly from the Python backend. - `/app/pages/pm.vue` — Live ATC page (speech-to-text, PTT, text input) - `/app/pages/classroom.vue` — Classroom learning mode (separate system, does NOT use communicationsEngine) diff --git a/CLAUDE.md b/CLAUDE.md index 233beab..e02e9c4 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -5,26 +5,59 @@ - **H3 server** handlers in `/server` - **Shared types/utils** in `/shared` - MongoDB models in `/server/models` +- **Python backend** — a separate repo, `OpenSquawk-LiveATC-api`, owns the authoritative + Live ATC (`/pm`) session state and routing decisions. It runs as its own service + (default `http://127.0.0.1:8000`). This Nuxt app owns STT, TTS, client audio, auth, + the flow editor, and account/admin features. + +> The LLM decision routing that used to live in this repo (`routeDecision()`, +> `/api/llm/decide`, a Nuxt `/api/decision-flows/runtime` route) has been removed. +> Decision routing now happens in the Python backend. Don't reintroduce it here. ## Key Files -- `/shared/utils/communicationsEngine.ts` — Core state machine composable (used by `/pm` live ATC) -- `/server/utils/openai.ts` — LLM decision router (`routeDecision()`) -- `/server/services/decisionFlowService.ts` — Builds runtime decision trees from MongoDB -- `/app/pages/pm.vue` — Live ATC page (speech-to-text, PTT, text input) -- `/app/pages/classroom.vue` — Classroom learning mode (separate system, does NOT use communicationsEngine) +- `/app/composables/useRadioBackend.ts` — Typed client for the Python backend REST API + (`createSession`, `transmit`, `deleteSession`, `fetchFlows`). This is how `/pm` talks + to the decision engine. +- `/shared/utils/communicationsEngine.ts` — Local state-machine composable used by `/pm`. + It mirrors the flow for cursor tracking and TTS scheduling; the **Python backend owns the + authoritative state**. `fetchRuntimeTree()` loads the flow definition from the backend's + `/api/decision-flows/runtime`. +- `/server/services/decisionFlowService.ts` — Builds/edits MongoDB-backed decision trees. + Used **only by the flow editor** (`/server/api/editor/flows*`), not by `/pm`. +- `/server/utils/openai.ts` — `getOpenAIClient()` only. Used for TTS (`/api/atc/say`) and + STT (`/api/atc/ptt`). It is **not** a decision router anymore. +- `/app/pages/pm.vue` — Live ATC page (speech-to-text, PTT, text input). +- `/app/pages/classroom.vue` — Classroom learning mode (separate system, does NOT use + communicationsEngine). ## Live ATC Flow (/pm) -1. User inputs (PTT or text) → `handlePilotTransmission()` -2. `processPilotTransmission()` logs the pilot message -3. `buildLLMContext()` builds candidates from `nextCandidates` -4. POST `/api/llm/decide` → `routeDecision()` selects next state -5. `applyLLMDecision()` moves to next state, updates vars/flags -6. `collectAtcStatesUntilPilotTurn()` advances through ATC/system states -7. Each ATC `say_tpl` is spoken via TTS (`scheduleControllerSpeech`) +1. `startMonitoring()` → `fetchRuntimeTree('icao_atc_decision_tree', radioBackendUrl)` + loads the flow definition from the Python backend into the local engine (cursor / TTS). +2. `startMonitoring()` → `radioBackend.createSession(...)` creates the authoritative + server-side session and stores `backendSessionId`. +3. User input (PTT or text) → `handlePilotTransmission()`. + PTT audio is transcribed first via Nuxt `POST /api/atc/ptt` (Whisper). +4. `radioBackend.transmit(backendSessionId, transcript)` → the Python backend runs regex + routing, readback evaluation, side effects, and flow chaining; it returns + `next_state_id`, `controller_say_template`, `auto_advanced_states`, `flags`, + and `session_complete`. +5. `moveToSilent(stateId)` is called for each auto-advanced state and the final state — + advances the local cursor without re-triggering auto-transitions. +6. `scheduleControllerSpeech(...)` speaks the ATC reply via TTS (`/api/atc/say`). ## Decision Tree States -States have `role: 'pilot' | 'atc' | 'system'`. ATC states have `say_tpl` (what controller says). Pilot states have `utterance_tpl` (expected pilot response). Transitions: `next`, `ok_next`, `bad_next`, `timer_next`. +States have `role: 'pilot' | 'atc' | 'system'`. The local engine handles two template +schemas transparently (via `stateSayTpl()` / `stateUtteranceTpl()`): +- Legacy (MongoDB editor): `say_tpl`, `utterance_tpl`, vars as `{var}` +- Python backend YAML: `say_template`, `expected_pilot_template`, vars as `{{var}}` + +Both `{{var}}` and `{var}` are rendered by `renderTpl()`. Transition fields seen across +schemas: `next`, `ok_next`, `bad_next`, `timer_next`, `auto_transitions`. + +## Environment Variables +- `NUXT_PUBLIC_RADIO_BACKEND_URL` — URL of the Python backend (default `http://127.0.0.1:8000`) ## Commands -- `bun run dev` — dev server -- Decision trees are stored in MongoDB and fetched via `/api/decision-flows/runtime` +- `yarn dev` — dev server (Nuxt) +- `yarn test` — runs the `tsx --test` suite (`tests/`, `server/`, `shared/`) +- Python backend: see `OpenSquawk-LiveATC-api/README.md` diff --git a/README.md b/README.md index 3f0759a..ec61743 100644 --- a/README.md +++ b/README.md @@ -28,6 +28,7 @@ Goal: a community-driven alternative to other costly AI-ATC solutions while bein | MongoDB 7+ | Local instance or hosted database. Configure via `MONGODB_URI`. | | ffmpeg | Needed for audio processing (`fluent-ffmpeg`), must be on the system PATH. | | OpenAI API Key | For TTS and LLM calls (`OPENAI_API_KEY`). | +| Live ATC backend | The `/pm` Live ATC mode requires the Python decision backend ([OpenSquawk-LiveATC-api](https://github.com/OpenSquawk/OpenSquawk-LiveATC-api)). Point `NUXT_PUBLIC_RADIO_BACKEND_URL` at it (default `http://127.0.0.1:8000`). Other features run without it. | | Optional: Piper TTS | For local speech (`pip install "piper-tts[http]"`). | You can connect to any OpenAI-compatible (self-hosted or third-party) API by setting `OPENAI_BASE_URL` to the service's base URL. @@ -107,7 +108,7 @@ Local models via WSL+Docker is planned but not yet implemented. Placeholders and ``` app/ # Nuxt frontend (pages, components, stores) -server/ # API routes, services, utilities (ATC, Auth, LLM) +server/ # API routes, services, utilities (ATC TTS/STT, Auth, flow editor) shared/ # Shared types and helpers content/ # Markdown news & CMS content ``` diff --git a/shared/types/llm.ts b/shared/types/llm.ts index 43f940c..e3f9b92 100644 --- a/shared/types/llm.ts +++ b/shared/types/llm.ts @@ -1,15 +1,5 @@ import type { DecisionNodeCondition, DecisionNodeTrigger } from './decision' -export interface LLMDecisionInput { - state_id: string - state: any - candidates: Array<{ id: string; state: any; flow?: string }> - variables: Record - flags: Record - pilot_utterance: string - flow_slug?: string -} - export type FlowActivationMode = 'main' | 'parallel' | 'linear' export interface FlowActivationInstruction { @@ -17,14 +7,6 @@ export interface FlowActivationInstruction { mode?: FlowActivationMode } -export interface ActiveNodeSummary { - flow: string - state: string - role?: string - say_tpl?: string - controller_say_tpl?: string -} - export interface CandidateTraceEntry { id: string flow: string @@ -81,17 +63,6 @@ export interface DecisionCandidateTimeline { autoSelected?: CandidateTraceEntry | null } -export interface LLMDecision { - next_state: string - updates?: Record - flags?: Record - controller_say_tpl?: string - off_schema?: boolean - radio_check?: boolean - activate_flow?: string | FlowActivationInstruction - resume_previous?: boolean -} - export interface LLMDecisionTraceCall { stage: 'readback-check' | 'decision' request: Record @@ -116,10 +87,3 @@ export interface LLMDecisionTrace { reason?: string } } - -export interface LLMDecisionResult { - decision: LLMDecision - trace?: LLMDecisionTrace - active_nodes?: ActiveNodeSummary[] - pilot_intent?: string | null -} diff --git a/shared/utils/openaiDecision.ts b/shared/utils/openaiDecision.ts deleted file mode 100644 index e36f857..0000000 --- a/shared/utils/openaiDecision.ts +++ /dev/null @@ -1,10 +0,0 @@ -// utils/openaiDecision.ts -import type { LLMDecisionInput, LLMDecisionResult } from '../types/llm' - -/** Client-seitig: ruft den Backend-Endpunkt auf */ -export async function decideNextStateLLM(input: LLMDecisionInput): Promise { - return await $fetch('/api/llm/decide', { - method: 'POST', - body: input - }) -}