Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
54 commits
Select commit Hold shift + click to select a range
738f23c
Add OpenClaw integration and rules engine
saschabuehrle Feb 5, 2026
29ba52c
Add rule engine unit tests
saschabuehrle Feb 5, 2026
ced8a46
Fix OpenClaw async handling and tool normalization
saschabuehrle Feb 5, 2026
0b3ba1a
Add OpenClaw cron channels and config-based routing
saschabuehrle Feb 5, 2026
9f1cdc9
Add per-channel routing strategies
saschabuehrle Feb 5, 2026
f41043a
Expose OpenClaw stats endpoint
saschabuehrle Feb 5, 2026
a39fbdb
Add savings metrics to stats export
saschabuehrle Feb 5, 2026
55927d2
Add comparison and factual domains
saschabuehrle Feb 5, 2026
8a20048
Add comparison and factual domain configs
saschabuehrle Feb 5, 2026
ad86c52
Add domain config threshold tests
saschabuehrle Feb 5, 2026
4d7d1bf
Honor OpenClaw metadata tags
saschabuehrle Feb 5, 2026
fb8bb83
Log OpenClaw routing tags
saschabuehrle Feb 5, 2026
391c9f4
Extend OpenClaw routing log
saschabuehrle Feb 5, 2026
a639fb8
Fix multi-turn detection and tool schema normalization
saschabuehrle Feb 5, 2026
7dc3d09
Fix OpenClaw routing hints and tool cost metrics
saschabuehrle Feb 5, 2026
59de0c3
Fix tool_calls in OpenAI response format and add /health endpoint
saschabuehrle Feb 5, 2026
6618ea5
test: add comprehensive OpenClaw integration test suite
saschabuehrle Feb 5, 2026
06135b0
fix: correct test assertions for field names and remove invalid tool …
saschabuehrle Feb 5, 2026
f8dcf90
fix: agent loop cascade rejects valid text after tool results
saschabuehrle Feb 5, 2026
c80eeb2
feat: universal message format conversion for tool calls
saschabuehrle Feb 5, 2026
94d33fe
feat: add OpenClaw domain routing configs
saschabuehrle Feb 5, 2026
f9fd7d1
feat: P0 quality fixes - enhanced complexity detection, remove trivia…
saschabuehrle Feb 5, 2026
c8a3d7b
feat: enhance FastEmbed domain detection with improved exemplars and …
saschabuehrle Feb 5, 2026
d6ca4ef
feat: add openclaw optional dep with FastEmbed + conversation domain
saschabuehrle Feb 5, 2026
597692f
fix: repair domain detection regex bug and expand keyword coverage
saschabuehrle Feb 5, 2026
b73ba8c
feat: comprehensive domain detection improvements - 90% accuracy
saschabuehrle Feb 5, 2026
bd72280
test: update domain detection tests for improved routing
saschabuehrle Feb 5, 2026
fe5a58f
chore: remove accidental file
saschabuehrle Feb 5, 2026
e54c36d
feat: improve FastEmbed hybrid detection + model selection
saschabuehrle Feb 5, 2026
cd6c144
feat: DX Plan V2 P0 implementation + audit reports
saschabuehrle Feb 6, 2026
2b890e5
docs: production evaluation report
saschabuehrle Feb 6, 2026
50470f4
docs: FastEmbed quick evaluation — 10-query routing test
saschabuehrle Feb 6, 2026
25a31a5
feat: scaffold Ink terminal demo app with welcome screen
saschabuehrle Feb 6, 2026
04d4e1f
fix: correct savings calculation baseline
saschabuehrle Feb 6, 2026
105d107
feat: DX Plan V2 P0 items from Codex Cloud
saschabuehrle Feb 6, 2026
103f3b6
fix: update FastEmbed model from intfloat/e5-large-v2 to BAAI/bge-bas…
saschabuehrle Feb 6, 2026
ab78ca0
fix: tighten FastEmbed version requirement to >=0.7.0
saschabuehrle Feb 6, 2026
d8fdf41
docs: add eval mode exception to AGENTS.md
saschabuehrle Feb 6, 2026
932fa1c
feat(openclaw): make hybrid domain detection OpenClaw-only
Feb 9, 2026
82177f2
fix: remove require_verifier for medical/legal, add all provider configs
Feb 9, 2026
0e45869
fix: use 60x cost ratio for savings calculation (was 2x)
Feb 9, 2026
923e91d
fix: record telemetry AFTER cost recalculation
Feb 9, 2026
a81cc1c
fix: allow config-loaded agent without api keys
saschabuehrle Feb 9, 2026
b00a0db
fix: estimate model cost when only total tokens known
saschabuehrle Feb 9, 2026
9571f53
fix: stabilize openclaw metadata and acceptance metrics
saschabuehrle Feb 9, 2026
046c788
fix: correct benchmark baseline and effective savings
saschabuehrle Feb 9, 2026
5d2391a
docs: fix python preset examples and savings fields
saschabuehrle Feb 9, 2026
b297c82
chore: remove investor demos from repo
saschabuehrle Feb 9, 2026
f4a9714
feat(openclaw): add decision trace JSONL + domain stats
saschabuehrle Feb 11, 2026
64a43e4
Merge main into codex/feature-openclaw-native
saschabuehrle Feb 11, 2026
b4903c2
fix(openclaw): wire domain detection into presets + strip NO_REPLY se…
saschabuehrle Feb 12, 2026
fbe364f
ci: trigger workflow run
saschabuehrle Feb 12, 2026
6508bd3
Merge main into codex/feature-openclaw-native
saschabuehrle Feb 12, 2026
34a6e6f
style: fix black formatting and ruff lint for CI
saschabuehrle Feb 12, 2026
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
4 changes: 4 additions & 0 deletions cascadeflow/agent.py
Original file line number Diff line number Diff line change
Expand Up @@ -90,6 +90,10 @@
# Phase 4: Tool complexity routing (NEW - v19)
from .rules import RuleContext, RuleEngine

