Skip to content

feat: plumb tool-reported researchability through to the strategy layer - #1035

Open
jmoreira-valory wants to merge 4 commits into
mainfrom
feat/mech-market-context
Open

feat: plumb tool-reported researchability through to the strategy layer#1035
jmoreira-valory wants to merge 4 commits into
mainfrom
feat/mech-market-context

Conversation

@jmoreira-valory

@jmoreira-valory jmoreira-valory commented Sep 2, 2026

Copy link
Copy Markdown
Contributor

feat: plumb tool-reported researchability through to the strategy layer

Summary

The mech's optional researchability signal (0-1, "can research even answer
this question?") travels to the betting strategy and nowhere else:

  • _get_decision coerces the raw response's optional researchability
    (absent / boolean / non-numeric / out-of-range all degrade to None, and a
    stale value can never leak across markets) onto a behaviour attribute. A
    value that is present but rejected logs a warning, so "tool never
    implemented the field" and "implemented and broken" are distinguishable;
    an absent key stays quiet.
  • get_bet_amount forwards it into the strategy run(**kwargs) next to
    confidence (already plumbed, unread). Shipped strategies ignore it; a
    future sizing strategy (kelly_shrink -- continuous stake shrinkage toward
    the price, the market-aware study's forward-test candidate) can read it
    with zero further trader changes.

Deliberately absent, per the study evidence (mech-predict #450) and review
discussion below:

  • No gate: hard researchability gates were pre-registered, tested, and
    failed out-of-sample (the discarded bets were still profitable; the tool
    over-reports the field). The "skip hopeless markets" effect already emerges
    from the tool's price-anchored probability meeting the existing min-edge
    rule.
  • No local recording: PredictionResponse and the stored-bets
    serialization are untouched -- every delivery already records the signal
    permanently on IPFS/subgraph, so offline analysis needs nothing from the
    trader. The only thing that cannot be recovered after the fact is the
    signal at bet-sizing time -- which is exactly and only what this PR plumbs.
  • No new config parameter, no new decision path, tool-selection policy
    untouched.

Request side

Unchanged: the trader already attaches request_context (market price, close
time, resolution rules, ...) to every mech request via
sampled_bet.to_request_context().

Tests

  • Parametrized _get_decision capture matrix (0.35 kept; absent / bool /
    out-of-range / non-numeric -> None; stale-value reset pinned; rejected
    values warn, absent stays quiet).
  • Parametrized forwarding test (0.35 and None reach the strategy kwargs).
  • Both mutation-verified: dropping the stash line or the forwarding kwarg
    fails a test. 2080 tests green; isort/black/pylint/mypy/flake8/darglint
    green; autonomy packages lock clean, third-party CIDs untouched.

🤖 Generated with Claude Code

@jmoreira-valory

Copy link
Copy Markdown
Contributor Author

Review verdict: DO NOT MERGE as a gate — this implements the exact design the market-aware study falsified, and the gate's exit path has a policy side effect. (Posted as a comment because GitHub blocks request-changes on one's own PR.)

1. The hard researchability gate is empirically contraindicated (blocking)

The A/B study that shipped superforcaster-market-aware (mech-predict #450; full report in trader-analysis 2026-08-24/market-aware-tool/2026-08-31-refined-study/) pre-registered and tested exactly this rule — "only bet when the tool says the question is researchable" — and it failed on the only sample with enough bets:

  • On 163 bets, the researchability rule KEPT bets earning +38.08% and THREW AWAY bets earning +19.79% — the discarded bets were still profitable. Every other candidate threshold was neutral or backwards (evidence_quality >= 0.6 discarded the single best slice, +54.36%).
  • The tool over-reports researchability (154/163 of its bets labelled researchable, including 23 of the 32 an independent classifier calls not-researchable) — the field is not calibrated enough to be a binary gate input.
  • The shipped recommendation, verbatim: "keep the existing betting rules exactly as they are; record the four extra outputs without acting on them; and test on live markets before turning any of them into a filter."

The validated forward candidate is continuous stake shrinkage inside the strategy (kelly_shrink: p' = m + lam*(p - m), lam = lam_min + (1 - lam_min) * researchability) — low researchability shrinks the edge and the existing min-edge gate drops the bet naturally. A binary skill-level gate is both the wrong shape and the wrong layer. Default-0.0 makes this PR inert today, but it institutionalizes a knob whose only activation is the measured-harmful move — a dead-config hazard.

2. Gate-fire aliases with tool failure in the selection policy (bug)

The gate exits via return None from _get_decision(). Downstream, policy.tool_responded(...) runs only under if prediction_response is not None: — so every gated market skips the policy update entirely, exactly like a parse failure. An epistemic "researchable but do-not-trade" becomes indistinguishable from "tool returned garbage" to EGreedyPolicy: enable the gate and the reporting tool's response accounting is suppressed on precisely the markets where it was most candid. If any gate survives (see 1: it should not), it must fire after the policy update, on a dedicated code path.

3. Nits

  • models.py: the "no-effect value / startup" comment paragraph is duplicated (copy-paste artifact).
  • isinstance(researchability, (int, float)) accepts bool (True < threshold comparisons); the mech-side coercion deliberately rejects bools — mirror that.
  • The threshold is unvalidated (nothing stops 1.5 = gate everything from a gating tool).

Suggested resolution

Repurpose the PR to what the study actually calls for: pass the raw researchability (and siblings) through to the strategy layer (e.g. into the strategy run(**kwargs) dict next to confidence, which is already plumbed and unread) and log it — no gate, no new decision path. That gives kelly_shrink a forward-test path as a customs strategy package with zero skill-level behavior change. Alternatively close, and keep the signal logging-only until live data justifies more.

Reworked from the original gate design per the market-aware study
evidence (mech-predict #450): hard researchability gates were
pre-registered, tested, and failed out-of-sample (the discarded bets
were still profitable, and the tool over-reports the field), so the
trader keeps its decision path byte-identical and the signal becomes
ADVISORY plumbing instead:

- PredictionResponse gains an optional `researchability` field:
  absent, boolean, non-numeric, or out-of-range values all degrade to
  None, so responses from every existing tool parse exactly as before.
- decision_receive logs the signal when present and forwards it to
  get_bet_amount, which passes it into the strategy kwargs next to
  `confidence` -- shipped strategies ignore it; a future sizing
  strategy (kelly_shrink: continuous stake shrinkage toward the price,
  the study's forward-test candidate) can read it with no skill change.
- No new config, no gate, no new decision path; the tool-selection
  policy update is untouched.

Tests: parsing matrix (float kept / absent / bool / out-of-range ->
None) and a parametrized forwarding test (0.35 and None) verified by
mutation (dropping the kwarg fails it). 254 tests green; lint sextet
green; packages lock clean with third-party untouched.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@jmoreira-valory
jmoreira-valory force-pushed the feat/mech-market-context branch from 7590583 to 36330fc Compare September 2, 2026 17:17
@jmoreira-valory jmoreira-valory changed the title feat: gate mech responses on tool-reported researchability feat: plumb tool-reported researchability through to the strategy layer Sep 2, 2026
@jmoreira-valory

Copy link
Copy Markdown
Contributor Author

Reworked per the review above: the branch is reset onto main and force-pushed as 36330fc63 — gate removed, signal now advisory plumbing to the strategy kwargs (details in the updated PR description). Diff shrank to ~60 substantive lines + CID cascade; no new config. The kelly_shrink sizing strategy can now be forward-tested as a customs package with no further trader change.

…ixture

The optional field now round-trips through the stored bets (deliberate:
the signal is recorded), so the serialize_bets golden string gains
"researchability": null. Full market_manager+decision_maker suites:
2083 passed.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

@OjusWiZard OjusWiZard left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Multi-perspective review (correctness, tests, silent failures, type design, comments, simplification).

One blocking issue, inline on bets.py:116: adding a field to PredictionResponse breaks BetsDecoder.hook's exact-key-set type discrimination, so every bets store written before this deploy decodes prediction_response as a plain dict. It fails silently at read time and crashes later in rebet_allowed for any market with an existing position. Reproduced, and a fix is prototyped and verified in that comment.

The rest are non-blocking: a latent TypeError in the reworked validator, an unobservable tool-misbehaviour path, and four comments/docstrings that restate what the PR description and history already record.

Verified alongside this: 254 tests pass, check-hash clean, and both shipped strategies take **kwargs with .get() so the extra kwarg is harmless. The parsing matrix itself is correct — bool, NaN, out-of-range, non-numeric and absent all degrade to None as designed. The decision path really is unchanged; the problem is purely the decoder that was only ever safe for Bet.

Comment thread packages/valory/skills/market_manager_abci/bets.py Outdated
Comment thread packages/valory/skills/market_manager_abci/bets.py Outdated
Comment thread packages/valory/skills/market_manager_abci/bets.py Outdated
Comment thread packages/valory/skills/market_manager_abci/bets.py Outdated
Comment thread packages/valory/skills/decision_maker_abci/behaviours/decision_receive.py Outdated
Comment thread packages/valory/skills/decision_maker_abci/behaviours/base.py Outdated
Comment thread packages/valory/skills/market_manager_abci/bets.py Outdated
Per review discussion: the mech delivery already records the signal
permanently (IPFS + subgraph), so the trader stores nothing. The value
now travels _get_decision -> behaviour attribute -> strategy kwargs:
PredictionResponse and the stored-bets serialization are untouched
(bets.py and its golden fixtures revert to main), the log line is gone,
and the only remaining purpose is the one that cannot be recovered
after the fact: a sizing strategy (kelly_shrink) reading the signal at
bet time. Coercion (bool / out-of-range / non-numeric -> None) is a
module helper with parametrized tests; stale values reset per response.
Both new tests mutation-verified. 2080 tests green.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@jmoreira-valory

Copy link
Copy Markdown
Contributor Author

Slimmed further per discussion: local recording removed (629a5a0fa). PredictionResponse and stored-bets serialization now revert to main untouched — the delivery layer already records the signal permanently on IPFS/subgraph. What remains is only the piece that cannot be reconstructed later: the signal reaching the strategy kwargs at bet-sizing time, for a future kelly_shrink forward test. No gate, no config, no recording — pure plumbing.

- Present-but-rejected researchability values now log a warning
  (distinguishing 'tool never implemented the field' from 'implemented
  and broken'); absent stays quiet. Asserted in the capture matrix and
  mutation-verified (removing the warning fails 6 cases).
- bool-subclasses-int note added to the coercion guard; design-rationale
  comments in base.py and the forwarding test docstring trimmed per the
  comment bar.
- The two blocking BetsDecoder findings and the validator-loop trap were
  resolved by the earlier scope reduction (629a5a0): bets.py and its
  serialization are byte-identical to main again.

2080 tests green; lint sextet green; lock clean, third-party untouched.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@jmoreira-valory

Copy link
Copy Markdown
Contributor Author

All 8 review threads addressed and resolved (details in-thread). Five targeted the pre-slim-down scope and were resolved by the 629a5a0fa reduction itself — including both blocking BetsDecoder findings (bets.py is byte-identical to main again, so no persisted-store compatibility risk exists). The three still-applicable ones are fixed in ffb47d5f0: present-but-rejected researchability values now warn (absent stays quiet, mutation-verified), the bool-subclasses-int note survives in the coercion guard, and the rationale comments/docstrings are trimmed per the comment bar. 2080 tests, lint sextet, lock clean.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants