Files
foxhunt/docs/plans/2026-03-03-fxt-overhaul-implementation.md
jgrusewski df93b4c534 docs: fxt 2.0 design and implementation plan
Clean rewrite into full-scale operations platform.
Single proto/ root, monitoring_service removed,
CLI + MCP + cockpit TUI with purple/cyan theme.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-03 20:11:21 +01:00

29 KiB
Raw Blame History

fxt 2.0 — Foxhunt Operations Platform Implementation Plan

For Claude: REQUIRED SUB-SKILL: Use superpowers:executing-plans to implement this plan task-by-task.

Goal: Clean rewrite of fxt into a full-scale monitoring and operations CLI with JSON output, MCP server, and cockpit TUI — using a single consolidated proto directory.

Architecture: fxt binary with three interfaces (CLI, MCP, TUI) sharing a unified gRPC client layer compiled from proto/ at workspace root. API Gateway is the single endpoint. monitoring_service is removed.

Tech Stack: Rust, tonic 0.14, clap 4.5, ratatui 0.29, serde_json, crossterm 0.28

Working directory: /home/jgrusewski/Work/foxhunt/.claude/worktrees/fxt-overhaul

Build command: SQLX_OFFLINE=true cargo check -p fxt 2>&1

Test command: SQLX_OFFLINE=true cargo test -p fxt --lib 2>&1

Clippy command: SQLX_OFFLINE=true cargo clippy -p fxt -- -D warnings 2>&1


Phase 1: Proto Consolidation (Tasks 13)

Task 1: Create workspace-root proto/ directory

Copy the authoritative service protos to proto/ at workspace root. Merge the two monitoring protos into one.

Files:

  • Create: proto/trading.proto (copy from services/trading_service/proto/trading.proto)
  • Create: proto/config.proto (copy from services/trading_service/proto/config.proto)
  • Create: proto/ml.proto (copy from services/trading_service/proto/ml.proto)
  • Create: proto/risk.proto (copy from services/trading_service/proto/risk.proto)
  • Create: proto/ml_training.proto (copy from services/ml_training_service/proto/ml_training.proto)
  • Create: proto/broker_gateway.proto (copy from services/broker_gateway_service/proto/broker_gateway.proto)
  • Create: proto/trading_agent.proto (copy from services/trading_agent_service/proto/trading_agent.proto)
  • Create: proto/data_acquisition.proto (copy from services/data_acquisition_service/proto/data_acquisition.proto)
  • Create: proto/config_service.proto (copy from services/api_gateway/proto/config_service.proto)
  • Create: proto/health.proto (copy from bin/fxt/proto/health.proto)
  • Create: proto/monitoring.proto (MERGED from monitoring_service + trading_service versions)

Step 1: Copy all non-conflicting protos

mkdir -p proto
cp services/trading_service/proto/trading.proto proto/
cp services/trading_service/proto/config.proto proto/
cp services/trading_service/proto/ml.proto proto/
cp services/trading_service/proto/risk.proto proto/
cp services/ml_training_service/proto/ml_training.proto proto/
cp services/broker_gateway_service/proto/broker_gateway.proto proto/
cp services/trading_agent_service/proto/trading_agent.proto proto/
cp services/data_acquisition_service/proto/data_acquisition.proto proto/
cp services/api_gateway/proto/config_service.proto proto/
cp bin/fxt/proto/health.proto proto/

Step 2: Create merged monitoring.proto

