Overview › Design › Space Channel subflows

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.

The organizing rule The parent flow owns the caller relationship. Parent-scoped subflows own bounded jobs. RIFF imports those modules at flow-load time and still runs one flat, declared FSM.

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
Parent
Shell

Greeting, identity, global policy, high-level dispatch, fallback, and call closure.

Scoped
Subflows

Space Channel jobs: launches, content briefing, bug intake, menu repair, not-ready consoles.

Viewer
Provenance

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.

FamilyExamples from Space ChannelSubflow
LaunchesLaunch Command, Upcoming Launcheslaunch_status
BriefingsDaily Briefing, Solar Operations, Cosmic Weather, Webb Observatory, ISS Operations, Mars Operationscontent_briefing
UFO/UAPAnomaly Division, Intelligence Feedcontent_briefing
Audio/statusMusic, Deep Space Network, Sky Monitor, Orbital Trafficcontent_briefing
Catalog summariesNetTrek, Object Atlas, Mission Supply, Sector Analysis, Exoplanet Catalogcontent_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

ArmChangeWhat it tests
OriginalCurrent flat flow and sticky routeBaseline behavior
ACurrent flow plus route_candidateSmallest stale-route fix
BParent shell plus scoped subflowsScalability and ownership
CDirect topic router for capability questionsShorter 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

Implemented Parent-scoped subflows, viewer/API provenance, required-tool short-circuiting, Space Channel preflow JSON/audio cache warming, rendered-text static-audio lookup, and mixed-intent route safeguards are in place.

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.

Remaining design gap Multi-intent calls still need a parent follow-up or intent queue. Current subflows pick one branch and close, so a caller asking to log a bug and also hear launch/weather information can lose the second intent.
Review note Claude reviewed the design and flagged three risks: incomplete guard migration, slot namespace collisions, and cold subflow load latency. The implementation avoids call-time load latency by stitching modules at flow load. Antigravity's 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.