docs: align CLAUDE.md/AGENTS.md/README with the two-repo architecture; drop dead LLM routing

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 <noreply@anthropic.com>
This commit is contained in:
leubeem
2026-06-15 14:46:57 +02:00
parent 6d8a7b8fc0
commit 95b380f5e0
5 changed files with 52 additions and 64 deletions

View File

@@ -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)

View File

@@ -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`

View File

@@ -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
```

View File

@@ -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<string, any>
flags: Record<string, any>
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<string, any>
flags?: Record<string, any>
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<string, any>
@@ -116,10 +87,3 @@ export interface LLMDecisionTrace {
reason?: string
}
}
export interface LLMDecisionResult {
decision: LLMDecision
trace?: LLMDecisionTrace
active_nodes?: ActiveNodeSummary[]
pilot_intent?: string | null
}

View File

@@ -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<LLMDecisionResult> {
return await $fetch<LLMDecisionResult>('/api/llm/decide', {
method: 'POST',
body: input
})
}