Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
27 changes: 13 additions & 14 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,9 @@ name: CI

on:
push:
branches: [main, develop]
branches: [main, develop, 'feat/**', 'fix/**']
pull_request:
branches: [main]
branches: [main, develop]

env:
BUILD_TYPE: Release
Expand All @@ -14,13 +14,10 @@ jobs:
strategy:
fail-fast: false
matrix:
os: [ubuntu-22.04, macos-13, macos-14]
include:
- os: ubuntu-22.04
cmake_flags: ""
- os: macos-13
- os: ubuntu-latest
cmake_flags: ""
- os: macos-14
- os: macos-latest
cmake_flags: "-DCMAKE_OSX_ARCHITECTURES=arm64"

runs-on: ${{ matrix.os }}
Expand Down Expand Up @@ -52,38 +49,40 @@ jobs:
run: ctest --output-on-failure --timeout 120

- name: Upload build artifacts
if: matrix.os == 'ubuntu-22.04'
if: matrix.os == 'ubuntu-latest'
uses: actions/upload-artifact@v4
with:
name: sparx-linux-x64
path: build/cli/sparx
retention-days: 7
if-no-files-found: ignore

lint:
runs-on: ubuntu-22.04
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4

- name: Install clang-tidy
run: sudo apt-get install -y clang-tidy-15
run: |
sudo apt-get update
sudo apt-get install -y clang-tidy

- name: Run clang-tidy on Agent OS modules
run: |
clang-tidy-15 \
-p build \
clang-tidy \
--checks='-*,bugprone-*,performance-*,modernize-*,-modernize-use-trailing-return-type' \
cli/src/sparx_agent_scheduler.cpp \
cli/src/sparx_context_manager.cpp \
cli/src/sparx_memory_manager.cpp \
cli/src/sparx_access_control.cpp \
cli/src/sparx_tool_registry.cpp \
-- -std=c++17 -Icli/include -Isrc/include \
-- -std=c++17 -Icli/include -Iinclude \
-Ithird_party/memory_short_term/include \
-Ithird_party \
|| true # Non-blocking for now

security-check:
runs-on: ubuntu-22.04
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4

Expand Down
19 changes: 12 additions & 7 deletions .github/workflows/release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -14,15 +14,13 @@ env:
jobs:
build-release:
strategy:
fail-fast: false
matrix:
include:
- os: ubuntu-22.04
- os: ubuntu-latest
target: linux-x64
artifact: sparx
- os: macos-13
target: macos-x64
artifact: sparx
- os: macos-14
- os: macos-latest
target: macos-arm64
artifact: sparx
cmake_flags: "-DCMAKE_OSX_ARCHITECTURES=arm64"
Expand Down Expand Up @@ -59,7 +57,14 @@ jobs:
- name: Package
run: |
mkdir -p dist
cp build/cli/sparx dist/
# The sparx CLI requires the proprietary kernel; in OSS builds it is
# absent. Package whatever binaries the build produced.
if [ -f build/cli/sparx ]; then
cp build/cli/sparx dist/
fi
for bin in build/bench_strategic build/eval_*; do
[ -f "$bin" ] && cp "$bin" dist/ || true
done
cp README.md LICENSE dist/
cd dist && tar czf ../sparx-${{ matrix.target }}.tar.gz .

Expand All @@ -76,7 +81,7 @@ jobs:

create-release:
needs: build-release
runs-on: ubuntu-22.04
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4

Expand Down
21 changes: 20 additions & 1 deletion ARCHITECTURE.md
Original file line number Diff line number Diff line change
Expand Up @@ -34,9 +34,16 @@ OAK/
│ │ ├── sparx_delta_crdt.cpp # Delta-state CRDT (OR-Set, LWW, GCounter)
│ │ ├── sparx_learning.cpp # DP-SGD on-device fine-tuning
│ │ ├── sparx_constrained_decode.cpp # GBNF grammar enforcement
│ │ ├── sparx_cloud_fusion.cpp # Cloud/edge inference routing
│ │ ├── sparx_cloud_fusion.cpp # Cloud/edge inference routing (legacy)
│ │ ├── sparx_trace.cpp # Distributed tracing
│ │ │
│ │ ├── # ─── Edge-Cloud Harness (端云融合) ───
│ │ ├── sparx_pipeline_harness.cpp # Pluggable pipeline orchestrator
│ │ ├── sparx_prompt_engine.cpp # Prompt compression + intent distillation
│ │ ├── sparx_cloud_backend.cpp # Cloud LLM HTTP client (OpenAI compat)
│ │ ├── sparx_arbiter.cpp # Local arbitration (cloud_prefer/latency/confidence)
│ │ ├── sparx_confidence_scorer.cpp # Confidence-gated routing
│ │ │
│ │ ├── # ─── Model Runtime Adapters ───
│ │ ├── llama_cpp_model_runtime.cpp # llama-server HTTP adapter
│ │ └── genie_model_runtime.cpp # Qualcomm QNN/GenieX adapter
Expand Down Expand Up @@ -112,6 +119,18 @@ OAK/
│ Kernel API (include/master_agent/) │
│ IOrchestrator, IModelRuntime, types │
└──────────────────────────────────────────────────┘

┌──────────────────────────────────────────────────┐
│ Edge-Cloud Pipeline Harness (harness/) │
│ │
│ IPromptEngine ─→ ICloudBackend │
│ │ │ │
│ ▼ ▼ │
│ IConfidenceScorer ──→ IArbiter ──→ Output │
│ ▲ │
│ │ │
│ ILocalInference (wraps Model Runtime) │
└──────────────────────────────────────────────────┘
```

## Build Targets
Expand Down
14 changes: 14 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,20 @@ All notable changes to OAK (Open Agent Kernel) will be documented in this file.
Format follows [Keep a Changelog](https://keepachangelog.com/en/1.1.0/).
Versioning follows [Semantic Versioning](https://semver.org/).

## [Unreleased]

### Added
- Edge-Cloud Pipeline Harness — pluggable dual-path inference architecture
- `IPromptEngine`: prompt compression + intent distillation before cloud dispatch
- `ICloudBackend`: async cloud LLM client (OpenAI-compatible)
- `IArbiter`: local arbitration (cloud_prefer / latency_first / confidence)
- `IConfidenceScorer`: two-phase confidence gating (pre-score + post-score)
- `PipelineHarness`: top-level orchestrator with component registry
- Configuration: `config/harness.yaml` for edge-cloud pipeline settings
- Prompt templates: `templates/` directory with default, navigation, vehicle_control
- Test suite: `test_harness` with 15 unit tests covering all harness components
- Design documentation: `docs/edge_cloud_design.md`

## [0.3.0] - 2026-08-21

### Added
Expand Down
8 changes: 8 additions & 0 deletions CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -33,9 +33,17 @@ Full CLI (`sparx`) requires proprietary kernel source in `src/`.
- `cli/src/sparx_*.cpp` — Strategic feature implementations
- `cli/src/cmd_*.cpp` — CLI commands
- `cli/src/llama_cpp_model_runtime.cpp` — llama-server adapter
- `cli/src/sparx_pipeline_harness.cpp` — Edge-cloud pipeline orchestrator
- `cli/src/sparx_prompt_engine.cpp` — Prompt compression for cloud dispatch
- `cli/src/sparx_cloud_backend.cpp` — Cloud LLM HTTP client
- `cli/src/sparx_arbiter.cpp` — Local arbitration logic
- `cli/src/sparx_confidence_scorer.cpp` — Confidence-gated routing
- `config/harness.yaml` — Edge-cloud pipeline configuration
- `templates/` — Prompt templates (default, navigation, vehicle_control)
- `tests/CMakeLists.txt` — Test target definitions
- `VERSION.json` — Project version metadata
- `ARCHITECTURE.md` — Full code map
- `docs/edge_cloud_design.md` — Edge-cloud architecture design doc

## Git Workflow

Expand Down
26 changes: 26 additions & 0 deletions CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -344,6 +344,32 @@ elseif(MASTER_AGENT_BUILD_TESTS)
endif()
add_test(NAME bench_strategic COMMAND bench_strategic)
endif()

# test_harness — edge-cloud pipeline harness
if(EXISTS "${CMAKE_SOURCE_DIR}/tests/test_harness.cpp")
add_executable(test_harness
tests/test_harness.cpp
"${_cli_src}/sparx_pipeline_harness.cpp"
"${_cli_src}/sparx_prompt_engine.cpp"
"${_cli_src}/sparx_cloud_backend.cpp"
"${_cli_src}/sparx_arbiter.cpp"
"${_cli_src}/sparx_confidence_scorer.cpp")
target_compile_features(test_harness PRIVATE cxx_std_17)
target_include_directories(test_harness PRIVATE
"${CMAKE_SOURCE_DIR}/cli/include" "${CMAKE_SOURCE_DIR}/third_party")
target_link_libraries(test_harness PRIVATE MasterAgent::Core)
if(CMAKE_SYSTEM_NAME STREQUAL "Linux" OR CMAKE_SYSTEM_NAME STREQUAL "Darwin")
target_link_libraries(test_harness PRIVATE pthread)
endif()
find_package(CURL QUIET)
if(CURL_FOUND)
target_link_libraries(test_harness PRIVATE CURL::libcurl)
target_compile_definitions(test_harness PRIVATE SPARX_HAS_CURL=1)
else()
target_compile_definitions(test_harness PRIVATE SPARX_HAS_CURL=0)
endif()
add_test(NAME test_harness COMMAND test_harness)
endif()
endif()

add_subdirectory(eval)
Expand Down
163 changes: 163 additions & 0 deletions cli/include/sparx_arbiter.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,163 @@
#pragma once
/**
* @file sparx_arbiter.h
* @brief Arbiter — local arbitration between on-device and cloud results.
*
* Receives results from both inference paths and selects the final output.
* Strategies:
* - CloudPrefer: when both available, prefer cloud (stronger model)
* - LatencyFirst: use whichever arrives first within deadline
* - Confidence: use post-score to pick the more reliable result
*
* The arbiter also handles deadline enforcement: if a path hasn't returned
* by the deadline, the available result is used immediately.
*/

#include "sparx_cloud_backend.h"
#include "sparx_confidence_scorer.h"

#include <chrono>
#include <future>
#include <memory>
#include <optional>
#include <string>

namespace sparx {
namespace harness {

// ─── Data Types ─────────────────────────────────────────────────────────────

/// Result from the local inference path.
struct LocalResult {
bool success = false;
std::string content;
int latency_ms = 0;
ConfidenceScore confidence;
std::string error;
};

/// The final arbitration output.
struct ArbiterOutput {
enum class Source { Local, Cloud, Fallback };

std::string content; // Final selected output
Source source = Source::Local; // Which path was selected
std::string reason; // Why this path was chosen
int total_latency_ms = 0; // Wall-clock from request start

// Metadata for observability
std::optional<LocalResult> local_result;
std::optional<CloudResult> cloud_result;
};

/// Arbiter strategy enum (maps to config).
enum class ArbiterStrategy {
CloudPrefer, // Both available → prefer cloud
LatencyFirst, // Both available → prefer faster
Confidence, // Both available → prefer higher confidence
LocalOnly, // Never use cloud (override / offline mode)
};

/// Arbiter configuration.
struct ArbiterConfig {
ArbiterStrategy strategy = ArbiterStrategy::CloudPrefer;

/// Maximum time to wait for any result path.
int deadline_ms = 3000;

/// Per-intent deadline overrides (intent_type → deadline_ms).
std::unordered_map<std::string, int> intent_deadlines;

/// Minimum confidence gap to prefer one result over another.
/// Only used with Confidence strategy.
float confidence_gap_threshold = 0.15f;

/// Fallback when both paths fail.
std::string fallback_message = "I'm unable to process this request right now.";
};

// ─── Interface ──────────────────────────────────────────────────────────────

/// Abstract arbiter interface.
class IArbiter {
public:
virtual ~IArbiter() = default;

/// Arbitrate between local and cloud results.
/// Either result may be absent (nullopt) if the path failed or timed out.
virtual ArbiterOutput arbitrate(
const std::optional<LocalResult>& local,
const std::optional<CloudResult>& cloud,
const std::string& intent_type = ""
) const = 0;

/// Get the effective deadline for a given intent type.
virtual int getDeadline(const std::string& intent_type = "") const = 0;

/// Strategy name (for tracing).
virtual std::string name() const = 0;
};

// ─── Implementations ────────────────────────────────────────────────────────

/// Cloud-prefer arbiter: when both are available, pick cloud.
class CloudPreferArbiter : public IArbiter {
public:
explicit CloudPreferArbiter(const ArbiterConfig& config);

ArbiterOutput arbitrate(
const std::optional<LocalResult>& local,
const std::optional<CloudResult>& cloud,
const std::string& intent_type = ""
) const override;

int getDeadline(const std::string& intent_type = "") const override;
std::string name() const override { return "cloud_prefer"; }

private:
ArbiterConfig config_;
};

/// Latency-first arbiter: pick whichever is available (arrived first).
class LatencyFirstArbiter : public IArbiter {
public:
explicit LatencyFirstArbiter(const ArbiterConfig& config);

ArbiterOutput arbitrate(
const std::optional<LocalResult>& local,
const std::optional<CloudResult>& cloud,
const std::string& intent_type = ""
) const override;

int getDeadline(const std::string& intent_type = "") const override;
std::string name() const override { return "latency_first"; }

private:
ArbiterConfig config_;
};

/// Confidence-based arbiter: pick the result with higher confidence.
class ConfidenceArbiter : public IArbiter {
public:
explicit ConfidenceArbiter(const ArbiterConfig& config);

ArbiterOutput arbitrate(
const std::optional<LocalResult>& local,
const std::optional<CloudResult>& cloud,
const std::string& intent_type = ""
) const override;

int getDeadline(const std::string& intent_type = "") const override;
std::string name() const override { return "confidence"; }

private:
ArbiterConfig config_;
};

// ─── Factory ────────────────────────────────────────────────────────────────

/// Create an arbiter from strategy enum.
std::unique_ptr<IArbiter> createArbiter(const ArbiterConfig& config);

} // namespace harness
} // namespace sparx
Loading
Loading