Space Channel Subflow Architecture
A skills-style design for keeping the Space Channel phone agent small, discoverable, and testable as the public Space Channel catalog grows.
Why change it
The latest phone call sounded clearer and the measured tool waits were short. The remaining risk was routing hygiene: after the static menu, the durable route slot can still be suggestions while the caller is giving a fresh intent such as solar weather.
That is a structural problem. The current guards read the old route slot before they inspect the newest user text. A better router needs a single-use route_candidate: propose, dispatch, clear.
Shape
flows/
space_channel_network_ops.yaml
subflows/
space_channel_network_ops/
manifest.yaml
launch_status.yaml
content_briefing.yaml
bug_intake.yaml
menu_choice.yaml
console_not_ready.yaml
Greeting, identity, global policy, high-level dispatch, fallback, and call closure.
Space Channel jobs: launches, content briefing, bug intake, menu repair, not-ready consoles.
Subflow chips and inspector rows keep imported state ownership visible.
Implemented Runtime
The parent YAML now keeps only greeting and triage. Everything else is stitched in through imports:.
imports:
- subflows/space_channel_network_ops/menu_choice
- subflows/space_channel_network_ops/launch_status
- subflows/space_channel_network_ops/content_briefing
- subflows/space_channel_network_ops/console_not_ready
- subflows/space_channel_network_ops/bug_intake
Each module declares a subflow: header. The loader copies that header to every imported state as subflow_id, subflow_title, and subflow_source. State IDs stay unchanged, so logs and replays still align with the previous graph.
Viewer Contract
The state-flow viewer still receives one graph, but imported states now show an ownership chip and the inspector includes a Subflow row.
{
"id": "fetch_launches",
"subflow_id": "launch_status",
"subflow_title": "Launch Status",
"subflow_source": "flows/subflows/space_channel_network_ops/launch_status.yaml"
}
The same fields are published through /api/flows/<id>, /api/flows/<id>/states, and path-explorer graph.json. This keeps UI screenshots, timeline logs, and offline graph exports reconcilable.
Manifest Cards
The router should inspect small cards first, not entire YAML graphs.
cards:
- id: launch_status
triggers: [launch, rocket, countdown, mission]
data_sources:
- https://www.spacechannel.com/data/launches.json
outputs: [space_launch_summary, space_launch_count]
terminal: true
first_audio_state: report_launches_audio
module_path: flows/subflows/space_channel_network_ops/launch_status.yaml
- id: content_briefing
triggers: [solar weather, space news, ufo, uap, radio, what is on]
data_sources:
- https://www.spacechannel.com/
outputs: [space_content_topic, space_content_summary]
terminal: true
first_audio_state: report_space_content_audio
module_path: flows/subflows/space_channel_network_ops/content_briefing.yaml
Route Contract
This is the next router cleanup. The shipped modules still use route, with targeted safeguards: fresh launch/content requests can override a stale bug_report route, and bug-report language about live briefings no longer becomes a content briefing unless the caller explicitly asks for one. The follow-up is a single-use dispatch signal.
route_candidate: solar_weather
route_confidence: high
active_subflow: content_briefing
parent_return_state: triage
last_committed_route: solar_weather
Dispatch guards should read route_candidate, commit active_subflow, write last_committed_route, then clear route_candidate. Clearing at state entry is too late if a guard can fire before entry hooks run.
Preflow Cache
The Space Channel flow now has a preflow job that runs before callers arrive. It refreshes the homepage-derived content snapshot, refreshes Launch Command JSON, builds the launch/content/static audio text set, warms web/assets/static_audio/, and writes data/space_channel/preflow_manifest.json.
.venv/bin/python -m riff.space_channel_preflow \
--flow space_channel_network_ops
Runtime then reads fresh preflow launches before falling back to live fetch, rebuilds old content snapshots through schema space_channel_content_v3, and checks static audio by fully rendered text before doing call-time droid synthesis. The v3 snapshot keeps the homepage widget catalog plus optional phone-safe payloads for recent space headlines, NOAA SWPC space weather, Space Channel UFO files, and the Space Channel DSN snapshot.
tmpdir=$(mktemp -d /tmp/riff-space-preflow.XXXXXX)
.venv/bin/python -m riff.space_channel_preflow \
--flow space_channel_network_ops \
--content-cache "$tmpdir/content.json" \
--launch-cache "$tmpdir/launches.json" \
--manifest "$tmpdir/preflow.json" \
--static-audio-base "$tmpdir/static_audio" \
--no-audio
A verified dry run on 2026-06-30 found 7 content topics, 3 launch records, and 13 planned audio items.
Space Channel Data
Research on 2026-06-29 used the public Space Channel homepage, Launch Command JSON, UFO cases, UFO wire, DSN snapshot, Spaceflight News API headlines, and NOAA SWPC space-weather products.
| Family | Examples from Space Channel | Subflow |
|---|---|---|
| Launches | Launch Command, Upcoming Launches | launch_status |
| Briefings | Daily Briefing, Solar Operations, Cosmic Weather, Webb Observatory, ISS Operations, Mars Operations | content_briefing |
| UFO/UAP | Anomaly Division, Intelligence Feed | content_briefing |
| Audio/status | Music, Deep Space Network, Sky Monitor, Orbital Traffic | content_briefing |
| Catalog summaries | NetTrek, Object Atlas, Mission Supply, Sector Analysis, Exoplanet Catalog | content_briefing |
The launch feed returned 20 launch records in the sampled fetch. The first records included SpaceX Sirius SXM-11, a South Korean ADD solid-fuel demo flight, Pegasus XL Swift Boost, Rocket Lab's Electron iQPS launch, and Long March 4C.
Customer Flow View
For a Space Channel customer review, start the RIFF web server:
.venv/bin/python3 -m riff.web_server
Then open http://127.0.0.1:8765/voice.html?flow=space_channel_network_ops. The customer sees one state graph with subflow chips for Menu Choice, Launch Status, Content Briefing, Console Not Ready, and Bug Intake. Clicking a node shows the state metadata, required tool, slots, and imported source file.
Raw metadata is available at /api/flows/space_channel_network_ops and /api/flows/space_channel_network_ops/states. Topic-specific choices such as space_news, solar_weather, ufo_files, radio, deep_space_network, and mission_ops are data-backed branches inside content_briefing; logs expose the selected space_content_topic slot.
Branch Count
Do not create 21 top-level routes. Collapse the catalog into about seven branch families:
launch_statuscontent_briefingbug_intakemenu_choiceconsole_not_readyfallback_repairglobal_handoff
A/B/C Plan
| Arm | Change | What it tests |
|---|---|---|
| Original | Current flat flow and sticky route | Baseline behavior |
| A | Current flow plus route_candidate | Smallest stale-route fix |
| B | Parent shell plus scoped subflows | Scalability and ownership |
| C | Direct topic router for capability questions | Shorter menu and fewer static turns |
Simulator Gate
Before any OTA call, run the in-process simulator tests and a fast flow-eval smoke:
.venv/bin/pytest tests/test_space_channel_content.py \
tests/test_space_channel_tools.py \
tests/test_space_channel_flow.py \
tests/test_space_channel_subflows.py -q
.venv/bin/pytest \
tests/test_space_channel_preflow.py \
tests/static_audio/test_manifest.py \
tests/test_live_session.py::test_required_tool_act_transition_short_circuits_to_static_report \
tests/phone/test_telnyx_transport.py::test_space_channel_launch_phone_sim_short_circuits_and_blocks_stale_audio -q
.venv/bin/python -m riff.flow_eval \
--flows space_channel_network_ops \
--personas cooperative,terse \
--repeat 1 \
--no-judge \
--json
The minimum scenario pack is direct launches, direct solar weather, capabilities then solar weather, capabilities then bug report, mixed launch plus discrepancy, repeated capabilities, unknown widget, and partial ASR such as "weather".
Status
Verification evidence: 59 focused tests passed for Space Channel subflows, preflow, static audio, and phone short-circuit behavior. Preflow dry run succeeded against temporary paths. LLM-as-judge runs completed without eval errors and trace lint stayed clean: 0 double beats, 0 collect loops, 0 stale asks, and 0 raw repr leaks.
agentapi was present but blocked because no active ANTIGRAVITY_LS_ADDRESS was available; a command-line codex exec -o docs pass was used as the CLI fallback.