# Phase 3: Tool routing
# Phase 2A: Routing module imports
# Phase 3.2: Domain detection (NEW)
# Phase 4: Tool complexity routing (NEW - v19)
# Phase 3: Tool routing
# Phase 2A: Routing module imports
# Phase 3.2: Domain detection (NEW)
Expand Down
41 changes: 31 additions & 10 deletions cascadeflow/integrations/openclaw/openai_server.py
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,18 @@

oc_logger = logging.getLogger("cascadeflow.openclaw")

_DEFAULT_SENTINELS = ("NO_REPLY",)


def _strip_sentinel(content: str, sentinels: tuple[str, ...]) -> str:
"""Strip known sentinel patterns from content. Returns empty string if only sentinels."""
if not content:
return content
cleaned = content.strip()
for sentinel in sentinels:
cleaned = cleaned.replace(sentinel, "")
return cleaned.strip()


def _to_openai_tool_calls(
tool_calls: list[dict[str, Any]],
Expand Down Expand Up @@ -403,7 +415,9 @@ def _handle_chat(self, server: OpenClawOpenAIServer, payload: dict[str, Any]) ->
result=result,
total_ms=total_ms,
)
log_decision(trace)
# Skip ghost traces where cascade wasn't initialised (null models)
if trace.get("draft", {}).get("model"):
log_decision(trace)
accepted = meta.get("draft_accepted", False)
model_used = getattr(result, "model_used", "unknown")
oc_logger.info(
Expand Down Expand Up @@ -614,11 +628,11 @@ async def _produce() -> None:
if "error" in error_box:
self.log_error("Streaming error: %s", error_box["error"])

full_content = "".join(chunk_parts)
full_content = _strip_sentinel("".join(chunk_parts), _DEFAULT_SENTINELS)
if not full_content:
completion_content = completion_result.get("content")
if isinstance(completion_content, str):
full_content = completion_content
full_content = _strip_sentinel(completion_content, _DEFAULT_SENTINELS)

# If no content was produced and there's an upstream error, send an
# error event so the client gets a meaningful failure instead of an
Expand Down Expand Up @@ -759,7 +773,9 @@ async def _produce() -> None:
decision_data=captured_decision,
total_ms=total_ms,
)
log_decision(trace)
# Skip ghost traces where cascade wasn't initialised (null models)
if trace.get("draft", {}).get("model"):
log_decision(trace)
accepted = captured_decision.get("accepted", False)
model_used = completion_result.get("model_used", "unknown")
total_cost = completion_result.get("total_cost", 0.0)
Expand Down Expand Up @@ -885,9 +901,11 @@ def _extract_upstream_error(result) -> dict[str, Any] | None:


