Without a TUSHARE_TOKEN, val:* columns degraded to all-NaN panels (and
before #1788's NaN guard, the run crashed outright). #1789 follow-up #3
asks for the free path: serve pe_ttm/pb/ps_ttm from baostock
query_history_k_data_plus daily bars, which need no token at all.
- _load_baostock_valuation maps val fields to baostock peTTM/pbMRQ/psTTM
and reuses the bounded socket guard from the OHLCV baostock loader
- load_valuation_panel now resolves tushare to None when no token is
configured (instead of raising) and retries unserved fields against
the baostock fallback; pe/dv_ttm stay NaN rather than being proxied
- tushare remains the primary source; baostock only serves fields
tushare could not (missing token or empty daily_basic)
- new tests pin the ladder: fallback serving, tushare-outage fallback,
no-token-no-baostock NaN, unserved fields not proxied, baostock
outage NaN; the existing missing-token test pins baostock off to
stay offline
Closes#1789 follow-up #3.
Co-authored-by: Jackzigen <zigenhan@gmail.com>
Six fix PRs (#1713-#1718) each appended a new row to the module contract
table instead of extending the existing row, leaving four modules
duplicated: credit, timeseries, factormodel·eventstudy and
multipletesting·crossvalidation. Merged each pair into one row keeping
the extended contract wording; no module lost coverage and no wording
was changed beyond the merge.
Adversarial review follow-ups on #1725:
- The bulk wiring duplicated the loader import block in 17 files
(identical adjacent 'from backtest.loaders...' lines). Remove the
copies; ruff F811 never flagged them because both were used.
- _CONTRACT_REASONS was defined but never consulted, so a typo'd
reason string would pass silently. bad() now asserts membership.
- Wire the shared contract into the 5 remaining per-loader suites
(okx, wallex, zerodha, ccxt-perpetual, longbridge) so the claim
'every per-loader suite' holds. okx and wallex are canary symbols
in loader_health — exactly the drift-prone loaders to pin.
Full unit suite: 1813 passed, 71 skipped (4 OAuth/MCP env failures
and 6 akshare forex failures reproduce on clean upstream/main).
Signed-off-by: Jackzigen <Jackzigen@users.noreply.github.com>
Each per-loader test file pinned its canonical fixture with bespoke
assertions; the only shared schema check lived in the weekly health
lane. A data-source drift that renames/reorders columns or lets
non-finite values through now only breaks whichever bespoke assertions
happen to cover it, and can slip past review unnoticed.
Extract the canonical-frame checks from backtest/loader_health.py into
tests/loader_contract.py (one call, same failure vocabulary) and wire
it into every per-loader suite's canonical-frame test (24 files).
This is the first step of the #1723 roadmap: with every loader suite
asserting the same shared contract, a loader that emits a
non-conforming frame fails its own suite immediately instead of
waiting for the weekly health lane.
Closes#1723 (step 1/3)
Signed-off-by: Jackzigen <Jackzigen@users.noreply.github.com>
- Remove stale memory_summary= kwarg from test_system_prompt_format_succeeds
(str.format tolerates excess kwargs, so it silently passed while masking
a potential reintroduction of the field).
- Pin that the <agent-state> envelope stays a sibling prefix outside
<user-message> tags when loop.py goal-context wrapping is active.
Co-authored-by: Claude <noreply@anthropic.com>
WorkspaceMemory.to_summary() renders run_dir (fresh timestamp_uuid per
run) and tool counters (bumped every tool call). Rendering it into the
system prompt (## State section) meant the prompt bytes changed on every
turn and every tool call, invalidating the provider prefix cache for
everything after that point — including the cache_control block the
Anthropic adapter marks on the system message.
Move the volatile summary into the first user message wrapped in an
<agent-state> envelope (same convention as <recalled-memories>), drop
the ## State section from the template, and pin the new property with
tests: system prompt is byte-identical when only run_dir/counters
differ, and no counter or run-dir value reaches the system prompt.
Fixes#1707
Co-authored-by: Claude <noreply@anthropic.com>
The doc guards require every shipped connector to be named in all seven
README broker tables and paragraphs, the pinned broker count to match the
registry, and every portfolio-eligible connector to carry an explicit
compatibility tier.
- bump EXPECTED_BROKERS 18 -> 19 and every broker badge
- add an mStock row to each broker table (REST Type B, read-only live,
no paper account) and name it in the no-paper paragraph
- register mstock as experimental in the portfolio compatibility map
(Type B reply shapes stay unverified until the HTTP read mapping lands)
The remaining test_readme_counts failures are pre-existing on main (the
southbound tool batch updated counts without touching the READMEs) and
are unchanged by this patch.
Signed-off-by: Jackzigen <jackzigen@users.noreply.github.com>
Registry-only first cut for #1367: one built-in profile
mstock-live-rest-readonly (account.read + positions.read, readonly,
no order path), registry registration, _LIVE_CAPABLE tier entry on
the no-paper-environment reasoning (Robinhood/Scalable/KIS line),
README matrix row, and focused tests (14 passing).
HTTP client, auth flow, and holdings mapping are deliberately a
follow-up after profile shape confirmation.
Signed-off-by: Jackzigen <9687529+Jackzigen@users.noreply.github.com>
Review of the extraction (he-yufeng, non-blocking observation) flagged
that pre-extraction the degraded flag on the end event and result dict
keyed off self._released_fallback read after the ladder, so a
cancel/stall in the gap between fallback release and the ladder would
stamp degraded=True on a non-success terminal. Post-extraction the flag
only comes from success branches. Both release sites set final_content
first so has_output wins the ladder in every normal flow, and nothing
outside loop.py consumes the flag on failed runs — document the delta in
the module docstring rather than re-plumb, per review.
Signed-off-by: Jackzigen <9687529+Jackzigen@users.noreply.github.com>
Roadmap item 3 (#1624), next slice after #1636. The 107-line tail of
AgentLoop._run_bound — the terminal-status ladder (stall → cancelled →
no-progress → content-filter breaker → success → empty response →
max-iterations), the end trace event, and the result-dict assembly —
moves to a new src/agent/run_outcome.py as pure functions.
Behavior is unchanged. The side-effectful state-store transitions stay
in loop.py in their original order; every branch condition and message
string moved verbatim (the empty_model_response reason keeps its
provider/model/iteration context). loop.py re-exports the moved names.
The ladder is now directly unit-testable without stubbing a whole LLM:
test_run_outcome_module.py pins all seven branches, the lazy
pending-write-directive evaluation, and the re-export contract.
agent/tests: 16023 passed, 149 skipped (full suite, CI lanes included)
Signed-off-by: Jackzigen <9687529+Jackzigen@users.noreply.github.com>
Split loop.py along an existing seam (issue #1624, roadmap item 3 of 5).
The module-level helpers that judge a finished tool call move verbatim to
a new module, src/agent/tool_results.py:
- success/failure classification of a JSON result envelope
(_is_tool_success, _failure_code)
- detection of provider tool-call DSL left in final text
(_looks_like_tool_call_syntax + the two DSL regexes)
- the user-message target-file scan (_named_target_paths,
_TARGET_PATH_RE, _TARGET_ACTION_RE)
- run_dir normalization (_normalize_tool_run_dir)
- the replace-not-merge archive of a successful detached backtest into
the active run (_archive_backtest_result, _previously_archived,
_ARCHIVE_MANIFEST re-import)
loop.py re-exports every moved name, so existing
from src.agent.loop import ... imports keep resolving to the same
objects. The moved block is byte-for-byte identical to the old inline
code (verified against upstream/main before commit), and loop.py drops
the imports the block owned.
New test module pins the re-export contract: every moved name in loop
must be the very object in tool_results.
loop.py: 3,761 -> 3,541 lines.
Signed-off-by: Jackzigen <9687529+Jackzigen@users.noreply.github.com>
The commit-time adjustment path in _resolve_profile only guarded the
both-numeric branch, so three bypasses slipped through:
- type confusion: a rendered numeric limit (leverage 2) adjusted with a
string ("10") skipped the widening check and committed as-is
- bool masquerade: True/False are int subclasses and passed isinstance
- list fields: instruments/allowed_instruments had no comparison branch,
so whitelists could be widened at commit time
The ceiling re-check _profile_fits_ceilings had the same both-numeric
blind spot.
Now numeric limits reject non-numeric adjustment types outright, bool is
excluded from numeric handling, list limits must be subsets of the
rendered whitelist, other scalar types must be equivalent, and the
ceiling re-check fails closed on non-numeric pairs.
Add H19 regression tests covering string/bool/list widening and
narrowing, plus defense-in-depth ceiling checks.
Fixes part of the roadmap audit in #1207 (finding 19).