The merged proto combines:

  • System health/alerting RPCs from services/trading_service/proto/monitoring.proto (10 RPCs)
  • Training metrics RPCs from services/monitoring_service/proto/monitoring.proto (3 RPCs)
  • All message types from both (no field number conflicts since they're separate messages)

Add these 3 RPCs to the existing service block in the trading_service version:

  // Training Metrics (absorbed from monitoring_service)
  rpc GetLiveTrainingMetrics(GetLiveTrainingMetricsRequest)
      returns (GetLiveTrainingMetricsResponse);
  rpc StreamTrainingMetrics(StreamTrainingMetricsRequest)
      returns (stream GetLiveTrainingMetricsResponse);
  rpc GetEpochHistory(GetEpochHistoryRequest)
      returns (GetEpochHistoryResponse);

And append all training-related message types (TrainingSession, GpuSnapshot, EpochFinancialSnapshot, etc.) after the existing system health messages.

Add cpu_percent, memory_used_mb, memory_total_mb fields to GetLiveTrainingMetricsResponse (these are used by the watch TUI).

Step 3: Verify proto syntax

protoc --proto_path=proto --descriptor_set_out=/dev/null proto/*.proto 2>&1

Expected: No errors.

Step 4: Commit

git add proto/
git commit -m "feat: create consolidated proto/ directory at workspace root

Merge monitoring_service training metrics into trading_service system health
monitoring.proto. All 11 proto files now in one canonical location."

Task 2: Rewire all service build.rs files to reference proto/

Each service build.rs currently references proto/*.proto relative to its own directory. We need to update them to reference ../../proto/ (services are at services/<name>/).

Files to modify:

  • services/trading_service/build.rs
  • services/ml_training_service/build.rs
  • services/broker_gateway_service/build.rs
  • services/trading_agent_service/build.rs
  • services/data_acquisition_service/build.rs
  • services/monitoring_service/build.rs (will be deleted in Phase 2, but keep working for now)
  • services/api_gateway/build.rs

Step 1: Update each build.rs

For each service, change proto paths from local (proto/<name>.proto) to root (../../proto/<name>.proto) and include path from proto to ../../proto.

Example for services/trading_service/build.rs:

// Before:
.compile_protos(&["proto/trading.proto"], &["proto"])?;
// After:
.compile_protos(&["../../proto/trading.proto"], &["../../proto"])?;

For services/api_gateway/build.rs, also remove the reference to ../../bin/fxt/proto/trading.proto (the fat-client proto). The API gateway should compile the same service protos. Keep the config_service.proto reference since that's also in proto/ now (../../proto/config_service.proto).

For cross-service references in build.rs files (e.g., trading_service compiling ml_training.proto, trading_agent_service compiling ml.proto), change to ../../proto/<name>.proto — they're all in the same root proto/ dir now.

Step 2: Update cargo:rerun-if-changed paths in each build.rs

Every println!("cargo:rerun-if-changed=...") must point to the new proto/ path.

Step 3: Verify all services compile

SQLX_OFFLINE=true cargo check --workspace 2>&1 | head -30

Expected: All services compile successfully.

Step 4: Delete old per-service proto directories

rm -rf services/trading_service/proto/
rm -rf services/ml_training_service/proto/
rm -rf services/broker_gateway_service/proto/
rm -rf services/trading_agent_service/proto/
rm -rf services/data_acquisition_service/proto/
rm -rf services/api_gateway/proto/
# Do NOT delete services/monitoring_service/proto/ yet (Phase 2)
# Do NOT delete bin/fxt/proto/ yet (Phase 1 Task 3)

Step 5: Verify workspace still compiles after deletion

SQLX_OFFLINE=true cargo check --workspace 2>&1 | head -30

Step 6: Commit

git add -A
git commit -m "refactor: rewire all service build.rs to proto/ root directory

Delete per-service proto/ directories. All services now compile from
the single canonical proto/ at workspace root."

Task 3: Rewire fxt build.rs to use proto/ root

Replace all fxt proto references with the workspace root protos.

Files:

  • Modify: bin/fxt/build.rs
  • Delete: bin/fxt/proto/ (entire directory — 8 fat-client proto files)

Step 1: Rewrite bin/fxt/build.rs

The new build.rs compiles all 11 protos from ../../proto/. Client-only (no server generation needed for fxt — except for E2E test mock servers).

fn main() -> Result<(), Box<dyn std::error::Error>> {
    let proto_root = "../../proto";

    tonic_prost_build::configure()
        .build_server(true)  // Required for E2E test mock servers
        .build_client(true)
        .type_attribute(".", "#[derive(serde::Serialize, serde::Deserialize)]")
        .server_mod_attribute(".", "#[allow(unused_qualifications)]")
        .client_mod_attribute(".", "#[allow(unused_qualifications)]")
        .protoc_arg("--experimental_allow_proto3_optional")
        .compile_protos(
            &[
                &format!("{proto_root}/trading.proto"),
                &format!("{proto_root}/health.proto"),
                &format!("{proto_root}/ml.proto"),
                &format!("{proto_root}/config.proto"),
                &format!("{proto_root}/ml_training.proto"),
                &format!("{proto_root}/trading_agent.proto"),
                &format!("{proto_root}/broker_gateway.proto"),
                &format!("{proto_root}/monitoring.proto"),
                &format!("{proto_root}/risk.proto"),
                &format!("{proto_root}/data_acquisition.proto"),
                &format!("{proto_root}/config_service.proto"),
            ],
            &[proto_root],
        )?;

    for proto in &[
        "trading", "health", "ml", "config", "ml_training",
        "trading_agent", "broker_gateway", "monitoring", "risk",
        "data_acquisition", "config_service",
    ] {
        println!("cargo:rerun-if-changed={proto_root}/{proto}.proto");
    }

    Ok(())
}

Step 2: Delete old fxt proto directory

rm -rf bin/fxt/proto/

Step 3: Update fxt source imports

The generated Rust module paths will change because proto packages changed. For example:

  • Old: foxhunt::tli::TradingServiceClient (from package foxhunt.tli)
  • New: trading::TradingServiceClient (from package trading)
  • Old: foxhunt::ml::MlServiceClient (from package foxhunt.ml)
  • New: ml::MlServiceClient (from package ml)

Update all tonic::include_proto!() calls and use statements across fxt source files.

Important: The new proto types have different field names/structures than the fat-client protos. For example, the service trading.proto has StreamOrders, StreamPositions, StreamMarketData RPCs instead of the fat-client's unified SubscribeMarketData.

This step will likely break many existing fxt commands. That's expected — we're doing a clean rewrite. Just make the build.rs + proto compilation work; the commands will be rewritten in Phase 3.

Step 4: Verify fxt compiles (lib.rs at minimum)

SQLX_OFFLINE=true cargo check -p fxt 2>&1 | head -50

At this point, expect compilation errors in command files (they reference old proto types). That's fine — we're rewriting those.

Step 5: Commit

git add -A
git commit -m "refactor: point fxt build.rs at proto/ root, delete fat-client protos

fxt now compiles from the same service protos as all backend services.
Existing command implementations will be rewritten to use new proto types."

Phase 2: Remove monitoring_service (Tasks 45)

Task 4: Absorb monitoring_service into API Gateway

Move the Prometheus scraping logic from monitoring_service into the API Gateway. The API Gateway will serve the monitoring RPCs directly (no proxy needed).

Files:

  • Read: services/monitoring_service/src/prometheus_client.rs (understand the Prometheus scraping)
  • Read: services/monitoring_service/src/service.rs (understand the gRPC service impl)
  • Create: services/api_gateway/src/monitoring_handler.rs (new: Prometheus client + monitoring service impl)
  • Modify: services/api_gateway/src/main.rs (register the monitoring service)
  • Modify: services/api_gateway/build.rs (compile monitoring.proto with server=true)

Step 1: Read the monitoring_service source

Understand prometheus_client.rs and service.rs to know what to port.

Step 2: Create monitoring_handler.rs in api_gateway

Port the Prometheus scraping and monitoring service implementation. Key pieces:

  • PrometheusClient struct: fetches from PROMETHEUS_URL env var
  • MonitoringServiceImpl: implements the 3 training metrics RPCs
  • The merged proto now has both system health RPCs (10) and training RPCs (3)
  • For system health RPCs: implement with real service health checks (call each backend service's health endpoint)
  • For training RPCs: port the Prometheus scraping logic

Step 3: Register monitoring service in API Gateway main.rs

Add the monitoring service to the tonic server builder alongside existing services.

Step 4: Verify API Gateway compiles

SQLX_OFFLINE=true cargo check -p api_gateway 2>&1 | head -30

Step 5: Commit

git add services/api_gateway/
git commit -m "feat: absorb monitoring service into API Gateway

Port Prometheus scraping and training metrics RPCs into api_gateway.
API Gateway now serves monitoring RPCs directly without proxy."

Task 5: Delete monitoring_service and update infrastructure

Files:

  • Delete: services/monitoring_service/ (entire directory)
  • Modify: Cargo.toml (remove from workspace members)
  • Modify: infra/k8s/gitlab/tailscale-proxy.yaml (remove monitoring path routing)
  • Modify: .gitlab-ci.yml (remove monitoring_service compile/deploy jobs)
  • Delete: proto/monitoring_service_standalone.proto if any leftovers
  • Modify: K8s deployment manifest for monitoring-service (if exists)

Step 1: Remove from workspace

In Cargo.toml, remove "services/monitoring_service" from workspace.members.

Step 2: Delete the service directory

rm -rf services/monitoring_service/

Step 3: Remove nginx monitoring path routing

In infra/k8s/gitlab/tailscale-proxy.yaml, remove the location /monitoring.MonitoringService/ block from the api.fxhnt.ai server. All monitoring RPCs now go to API Gateway's default location.

Step 4: Update CI pipeline

In .gitlab-ci.yml, remove any monitoring_service compile and deploy job references.

Step 5: Remove K8s deployment if it exists

Search for monitoring-service K8s manifests and delete them.

Step 6: Verify workspace compiles

SQLX_OFFLINE=true cargo check --workspace 2>&1 | head -30

Step 7: Commit

git add -A
git commit -m "chore: delete monitoring_service — absorbed into API Gateway

Remove from workspace members, delete K8s deployment, remove nginx routing.
Training metrics and system health now served directly by API Gateway."

Phase 3: New fxt Binary Skeleton (Tasks 69)

Task 6: Clean slate — strip fxt to minimal skeleton

Remove all existing command implementations and start fresh with the new command tree. Keep auth module and config module as reference (they're reusable).

Files:

  • Modify: bin/fxt/src/main.rs (new clap CLI definition)
  • Modify: bin/fxt/src/lib.rs (new module declarations)
  • Modify: bin/fxt/src/config.rs (keep, already has api_url)
  • Create: bin/fxt/src/output.rs (JSON/human output abstraction)
  • Create: bin/fxt/src/grpc.rs (unified gRPC client)
  • Delete: bin/fxt/src/client/ (old gRPC clients — will be replaced)
  • Delete: bin/fxt/src/commands/ (all old commands — will be rewritten)
  • Keep: bin/fxt/src/auth/ (reusable)
  • Keep: bin/fxt/src/error.rs (reusable)

Step 1: Create output.rs — JSON/human output abstraction

Every command returns a CommandResult that can be rendered as JSON or human-readable:

use serde::Serialize;

/// Output format selected by --json flag
#[derive(Debug, Clone, Copy)]
pub enum OutputFormat {
    Human,
    Json,
}

/// Render any serializable result in the chosen format
pub fn render<T: Serialize + HumanReadable>(result: &T, format: OutputFormat) -> anyhow::Result<()> {
    match format {
        OutputFormat::Json => {
            let json = serde_json::to_string_pretty(result)?;
            println!("{json}");
        }
        OutputFormat::Human => {
            result.print_human();
        }
    }
    Ok(())
}

/// Trait for human-readable rendering
pub trait HumanReadable {
    fn print_human(&self);
}

Step 2: Create grpc.rs — unified gRPC client factory

use tonic::transport::Channel;
use anyhow::Result;

/// Create a gRPC channel to the API endpoint
pub async fn connect(api_url: &str) -> Result<Channel> {
    let channel = Channel::from_shared(api_url.to_owned())?
        .connect()
        .await?;
    Ok(channel)
}

All service clients are created from this single channel.

Step 3: Rewrite main.rs with new command tree

Define the full clap CLI structure with all commands and subcommands from the design doc. Each command is a stub that prints "not implemented yet". Add the global --json flag.

#[derive(Parser)]
#[command(name = "fxt", about = "Foxhunt Operations Platform")]
struct Cli {
    /// API endpoint URL
    #[arg(long = "api-url", env = "API_URL", default_value = "https://api.fxhnt.ai")]
    api_url: String,

    /// Output format (JSON for CI/LLM integration)
    #[arg(long, short)]
    json: bool,

    #[command(subcommand)]
    command: Commands,
}

#[derive(Subcommand)]
enum Commands {
    Auth(auth::AuthCommand),
    Trade(trade::TradeCommand),
    Train(train::TrainCommand),
    Tune(tune::TuneCommand),
    Model(model::ModelCommand),
    Agent(agent::AgentCommand),
    Backtest(backtest::BacktestCommand),
    Broker(broker::BrokerCommand),
    Data(data::DataCommand),
    Service(service::ServiceCommand),
    Cluster(cluster::ClusterCommand),
    Risk(risk::RiskCommand),
    Config(config_cmd::ConfigCommand),
    Watch(watch::WatchCommand),
    Mcp(mcp::McpCommand),
}

Step 4: Create stub command modules

Create bin/fxt/src/commands/ with one file per command group. Each file defines the subcommand enum and a stub execute() function:

  • bin/fxt/src/commands/mod.rs
  • bin/fxt/src/commands/auth.rs
  • bin/fxt/src/commands/trade.rs
  • bin/fxt/src/commands/train.rs
  • bin/fxt/src/commands/tune.rs
  • bin/fxt/src/commands/model.rs
  • bin/fxt/src/commands/agent.rs
  • bin/fxt/src/commands/backtest.rs
  • bin/fxt/src/commands/broker.rs
  • bin/fxt/src/commands/data.rs
  • bin/fxt/src/commands/service.rs
  • bin/fxt/src/commands/cluster.rs
  • bin/fxt/src/commands/risk.rs
  • bin/fxt/src/commands/config_cmd.rs
  • bin/fxt/src/commands/watch.rs (stub for now, Phase 5)
  • bin/fxt/src/commands/mcp.rs (stub for now, Phase 6)

Step 5: Verify fxt compiles and runs help

SQLX_OFFLINE=true cargo check -p fxt 2>&1
SQLX_OFFLINE=true cargo run -p fxt -- --help 2>&1

Step 6: Write basic CLI tests

Test that --help works, --json flag is parsed, each subcommand is recognized.

Step 7: Run tests

SQLX_OFFLINE=true cargo test -p fxt --lib 2>&1

Step 8: Commit

git add -A
git commit -m "feat: fxt 2.0 skeleton — new command tree with 15 command groups

Clean rewrite with unified gRPC client, JSON/human output abstraction,
and stub implementations for all commands."

Task 7: Implement grpc.rs — full client layer

Build the unified gRPC client that creates typed service clients from a single channel.

Files:

  • Modify: bin/fxt/src/grpc.rs

Step 1: Write the client factory

pub struct FoxhuntClient {
    channel: Channel,
}

impl FoxhuntClient {
    pub async fn connect(api_url: &str) -> Result<Self> { ... }

    pub fn trading(&self) -> trading::trading_service_client::TradingServiceClient<Channel> { ... }
    pub fn ml(&self) -> ml::ml_service_client::MlServiceClient<Channel> { ... }
    pub fn ml_training(&self) -> ml_training::ml_training_service_client::MlTrainingServiceClient<Channel> { ... }
    pub fn broker(&self) -> broker_gateway::broker_gateway_service_client::BrokerGatewayServiceClient<Channel> { ... }
    pub fn trading_agent(&self) -> trading_agent::trading_agent_service_client::TradingAgentServiceClient<Channel> { ... }
    pub fn monitoring(&self) -> monitoring::monitoring_service_client::MonitoringServiceClient<Channel> { ... }
    pub fn config(&self) -> config::configuration_service_client::ConfigurationServiceClient<Channel> { ... }
    pub fn data_acquisition(&self) -> data_acquisition::data_acquisition_service_client::DataAcquisitionServiceClient<Channel> { ... }
    pub fn risk(&self) -> risk::risk_service_client::RiskServiceClient<Channel> { ... }
}

The exact client type names depend on the generated code from the service protos. Read the generated code (target/debug/build/fxt-*/out/) to find the correct module paths.

