Re-add the Discord badge (5 READMEs) and an issue-template contact removed
in 8346def, now pointing at the org-controlled HKUDS community server
(discord.gg/6TdQnT5xcF, guild 1296348098003734629, ~1.9k members) — the same
server linked from the HKUDS org profile and LightRAG — instead of the
compromised invite. Keeps GitHub Discussions as a second contact.
The discord.gg/2vDYc2w5 invite (in all 5 READMEs and the issue-template
contact links) resolves to a "Vibe-Trading" Discord server we no longer
control. It runs a fake Collab.Land wallet "verification" via
collab.layers-land.com — a throwaway domain registered 2026-06-18 — that
phishes users' crypto wallets. Remove every public pointer to it and replace
the issue-template community link with GitHub Discussions.
* feat(autopilot): add goal-hypothesis bridge for Research Autopilot Phase 1
New `run_research_autopilot` agent tool that connects the Hypothesis
Registry to the Research Goal runtime. Given a hypothesis_id, the tool
reads the hypothesis, creates a research goal with the thesis as
objective and backtest-relevant criteria, and returns a goal snapshot
with next-step hints.
Auto-registered via BaseTool subclass discovery.
Signed-off-by: Robin1987China <fanrenzhige@163.com>
* feat(autopilot): add generate_backtest_config tool for Phase 2
New `generate_backtest_config` tool auto-generates backtest config.json
from hypothesis metadata. Maps universe names (CSI 300, S&P 500, etc.)
to standard index codes and uses hypothesis.data_sources for the source
field. Writes config.json to a run directory and returns the path so the
agent can proceed to create signal_engine.py and call backtest.
Signed-off-by: Robin1987China <fanrenzhige@163.com>
* fix: harden research autopilot config generation
---------
Signed-off-by: Robin1987China <fanrenzhige@163.com>
Co-authored-by: Robin1987China <fanrenzhige@163.com>
Co-authored-by: Haozhe Wu <haozhe_wu@connect.hku.hk>
* feat(loaders): add local data loader for CSV/Parquet/DuckDB files
- New 'local' loader reads OHLCV from user-configured CSV/Parquet/DuckDB sources
- Config file at ~/.vibe-trading/data-bridge/config.yaml maps symbols to files
- Symbols prefixed with 'local:' auto-route to the local data source
- Integrated into loader registry with fallback chain support
Signed-off-by: Robin1987China <fanrenzhige@163.com>
* fix: allow duckdb local loader without path
---------
Signed-off-by: Robin1987China <fanrenzhige@163.com>
Co-authored-by: Robin1987China <fanrenzhige@163.com>
Co-authored-by: Haozhe Wu <haozhe_wu@connect.hku.hk>
- Replace assistant-role handoff messages with user-role in agent loop
to avoid "assistant message prefill" rejection on Anthropic Claude Opus 4.8+
- Allow MOONSHOT_USER_AGENT env var to override Kimi/Moonshot User-Agent header
for compatibility with Kimi Coding API endpoint
Closes#246, Closes#204
Signed-off-by: wesley <wesley87@wesley87deMacBook-Pro.local>
Co-authored-by: wesley <wesley87@wesley87deMacBook-Pro.local>
Add a first-class `glm` alias of the existing `zhipu` provider so a model
name like `glm-4.6`/`glm-5.1` resolves to the Zhipu capability record and
base URL instead of silently falling back to the OpenAI slot.
- capabilities.py: share one `_ZHIPU_CAPABILITIES` record between `zhipu`
and the new `glm` alias (mirrors kimi/moonshot), and infer `zhipu` for
model ids starting with `glm`.
- llm_providers.json: register a `glm` entry aliasing the Zhipu env names
and default model so onboarding/diagnostics surface it.
- tests: cover the alias, model-name inference, env-name resolution, the
unchanged openai fallback, and the new registry default.
Signed-off-by: Matt Van Horn <455140+mvanhorn@users.noreply.github.com>
Co-authored-by: Matt Van Horn <455140+mvanhorn@users.noreply.github.com>
Require a valid bearer token (not just loopback peer IP) for settings WRITE endpoints (/settings/llm, /settings/data-sources) when API_AUTH_KEY is configured, closing a DNS-rebinding / loopback-trust bypass while keeping zero-config local dev writes.
Also reconciles the DNS-rebound security tests across the #241-#245 sprint: now that #242's rebound-host middleware 403s loopback requests with an attacker-controlled Host before they reach the route, the end-to-end rebound tests for /swarm/runs and /sessions/messages (#243) and /settings/llm (#245) assert the request is rejected at the host-gate layer (403) and never reaches the shell-tool / settings-write decision. Full suite: 3217 passed.
Symptom 2 of #229: clicking Stop during a run did nothing, or only took
effect after a long delay. The cancel signal (_cancelled bool) was polled
only at the top of the ReAct iteration loop, so a Stop pressed while the
agent was mid-LLM-stream or executing tools was ignored until the entire
current iteration — the whole streamed answer plus all its tool calls —
finished and the next iteration check ran.
Add cooperative cancel checkpoints at the granularity the user actually
waits on:
- Switch _cancelled to a threading.Event (_cancel_event) so the flag is
safe to poll from the streaming thread.
- Thread a should_cancel predicate into ChatLLM.stream_chat; the per-chunk
loop breaks as soon as it is set, so a live stream stops at the next
token instead of running to completion.
- After the stream returns, check the flag before processing tool calls:
a cancel during streaming ends the run immediately and discards the
partial turn (no tools run).
- Check the flag at the top of _process_tool_calls and between tool batches
in _batch_execute, so a multi-tool turn stops after the current tool
rather than launching the rest.
Limitation (documented in the PR): a connection that hangs before emitting
any chunk leaves the worker blocked in a socket read, which Python cannot
interrupt cooperatively from another thread; that case stays bounded by the
existing client timeout (TIMEOUT_SECONDS). Cancel now lands promptly in all
the normal "working" states (tokens streaming, tools running).
Tests: chat-level should_cancel breaks the chunk loop (and absence is a
no-op); loop-level cancel mid-stream ends the run as cancelled and skips
tool execution. Full suite: 3199 passed, 2 skipped.
Symptom 1 of #229: navigating away from the chat page and back often made
the agent "stop working". The page run is server-side and the frontend only
subscribes via SSE; the unmount cleanup tore down that stream, but the
navigation effect only (re)subscribed when the URL session differed from the
store session. Returning to the SAME session hit neither branch, so SSE was
never reopened — in-flight events stopped arriving and the run looked frozen
until the 90s safety timeout flipped it to idle.
The backend already buffers events per session and replays them on connect
(GET /sessions/{id}/events?replay=active), and setupSSE already requests it;
the only defect was the missing re-subscribe on same-session remount.
Add that branch: re-hydrate like a reload — reset the transient streaming
view first (so replay=active rebuilds the in-flight turn from the ring buffer
instead of duplicating deltas onto the preserved text), refresh committed
history (covers an attempt that finished while away), then re-subscribe.
Symptom 2 (Stop ineffective during a hung/long turn) is cooperative-cancel
granularity in the agent core and is tracked separately.
Verified: tsc clean, frontend build passes, existing store/useSSE/Runtime
tests green (42).
GitHub forces Node 20 actions onto the Node 24 runtime starting 2026-06-16
and removes Node 20 from runners on 2026-09-16. Bump the three first-party
actions to their latest Node-24-native majors across all workflows:
- actions/checkout v4 -> v6
- actions/setup-python v5 -> v6
- actions/setup-node v4 -> v6
Inputs are unchanged (node-version "20" is the frontend JS runtime, not the
action runtime). Third-party cloudflare/wrangler-action is out of scope.
web_search relied on ddgs's default 'auto' backend, which leads with
DuckDuckGo — heavily rate-limited from cloud/shared IPs. When DDG
threw, the whole tool returned ❌ (issue #231) even though Google/
Bing/Brave/Mojeek/Yahoo were reachable.
- Pass an explicit ordered backend list so ddgs falls through a
throttled engine to the next; override via VIBE_TRADING_SEARCH_BACKENDS
- Retry transient failures with linear backoff (3 attempts)
- Treat "No results found" as an ok+empty envelope, not an error
- On persistent failure, return an actionable message (retry / set
backend env / use read_url) instead of a bare exception string
- Report the backend list used in the ok envelope
Tests: new tests/test_web_search_tool.py (6 cases, mocked ddgs) cover
multi-backend pass-through, env override, retry-then-succeed,
no-results, persistent-failure message, and max_results cap.
The baostock loader previously only accepted tushare-style codes (601398.SH)
and silently rejected baostock-native codes (sh.601398) — the format baostock
uses in its own docs. _is_a_share() now accepts both (case-insensitive), and
_fetch_one() passes native codes through unchanged while still converting
tushare-style suffixes. Adds 10 unit tests.
Thanks to @bhlt for the fix.
Malformed values like CCXT_TIMEOUT_MS=abc / OKX_TIMEOUT_S=abc raised ValueError at import time and could block startup. Add shared positive_env_int/float helpers in loaders/base.py; invalid/blank/non-positive timeout/budget values now warn and fall back to defaults. Valid values still honored.
Closes#227.
yfinance download(end=...) is exclusive while the loader API treats end_date as inclusive, so the final trading day was dropped. Pass end_date + 1 day to the yfinance call (bulk + single-symbol fallback); cache lookups/writes keep the original requested range.
Closes#226.
Add opt-in chart payload modes for the Run Detail endpoint and UI. Default /runs/{id} stays byte-for-byte unchanged; ?chart_payload=summary returns metadata + chart_symbols (no bars/markers); ?chart_symbol=SYM returns one symbol's bars+markers. Run Detail UI renders summary first, then progressively loads selected symbols with loading/progress + cancel. New copy localized en + zh-CN.
Refs #215.
Persist provider-reported AgentLoop usage as a run-scoped artifact (<run_dir>/llm_usage.json) and surface it additively on /runs/{id}. Provider-reported tokens only — no prompt/content capture, no price estimation. Behind the existing usage guard so providers without usage_metadata write nothing. Live llm_usage SSE event keys unchanged.
Refs #214.
Builds on @Shizoqua's verified provider core (#201) and resolves the three
open-box blockers from review:
- No built-in feed catalogue: the shipped DEFAULT_FEEDS routes (/stock/notice,
/stock/news) don't exist in RSSHub, so a default run scored every bar 0.0.
Feeds are now declared explicitly in config (event_feeds = list of
{name, route_template, event_type, code_style}); DEFAULT_FEEDS is empty, so
there is nothing to silently rot as RSSHub routes evolve.
- Per-symbol code formatting: a feed declares a code_style
(raw / bare / exchange_prefix), so dotted backtest codes (600519.SH) are
rewritten to the form a route needs (e.g. SH600519 for xueqiu-style routes).
- No silent all-zero scores: each fetch failure is logged per feed/code, and
query_events raises EventProviderError when every fetch failed (configured
but fully unreachable) instead of returning an all-zero event_score that
reads as "sentiment considered, no signal". A reachable feed that returned
no items stays a legitimate empty result.
PIT discipline, defusedxml hardening, the pluggable scorer, and the
engine/runner wiring are otherwise unchanged. Adds 12 tests for the code
styles, config parsing/validation, and the loud-failure path.
Thanks @Shizoqua (#201) for the provider core.
Co-authored-by: Lanre Shittu <136805224+Shizoqua@users.noreply.github.com>
A-share data sources (free, no-auth, bypass eastmoney CDN IP blocks):
- baostock loader (TCP protocol) + tencent loader (HTTP), both wrapped with
cached_loader_fetch like the mootdx/akshare loaders so they honor the opt-in
local cache
- registered into the a_share fallback chain (tushare -> mootdx -> baostock ->
tencent -> akshare); auto-detect default source stays tushare
- pyproject [ashare] extra for the optional baostock dependency
Frontend i18n (English default, sidebar toggle to Chinese, persisted):
- i18next + react-i18next + browser-languagedetector; first visit defaults to
English (localStorage-only detection, no navigator sniff), the toggle persists
the choice across reloads
- en / zh-CN locales at full key parity, including the Runtime page
- i18n initialized in the test setup so useTranslation resolves real strings;
restored the exported METRIC_LABELS so formatters consumers/tests keep working
Thanks @skloxo (#217) for the i18n layer and the A-share data sources.
Co-authored-by: skloxo <skloxo@users.noreply.github.com>
Exit interactive CLI shows the session ID so users can copy-paste
`vibe-trading resume <id>` to resume later. The resume subcommand
loads a specific session by ID and enters the interactive loop with
full history — messages are persisted through the existing
_run_one_turn / _append_message path.
Signed-off-by: zwrong <1003912034@qq.com>
A cluster of provider reports traced to one root: every OpenAI-compatible
provider shared a single shim that applied DeepSeek/Kimi/Gemini quirks
globally and silently swallowed stream failures.
- Provider capability layer (capabilities.py): reasoning capture/replay,
Gemini thought signatures, Kimi User-Agent (version from package
metadata), OpenRouter reasoning body — gated per provider, no
cross-contamination
- Optional native DeepSeek adapter via VIBE_TRADING_DEEPSEEK_ADAPTER
(auto/native/openai-compatible) + 'deepseek' extra (langchain-deepseek)
- ChatLLM.stream_chat: silent fallback to chat() removed; failures raise
ProviderStreamError with redacted provider/model context; one retry for
transient errors in AgentLoop + swarm worker (deterministic 4xx fail fast)
- Empty model response diagnosed as empty_model_response instead of
'reached max iterations'
- reasoning_delta SSE event (throttled to 1/s, first chunk immediate) +
frontend 'Reasoning…' indicator; reasoningActive reset on safety timeout
- SSE heartbeats carry no replay id; replay falls back to full buffer on
unknown last-event-id; read-only tools get a hard timeout
(VIBE_TRADING_TOOL_TIMEOUT_SECONDS) instead of hiding behind heartbeats
- 'vibe-trading provider doctor' prints redacted provider/model/package/
proxy diagnostics; preflight includes the same hint
- kimi-k2.x temperature=1 requirement auto-applied; Kimi path verified
end-to-end against the live API (kimi-k2.6 tool calls + strict
multi-turn reasoning replay)
Refs #208#203#195#204
Three interrelated fixes for trace.jsonl field consistency:
1. Field order — every entry now has `iter` immediately after `type`.
This was already the convention for tool_call, tool_result, thinking,
answer, and cancelled. Fixed start, message (user+assistant), and end
to follow the same pattern.
2. Iteration values — start and message (user) previously used
`self._run_iteration` (which is 0 before the loop begins), so the
first turn showed iter=0 while the first loop iteration showed iter=1.
Changed to `self._run_iteration + 1` so they share the same iter as
the first loop iteration. end now carries `iter` in addition to
`iterations` (the per-run count), so it participates in iteration
grouping like every other entry.
3. Resume ordering — the iteration-counter restore logic (reading max
iter from an existing trace to continue a session) was placed AFTER
start/message were written, so resumed sessions had iter=0 on those
entries. Moved the resume block before the write calls.
Tests added (TestTraceFieldConsistency):
- test_tool_result_iter_second — write_tool_result() dict order
- test_generic_write_preserves_type_then_iter — generic write order
- test_all_entry_types_carry_iter — 10 entry types all have iter at
position 1; catches future regressions
Closes the long-standing gap where start/message/end could not be
attributed to a specific turn in multi-turn sessions.
Signed-off-by: zwrong <1003912034@qq.com>
All user-level agent state lives under ~/.vibe-trading in the container
(persistent memory, sessions.db search index, user-created skills,
shadow accounts, hypothesis registry, broker connector config), but
docker-compose only mounted volumes for web sessions and backtest runs.
Every `docker compose up --build` therefore recreated the container and
silently wiped the user's memory store (#197). Swarm run history and
uploads were lost the same way.
- Mount vibe-home -> /home/vibe/.vibe-trading, plus vibe-swarm-runs and
vibe-uploads.
- Pre-create /home/vibe/.vibe-trading owned by the service user in the
Dockerfile so the named volume inherits writable permissions (a named
volume mounted onto a missing path is created root-owned and the
non-root user cannot write).
- README: document what persists across updates.
Verified end-to-end: memory written via PersistentMemory in one
container is intact in a fresh container on the same volume; the
counterfactual run without the volume reproduces the wipe.
Fixes#197.
* fix(docker): reach host Ollama via host.docker.internal
Inside the container, OLLAMA_BASE_URL=http://localhost:11434 resolves to
the container itself, so the agent's preflight check and AgentLoop fail
with "Connection refused" even when Ollama is running on the host.
Override OLLAMA_BASE_URL to http://host.docker.internal:11434 in the
compose service (leaving agent/.env correct for bare-metal runs) and add
an extra_hosts host-gateway mapping so the same file also works on Linux.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
* fix(docker): parameterize OLLAMA_BASE_URL and document host networking
Address PR review feedback:
- Make OLLAMA_BASE_URL overridable via Compose variable substitution
(${OLLAMA_BASE_URL:-http://host.docker.internal:11434}) so it can be
changed without editing the compose file when Ollama runs elsewhere.
- Note the Docker Engine >= 20.10 / Compose v2 requirement for the
host-gateway mapping, both inline and in the README Docker section.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
---------
Co-authored-by: Shah Newaz Khan <shah.khan@lexful.ai>
Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>