Add orchestrator training, channels, LiteLLM engine, and simplify learning taxonomy

Major changes across parallel sessions:

- Add orchestrator SFT & GRPO training subpackage (learning/orchestrator/)
  with episode types, multi-objective reward, prompt registry, policy model,
  RL environment, and registered learning policies
- Add structured THOUGHT/TOOL/INPUT/FINAL_ANSWER mode to OrchestratorAgent
- Add 15 channel backends (Discord, Slack, Telegram, Email, Webhook, IRC,
  Matrix, Teams, WhatsApp, Signal, Mattermost, BlueBubbles, Feishu,
  Google Chat, Webchat) with channel tools and config
- Add LiteLLM engine backend for unified LLM provider access
- Add RLM agent and REPL tool
- Remove ToolLearningPolicy — learning taxonomy now only targets
  Intelligence (LM weights/routing) and Agents (logic/ICL/tool strategies)
- Rename SFTPolicy to SFTRouterPolicy (backward-compat alias kept)
- Remove OpenClaw agent infrastructure (openclaw*.py, openclaw_bridge.py)
- Fix async streaming tests (asyncio.run vs deprecated get_event_loop)
- Fix server channel route tests (pytest.importorskip for optional fastapi)
- Track uv.lock for reproducibility
- Update CLAUDE.md and docs to reflect all changes

1676 tests pass, 37 skipped.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
Jon Saad-Falcon
2026-02-23 18:32:32 +00:00
co-authored by Claude Opus 4.6
parent a0961633a3
commit 8d538cd1b0
105 changed files with 16486 additions and 1858 deletions
-2
View File
@@ -31,7 +31,6 @@ def _mock_engine(content="Hello from engine"):
def _register_agents():
"""Re-register agents after registry clear."""
from openjarvis.agents.custom import CustomAgent
from openjarvis.agents.openclaw import OpenClawAgent
from openjarvis.agents.orchestrator import OrchestratorAgent
from openjarvis.agents.simple import SimpleAgent
from openjarvis.core.registry import AgentRegistry
@@ -40,7 +39,6 @@ def _register_agents():
("simple", SimpleAgent),
("orchestrator", OrchestratorAgent),
("custom", CustomAgent),
("openclaw", OpenClawAgent),
]:
if not AgentRegistry.contains(name):
AgentRegistry.register_value(name, cls)
+27 -20
View File
@@ -15,9 +15,9 @@ def _patch_channel(
send_return=True,
status_return=ChannelStatus.DISCONNECTED,
):
"""Return patches for load_config and OpenClawChannelBridge."""
"""Return patches for load_config and _get_channel."""
cfg = mock.MagicMock()
cfg.channel.gateway_url = "ws://127.0.0.1:18789/ws"
cfg.channel.default_channel = ""
bridge_instance = mock.MagicMock()
bridge_instance.list_channels.return_value = list_channels or []
@@ -27,11 +27,11 @@ def _patch_channel(
config_patch = mock.patch(
"openjarvis.core.config.load_config", return_value=cfg,
)
bridge_patch = mock.patch(
"openjarvis.channels.openclaw_bridge.OpenClawChannelBridge",
get_channel_patch = mock.patch(
"openjarvis.cli.channel_cmd._get_channel",
return_value=bridge_instance,
)
return config_patch, bridge_patch, bridge_instance
return config_patch, get_channel_patch, bridge_instance
class TestChannelHelp:
@@ -45,24 +45,26 @@ class TestChannelHelp:
class TestChannelList:
def test_list_with_channels(self) -> None:
config_p, bridge_p, _ = _patch_channel(list_channels=["slack", "discord"])
with config_p, bridge_p:
config_p, getch_p, _ = _patch_channel(
list_channels=["slack", "discord"],
)
with config_p, getch_p:
result = CliRunner().invoke(cli, ["channel", "list"])
assert result.exit_code == 0
assert "slack" in result.output
assert "discord" in result.output
def test_list_no_channels(self) -> None:
config_p, bridge_p, _ = _patch_channel(list_channels=[])
with config_p, bridge_p:
config_p, getch_p, _ = _patch_channel(list_channels=[])
with config_p, getch_p:
result = CliRunner().invoke(cli, ["channel", "list"])
assert result.exit_code == 0
assert "No channels available" in result.output
def test_list_connection_error(self) -> None:
config_p, bridge_p, inst = _patch_channel()
config_p, getch_p, inst = _patch_channel()
inst.list_channels.side_effect = ConnectionError("refused")
with config_p, bridge_p:
with config_p, getch_p:
result = CliRunner().invoke(cli, ["channel", "list"])
assert result.exit_code == 0
assert "Failed" in result.output or "refused" in result.output
@@ -70,25 +72,30 @@ class TestChannelList:
class TestChannelSend:
def test_send_success(self) -> None:
config_p, bridge_p, _ = _patch_channel(send_return=True)
with config_p, bridge_p:
result = CliRunner().invoke(cli, ["channel", "send", "slack", "Hello!"])
config_p, getch_p, _ = _patch_channel(send_return=True)
with config_p, getch_p:
result = CliRunner().invoke(
cli, ["channel", "send", "slack", "Hello!"],
)
assert result.exit_code == 0
assert "Message sent" in result.output
def test_send_failure(self) -> None:
config_p, bridge_p, _ = _patch_channel(send_return=False)
with config_p, bridge_p:
result = CliRunner().invoke(cli, ["channel", "send", "slack", "Hello!"])
config_p, getch_p, _ = _patch_channel(send_return=False)
with config_p, getch_p:
result = CliRunner().invoke(
cli, ["channel", "send", "slack", "Hello!"],
)
assert result.exit_code == 0
assert "Failed to send" in result.output
class TestChannelStatus:
def test_status_shows_info(self) -> None:
config_p, bridge_p, _ = _patch_channel(status_return=ChannelStatus.DISCONNECTED)
with config_p, bridge_p:
config_p, getch_p, _ = _patch_channel(
status_return=ChannelStatus.DISCONNECTED,
)
with config_p, getch_p:
result = CliRunner().invoke(cli, ["channel", "status"])
assert result.exit_code == 0
assert "disconnected" in result.output
assert "127.0.0.1" in result.output