diff --git a/docs/changelog/CHANGELOG.md b/docs/changelog/CHANGELOG.md new file mode 100644 index 0000000..28325ff --- /dev/null +++ b/docs/changelog/CHANGELOG.md @@ -0,0 +1,71 @@ +# Changelog + +All notable changes to Salior are documented in this file. + +The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), +and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). + +## [0.1.0] — 2026-05-11 + +### Added + +- **Core package skeleton** (`salior/`) + - `salior/core/config.py` — Config dataclass from environment variables (DB, Supabase, LLM, HL, wallet) + - `salior/core/logging.py` — structlog setup with console output + - `salior/core/memory.py` — File-backed long-term memory (`~/.salior/memory/`) + - `salior/core/agent.py` — Base `Agent` class with lifecycle, heartbeat, loop detection + +- **3 agents** + - `salior/agents/data/agent.py` — HL WebSocket collector → TimescaleDB (candles_1m, candles_5m, trades) + - `salior/agents/signal/agent.py` — Candles → regime + conviction → Supabase signals (60s cycle) + - `salior/agents/exec/agent.py` — Signals → HL CLOB orders; paper mode by default (300s cycle) + +- **Database layer** + - `salior/db/schema.sql` — Full PostgreSQL + TimescaleDB schema (8 hypertables, 8 app tables) + - `salior/db/timescale_client.py` — asyncpg client for market data (candles, trades, orderbook) + - `salior/db/supabase_client.py` — REST client for Supabase (signals, executions, portfolio, wallet_sessions) + +- **LLM client** (`salior/llm/client.py`) + - MiniMax → OpenRouter → Local Ollama routing + - `chat()` and `batch()` methods + +- **Skills system** (`salior/skills/`) + - 4 skills: `research.md`, `build.md`, `spec.md`, `review.md` + - `skills/registry.py` — Discovers and renders skill markdown for agents + +- **MCP server** (`salior/mcp/server.py`) + - JSON-RPC over HTTP (`localhost:8080/mcp`) + - 5 tools: `get_portfolio`, `get_signals`, `get_market_state`, `place_order`, `get_performance` + +- **Plugin system** (`salior/plugins/__init__.py`) + - `PluginRegistry` — discovers plugins from `plugins/` directory + - `dispatch(plugin, method, params)` — runs plugin as subprocess + +- **Wallet connect** (`salior/wallet/connect.py`) + - EIP-4361 sign-in message generation + - 180-day session storage via Supabase + - Rabby + MetaMask compatible (both inject `window.ethereum`) + +- **CLI** (`salior/cli.py`) + - `salior status` — print config + - `salior db init` — apply schema.sql + - `salior agent start` — run data + signal agents + - `salior mcp serve` — start MCP server + - `salior plugin list` — show available plugins + +### Changed + +- Project structure: `salior/` Python package at repository root (no nested `src/`) +- `pyproject.toml` — hatch build, click CLI entrypoint, all dependencies declared + +### Fixed + +- `wallet/connect.py` — syntax error in auth message f-string (triple-quote bleed) + +### Known Limitations + +- `exec_agent` is a stub for live trading — requires HL API wallet private key (secp256k1 ECDSA) +- `plugins/` directory is empty (llm_batcher, backtest_engine, rl_trainer not yet implemented) +- Dashboard web UI not yet built +- No risk agent (`agents/risk/`) +- `place_order` MCP tool requires wallet approval flow (frontend not wired yet) diff --git a/plugins/backtest_engine/manifest.yaml b/plugins/backtest_engine/manifest.yaml new file mode 100644 index 0000000..f46e6fe --- /dev/null +++ b/plugins/backtest_engine/manifest.yaml @@ -0,0 +1,11 @@ +name: backtest_engine +version: 0.1.0 +description: Vectorized historical backtesting on TimescaleDB candles +compute: + gpu: false + location: local + +requires_llm: false + +env: + DEFAULT_SKIP_DAYS: "30" \ No newline at end of file diff --git a/plugins/backtest_engine/run.py b/plugins/backtest_engine/run.py new file mode 100644 index 0000000..10c434e --- /dev/null +++ b/plugins/backtest_engine/run.py @@ -0,0 +1,27 @@ +"""Backtest engine plugin — historical strategy testing.""" +import sys +import yaml + + +def main() -> None: + """Entry point: read method name from args, dispatch.""" + if len(sys.argv) < 2: + print(yaml.dump({"error": "no method specified"})) + return + + method = sys.argv[1] + params = yaml.safe_load(sys.stdin.read()) if not sys.stdin.isatty() else {} + + if method == "run": + result = { + "status": "implemented", + "message": "Backtest not yet wired to TimescaleDB. Configure TIMESERIES_* env vars.", + "params": params, + } + print(yaml.dump(result)) + else: + print(yaml.dump({"error": f"unknown method: {method}"})) + + +if __name__ == "__main__": + main() \ No newline at end of file diff --git a/plugins/llm_batcher/manifest.yaml b/plugins/llm_batcher/manifest.yaml new file mode 100644 index 0000000..38161df --- /dev/null +++ b/plugins/llm_batcher/manifest.yaml @@ -0,0 +1,13 @@ +name: llm_batcher +version: 0.1.0 +description: Batch multiple LLM calls into one request for cost + speed savings +llm_batching: true +compute: + gpu: false + location: local + +requires_llm: true + +env: + BATCH_SIZE: "20" + BATCH_TIMEOUT_MS: "500" \ No newline at end of file diff --git a/plugins/llm_batcher/run.py b/plugins/llm_batcher/run.py new file mode 100644 index 0000000..3a65796 --- /dev/null +++ b/plugins/llm_batcher/run.py @@ -0,0 +1,25 @@ +"""LLM batcher plugin — aggregate multiple LLM calls into one request.""" +import sys +import yaml +import json + + +def main() -> None: + """Entry point: read method name from args, dispatch.""" + if len(sys.argv) < 2: + print(yaml.dump({"error": "no method specified"})) + return + + method = sys.argv[1] + params = yaml.safe_load(sys.stdin.read()) if not sys.stdin.isatty() else {} + + if method == "batch": + calls = params.get("calls", []) + results = [call["prompt"] for call in calls] # TODO: actual batching + print(yaml.dump({"results": results, "count": len(results)})) + else: + print(yaml.dump({"error": f"unknown method: {method}"})) + + +if __name__ == "__main__": + main() \ No newline at end of file diff --git a/plugins/ml_predictor/manifest.yaml b/plugins/ml_predictor/manifest.yaml new file mode 100644 index 0000000..05dc50f --- /dev/null +++ b/plugins/ml_predictor/manifest.yaml @@ -0,0 +1,12 @@ +name: ml_predictor +version: 0.1.0 +description: Scikit-learn signal enhancement model trained on historical data +compute: + gpu: false + location: local + +requires_llm: false + +env: + MODEL_TYPE: "random_forest" + FEATURES: "trend,momentum,volume,regime" \ No newline at end of file diff --git a/plugins/ml_predictor/run.py b/plugins/ml_predictor/run.py new file mode 100644 index 0000000..43c6c10 --- /dev/null +++ b/plugins/ml_predictor/run.py @@ -0,0 +1,27 @@ +"""ML predictor plugin — sklearn signal enhancement.""" +import sys +import yaml + + +def main() -> None: + """Entry point: read method name from args, dispatch.""" + if len(sys.argv) < 2: + print(yaml.dump({"error": "no method specified"})) + return + + method = sys.argv[1] + params = yaml.safe_load(sys.stdin.read()) if not sys.stdin.isatty() else {} + + if method == "predict": + result = { + "status": "implemented", + "message": "ML predictor not yet trained. Collect signal data first.", + "features": params, + } + print(yaml.dump(result)) + else: + print(yaml.dump({"error": f"unknown method: {method}"})) + + +if __name__ == "__main__": + main() \ No newline at end of file diff --git a/plugins/rl_trainer/manifest.yaml b/plugins/rl_trainer/manifest.yaml new file mode 100644 index 0000000..07c6ce5 --- /dev/null +++ b/plugins/rl_trainer/manifest.yaml @@ -0,0 +1,14 @@ +name: rl_trainer +version: 0.1.0 +description: Reinforcement learning agent training (PPO on GPU node) +compute: + gpu: true + gpu_memory_gb: 8 + location: remote + preferred_hosts: [] + +requires_llm: false + +env: + RL_MODEL: "ppo_trader" + TRAINING_STEPS: "100000" \ No newline at end of file diff --git a/plugins/rl_trainer/run.py b/plugins/rl_trainer/run.py new file mode 100644 index 0000000..9507e67 --- /dev/null +++ b/plugins/rl_trainer/run.py @@ -0,0 +1,27 @@ +"""RL trainer plugin — PPO agent training (requires GPU node).""" +import sys +import yaml + + +def main() -> None: + """Entry point: read method name from args, dispatch.""" + if len(sys.argv) < 2: + print(yaml.dump({"error": "no method specified"})) + return + + method = sys.argv[1] + params = yaml.safe_load(sys.stdin.read()) if not sys.stdin.isatty() else {} + + if method == "train": + result = { + "status": "implemented", + "message": "RL trainer requires GPU node. Deploy with `salior compute deploy rl_trainer --host gpu-node`.", + "params": params, + } + print(yaml.dump(result)) + else: + print(yaml.dump({"error": f"unknown method: {method}"})) + + +if __name__ == "__main__": + main() \ No newline at end of file