Step 2: Test client construction

Write a unit test that creates a client (can't connect without a server, but can verify construction doesn't panic).

Step 3: Commit

git add bin/fxt/src/grpc.rs
git commit -m "feat: unified FoxhuntClient with typed service accessors"

Task 8: Implement service command (first real command)

The service command group is the most useful for ops. Implement it first as the template for all other commands.

Files:

  • Modify: bin/fxt/src/commands/service.rs

Step 1: Define the ServiceCommand enum

#[derive(Parser)]
pub struct ServiceCommand {
    #[command(subcommand)]
    action: ServiceAction,
}

#[derive(Subcommand)]
enum ServiceAction {
    /// List all services with health status
    List,
    /// Get detailed status of a specific service
    Status { service: String },
    /// Health check all services
    Health,
}

Step 2: Implement list subcommand

Calls monitoring_client.get_system_status() → renders as table (human) or JSON.

Step 3: Implement health subcommand

Calls monitoring_client.get_health_check() → renders per-service health.

Step 4: Implement status subcommand

Calls monitoring_client.get_system_status() with filter → renders detailed view.

Step 5: Test with --json output

Both human-readable and JSON output paths should work.

Step 6: Commit

git add bin/fxt/src/commands/service.rs
git commit -m "feat: fxt service list/status/health with --json support"