def _has_content(result) -> bool:
"""Check if a result has non-empty content."""
"""Check if a result has non-empty content (excluding sentinels)."""
content = getattr(result, "content", None)
return isinstance(content, str) and bool(content.strip())
if not isinstance(content, str) or not content.strip():
return False
return bool(_strip_sentinel(content, _DEFAULT_SENTINELS))


def _run_agent(
Expand Down Expand Up @@ -1094,16 +1112,19 @@ def _build_openai_response(model: str, result) -> dict[str, Any]:
content = getattr(result, "content", "") or ""
if not isinstance(content, str):
content = str(content)
content = _strip_sentinel(content, _DEFAULT_SENTINELS)

# Never return an empty assistant message if we have usable content in metadata.
# This can happen when an upstream verifier returns only reasoning output.
if not tool_calls and not content.strip():
for source_key in ("verifier_response", "draft_response"):
candidate = meta.get(source_key)
if isinstance(candidate, str) and candidate.strip():
meta.setdefault("openclaw_content_fallback", source_key)
content = candidate
break
if isinstance(candidate, str):
candidate = _strip_sentinel(candidate, _DEFAULT_SENTINELS)
if candidate.strip():
meta.setdefault("openclaw_content_fallback", source_key)
content = candidate
break

message: dict[str, Any] = {"role": "assistant", "content": content}
if tool_calls:
Expand Down
4 changes: 4 additions & 0 deletions cascadeflow/providers/anthropic.py
Original file line number Diff line number Diff line change
Expand Up @@ -645,6 +645,10 @@ async def complete_with_tools(
if extracted_system:
payload["system"] = extracted_system

# Add system prompt if extracted from messages
if extracted_system:
payload["system"] = extracted_system

# Add tools if provided
if anthropic_tools:
payload["tools"] = anthropic_tools
Expand Down
10 changes: 10 additions & 0 deletions cascadeflow/utils/presets.py
Original file line number Diff line number Diff line change
Expand Up @@ -189,6 +189,8 @@ def get_cost_optimized_agent(
quality_config=quality_config,
enable_cascade=enable_cascade,
verbose=verbose,
enable_domain_detection=use_hybrid,
use_hybrid=use_hybrid,
)


Expand Down Expand Up @@ -300,6 +302,8 @@ def get_balanced_agent(
quality_config=quality_config,
enable_cascade=enable_cascade,
verbose=verbose,
enable_domain_detection=use_hybrid,
use_hybrid=use_hybrid,
)


Expand Down Expand Up @@ -410,6 +414,8 @@ def get_speed_optimized_agent(
quality_config=quality_config,
enable_cascade=enable_cascade,
verbose=verbose,
enable_domain_detection=use_hybrid,
use_hybrid=use_hybrid,
)


Expand Down Expand Up @@ -524,6 +530,8 @@ def get_quality_optimized_agent(
quality_config=quality_config,
enable_cascade=enable_cascade,
verbose=verbose,
enable_domain_detection=use_hybrid,
use_hybrid=use_hybrid,
)


Expand Down Expand Up @@ -622,6 +630,8 @@ def get_development_agent(
quality_config=quality_config,
enable_cascade=enable_cascade,
verbose=verbose,
enable_domain_detection=use_hybrid,
use_hybrid=use_hybrid,
)


Expand Down
Loading