Task 9: Implement remaining CLI commands

Follow the same pattern as Task 8 for each command group. Each command:

  1. Defines clap subcommands
  2. Creates the appropriate gRPC client from FoxhuntClient
  3. Makes the RPC call
  4. Renders result via output::render() (JSON or human)

Command implementation order (most useful first):

  1. train — start/stop/status/list/logs (uses ml_training client)
  2. tune — start/stop/status/approve (uses ml_training client)
  3. model — list/status/predict/ensemble/promote (uses ml client)
  4. trade — submit/cancel/positions/orders/account (uses trading + broker clients)
  5. broker — status/connect/executions (uses broker_gateway client)
  6. agent — start/stop/status/config (uses trading_agent client)
  7. backtest — run/status/results (uses trading client's backtesting RPCs)
  8. data — download/status/cache/feeds (uses data_acquisition client)
  9. risk — status/limits/drawdown/stress/emergency (uses risk client)
  10. config — get/set/export/env (uses config client)
  11. cluster — status/resources/events (uses monitoring client + kubectl)
  12. auth — login/logout/status (port from existing auth module)

Each command should be its own commit. Each commit should include:

  • The command implementation
  • At least one test (CLI arg parsing)
  • Verify cargo check and cargo clippy pass

Phase 4: TUI Cockpits (Tasks 1014)

Task 10: TUI foundation — theme, state, event loop

Build the cockpit TUI framework before adding individual cockpit views.

Files:

  • Create: bin/fxt/src/tui/mod.rs
  • Create: bin/fxt/src/tui/theme.rs (color constants from logo)
  • Create: bin/fxt/src/tui/state.rs (global TUI state)
  • Create: bin/fxt/src/tui/event_loop.rs (event handling + gRPC streams)
  • Create: bin/fxt/src/tui/cockpit.rs (cockpit trait + registry)
  • Modify: bin/fxt/src/commands/watch.rs (wire up TUI launch)

Step 1: Create theme.rs

Define the color palette from the foxhunt logo:

use ratatui::style::Color;

pub const BG: Color = Color::Rgb(15, 15, 35);          // #0F0F23
pub const SURFACE: Color = Color::Rgb(26, 26, 46);     // #1A1A2E
pub const TEXT: Color = Color::Rgb(224, 224, 224);      // #E0E0E0
pub const PRIMARY: Color = Color::Rgb(139, 92, 246);    // #8B5CF6 purple
pub const ACCENT: Color = Color::Rgb(6, 182, 212);      // #06B6D4 cyan
pub const SUCCESS: Color = Color::Rgb(16, 185, 129);    // #10B981 green
pub const WARNING: Color = Color::Rgb(245, 158, 11);    // #F59E0B amber
pub const ERROR: Color = Color::Rgb(239, 68, 68);       // #EF4444 red
pub const MUTED: Color = Color::Rgb(107, 114, 128);     // #6B7280 gray
pub const BORDER: Color = Color::Rgb(76, 29, 149);      // #4C1D95 purple dim
pub const HIGHLIGHT: Color = Color::Rgb(34, 211, 238);  // #22D3EE bright cyan

Step 2: Create cockpit trait

pub trait Cockpit {
    fn name(&self) -> &str;
    fn key(&self) -> char;  // '1' through '6'
    fn render(&self, frame: &mut Frame, area: Rect, state: &AppState);
}

Step 3: Create AppState

Hold all data for all cockpits. Updated by background gRPC streams.

Step 4: Create event loop

  • Terminal setup/teardown (crossterm)
  • Key handling: 1-6 switch cockpits, q quit, ? help
  • Background tasks: gRPC streaming subscriptions (monitoring, executions, training)
  • 1-second tick for gauge refresh

Step 5: Wire up watch command

fxt watch launches the TUI event loop.

Step 6: Verify compiles and basic TUI starts

SQLX_OFFLINE=true cargo check -p fxt 2>&1

Step 7: Commit

git add bin/fxt/src/tui/ bin/fxt/src/commands/watch.rs
git commit -m "feat: TUI foundation — theme, state, event loop, cockpit trait"

Task 11: Cockpit 1 — Overview

Four quadrants: Services | Resources | Training | Portfolio.

Files:

  • Create: bin/fxt/src/tui/cockpits/mod.rs
  • Create: bin/fxt/src/tui/cockpits/overview.rs

Step 1: Implement overview cockpit

Layout: 2x2 grid using Layout::default().direction(Direction::Vertical) split into top/bottom, each split horizontally.

  • Top-left: Service list (name + status indicator)
  • Top-right: System resources (CPU/RAM/GPU gauges)
  • Bottom-left: Active training summary (model, epoch, loss, Sharpe)
  • Bottom-right: Portfolio summary (equity, P&L, positions, margin)

Use the theme colors for all widgets.

Step 2: Commit


Task 12: Cockpit 2 — Training

Files:

  • Create: bin/fxt/src/tui/cockpits/training.rs

Active sessions table + GPU panel + CPU/RAM panel + Loss sparkline + Epoch financial metrics.


Task 13: Cockpit 36 — Trading, Services, Risk, Data

Files:

  • Create: bin/fxt/src/tui/cockpits/trading.rs
  • Create: bin/fxt/src/tui/cockpits/services.rs
  • Create: bin/fxt/src/tui/cockpits/risk.rs
  • Create: bin/fxt/src/tui/cockpits/data.rs

Implement each cockpit according to the design doc layouts.


Task 14: TUI integration test

Write an E2E test that launches the TUI with a mock gRPC server, verifies all 6 cockpits render without panicking, and responds to key events.


Phase 5: MCP Server Mode (Tasks 1516)

Task 15: MCP protocol implementation

Files:

  • Create: bin/fxt/src/mcp/mod.rs
  • Create: bin/fxt/src/mcp/server.rs (JSON-RPC 2.0 stdin/stdout)
  • Create: bin/fxt/src/mcp/tools.rs (tool definitions)
  • Modify: bin/fxt/src/commands/mcp.rs (wire up serve command)

Step 1: Implement MCP JSON-RPC protocol

Read JSON-RPC requests from stdin, dispatch to tool handlers, write responses to stdout.

Protocol messages:

  • initialize → return server info + tool list
  • tools/list → return all available tools
  • tools/call → execute a tool and return result

Step 2: Define tool registry

Map each CLI command to an MCP tool:

Tool { name: "fxt_service_list", description: "List all services with health status", input_schema: {} }
Tool { name: "fxt_train_start", description: "Start ML training job", input_schema: { model: string, symbol: string } }
// ... one per CLI subcommand

Step 3: Wire up serve command

fxt mcp serve starts the stdin/stdout loop.

Step 4: Test with mock input

Write a test that pipes JSON-RPC requests and verifies responses.

Step 5: Commit


Task 16: MCP tool implementations

Each MCP tool reuses the same logic as the CLI command, but returns structured JSON instead of printing.

Refactor each command's execute() to return a serde-serializable result, then:

  • CLI path: calls output::render(result, format)
  • MCP path: serializes result as JSON-RPC response

This is where the --json architecture pays off — both CLI and MCP use the same serialization.


Phase 6: Final Verification (Task 17)

Task 17: Full verification and cleanup

Step 1: Run full workspace check

SQLX_OFFLINE=true cargo check --workspace 2>&1

Step 2: Run fxt tests

SQLX_OFFLINE=true cargo test -p fxt --lib 2>&1

Step 3: Run clippy

SQLX_OFFLINE=true cargo clippy -p fxt -- -D warnings 2>&1

Step 4: Run fxt --help to verify command tree

SQLX_OFFLINE=true cargo run -p fxt -- --help 2>&1
SQLX_OFFLINE=true cargo run -p fxt -- service --help 2>&1
SQLX_OFFLINE=true cargo run -p fxt -- watch --help 2>&1
SQLX_OFFLINE=true cargo run -p fxt -- mcp --help 2>&1

Step 5: Verify --json flag works

SQLX_OFFLINE=true cargo run -p fxt -- service list --json 2>&1

Step 6: Final commit

git add -A
git commit -m "feat: fxt 2.0 complete — operations platform with CLI, TUI cockpits, MCP server"