feat(sl-hunting): v4e knowledge + session-state durability fix - #120
Merged
Conversation
Source: Intraday Hunter live session, 11 Aug 2026 (_JXirKMmI58, 9:25). This one is a LOSS, and it is encoded because it is one -- every prior addendum distilled a winning session, and a losing session shows which part of the method was load-bearing and which part was rationalisation. He named the disqualifying fact himself, twice, before entering: "neither the BUYER's stop losses are available nor the SELLER's" and "here not many traders were seated." He then traded a FORECAST of who would arrive -- a sharp drop tempts intraday sellers in, so the market should rise to take them out -- and the selling simply continued. Knowledge changes (all prose): - OPENING_DRIVE: WHICH CROWD THE OPEN RECRUITS DECIDES HOW BIG THE TRAP IS (gap-down recruits POSITIONAL sellers who hold overnight; a flat open recruits INTRADAY sellers only, so the same-shaped trap is smaller and perishable -- a genuine advance on v4d, independent of the outcome); A FORECAST OF WHO WILL ARRIVE IS NOT EVIDENCE OF WHO IS SEATED; A SHARP FIRST SLIDE BAITS, A SLOW ONE MEANS IT. - RISK: NAME THE LAST POINT, NOT ONLY THE STOP; DISCIPLINE IS ASYMMETRIC BETWEEN WINNERS AND LOSERS. The sharp-slide rule ships as a weak prior with its own counter-example attached: it is the read that lost him the session, so it is recorded as a tie-breaker and explicitly barred from being a trade premise. Two test markers, the second a drift guard asserting the forecasting rule still resolves to HOLD and still reconciles with v4c's MANUFACTURES MORE, so a later edit cannot turn it into a licence to predict a crowd into existence. Prompt 96,627 -> 101,087 chars (headroom 18,913). The doc addendum also records how our own agent traded the same session. It was SHORT all morning -- directionally RIGHT where IH was wrong -- and still lost the most of any strategy, because it took four short entries in 47 minutes and released three of them on premise-STALL judgements rather than stops (two held 1 and 2 minutes; a 24400-target trade was let go at 24469-24492 having never been stopped). That is the inverse of the failure v3y guards against, and it is flagged as a lessons-loop candidate rather than encoded as IH knowledge, because it is a property of our agent and not of the method. Session P&L is marked provisional: the runner was still live at 13:07 when this was written. SL Hunting's -2,647.75 is cross-checked against its own Result summary and matches exactly -- note MIRROR EXIT lines do not contain "| EXIT ", which under-counted the agent by 948.00 on a first pass. Gates: SL Hunting pytest 173 passed, full pytest 1130 passed, ruff clean. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Closes the follow-up recorded in ADR-0011. Three gates in Tests/Dependencies/test_repository_policy.py, each answering a different way documentation rots. 1. docs/hld/system-overview.md joins the existing architecture gate, so the HLD must keep both optional agents visible and must not let the ~27 core roster count read as the enabled total. The HLD did not actually pass that check as written: it named the agents only as class names in an ASCII diagram (SLHuntingAIWorker) and as file links, and said "two independently opt-in AI agents" without naming either. That is a real gap, not a test-matching problem -- a reader of the system overview could not tell what the two agents were. Section 1 now carries a short table naming both, their provider, their default-off flag and their LLD. 2. Every committed ADR and LLD must be linked from docs/README.md, and the index must not link a file that does not exist. Checked in BOTH directions: an orphaned document is never read and quietly goes stale, and a link left by a rename sends readers to a 404 and makes the whole index untrustworthy. 3. Every relative link inside docs/ must resolve. 97 links today. Renames are the normal way these break, and a broken link is invisible until somebody follows it, so it is checked mechanically rather than by review. docs/superpowers/ is excluded from all three -- gitignored session working material, not product documentation. Each gate was verified to FAIL on a deliberate mutation before being committed: an unlinked ADR, a dangling-only index link, a renamed cross-reference, and an agent dropped from the HLD. A policy test that cannot fail is worse than no test because it reads like coverage. Suite goes 11 -> 13 tests, all green; ruff clean. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
The first live session with crash-durable state (2026-08-11) measured
223 writes over the 250ms warning threshold. Split by thread:
MainThread (30s snapshot) n=210 median 0.830s max 8.732s 85 >1s
trading threads n=13 median 0.414s max 5.111s 2 >1s
The file was only 79 KB, so this is disk contention rather than payload
size and no amount of shrinking the document would have helped. The
supervisor writes are the overwhelming majority and the worst outliers.
Dropping fsync from the snapshot path is the obvious fix, and on its own
it is UNSAFE. os.replace is atomic for the NAME but not for the DATA: a
hard kill during an un-fsynced rewrite can publish a present-but-garbage
file. Because that single document also held trades[] and the P&L
rollup, one torn SNAPSHOT could destroy the record ADR-0012 exists to
protect -- turning a latency fix into a reintroduction of the original
incident.
So the state is now two files:
session_state.json trades[], P&L rollup, session date, shutdown
flags. Written on trade events, on clean
shutdown, and once at construction. fsync.
session_state.marks.json live counters and open_position with
last_mark_ltp. Written every 30s. No fsync.
The supervisor no longer touches the durable file at all, so it cannot
corrupt it however it fails, and 210 of the 223 slow writes stop paying
fsync. Losing the marks file costs at most one snapshot interval of mark
data, which ADR-0012 already documented as acceptable.
load_session_state merges the pair back into the single-document shape,
so resumable_open_positions, recorded_realized_pnl and the runner's
resume path are unchanged. The merge is deliberately asymmetric: a
corrupt DURABLE file means no recovery and returns None; a corrupt or
missing MARKS file still returns the P&L and simply offers no positions.
Two things found while building it:
- a store that had only ever snapshotted never created the durable file,
so a session dying before its first trade left no document at all --
losing the session date, shutdown flags and any carried-forward trade
book. The durable file is now established at construction.
- the slow-write warning blamed "the caller's trading loop" for what was
usually a supervisor write. Marks now warn separately, at a looser 2s
threshold, with a message that says which one it is.
Rejected: moving trade-event writes to a queue and writer thread. It
would remove the remaining 13 stalls, but only by weakening durability
from "guaranteed before the call returns" to a sub-second window, which
is the guarantee ADR-0012 was written to provide. At a 0.414s median,
13 times a session, that trade is not worth making.
ADR-0012 gains an amendment section and the reporting LLD is updated in
the same commit. .gitignore now globs session_state*.json so a future
sidecar cannot be committed by accident.
Gates: session-state 45 passed (10 new, covering fsync-per-path, the
durable file being untouched by snapshots, corrupt/missing/stale marks,
and both files archiving under one timestamp); master 508 OK;
market-data-health 26 OK; pytest 1136 passed; ruff and mypy clean;
module branch coverage 91.6% from its own suite alone against its 90%
budget.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Co-authored-by: Codex <codex@openai.com>
Keep the collision-checked CPR AI source directory importable after master startup and bind the canonical agent name to the already-loaded module so delayed prompt/schema/runner imports resolve without duplicate dataclass types. Co-authored-by: Codex <codex@openai.com>
…ideo
Source: Intraday Hunter, "Prediction For 12 AUG 2026" (CoxS77NfnsI,
uploaded 2026-08-11, 2:00).
The starting condition differs from every note in this series so far: the
seller crowd is described as SPENT rather than seated. "Sellers would
certainly have entered on the retracement, but the market would have hit
their SLs. So if it has already taken the sellers out, we can go WITH the
market."
He then explains why nobody carried size overnight either -- "it did not
cross the round number, so not many people would have held their selling
quantity" -- which is v4e's recruitment rule applied to the close. No
round-number breach, no follow-through, so the session opens with thin
positioning on BOTH sides.
Plan: SELL-side setups on flat-to-gap-down, stated separately for all
three indices; a moderate gap-up keeps broadly the same plan.
Recorded faithfully rather than smoothed:
- the series' SECOND explicit escape hatch, this time for a large gap-up
("maybe the market has just made a trap... in a big gap-up we cannot
make such a plan for now"), kept as stand-aside rather than a guessed
branch, exactly as the 10 Aug note handled its missing branch;
- one genuine AMBIGUITY left unresolved: on NIFTY he also says a mild
gap-up can be followed WITH the market "if not many sellers are seated
the market may not find SLs", which cuts against the sell-side line.
v4e's A FORECAST OF WHO WILL ARRIVE rule says an unclear read is not
something to force into a direction, so the note carries the tension.
One BankNIFTY support arrived as "5710", read as 57100 alongside 56960 --
the same dropped-trailing-zero artefact seen on 4, 7 and 10 Aug. Noted in
the doc; advisory candidate levels only.
test_shipped_note_matches_august_12_intraday_hunter_plan replaces the
11 Aug equivalent and asserts the escape hatch and the ambiguity line
survive verbatim, because those are the two things a copy-forward or a
tidy-up would silently remove.
Gates: SL Hunting pytest 173 passed, ruff clean.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Distils Intraday Hunter's 11 Aug 2026 live session (
_JXirKMmI58) into the SL Hunting agent's knowledge. Unlike every prior addendum, this session is a loss ? which is why it is worth encoding.The lesson
He named the disqualifying fact himself, twice, before entering:
He then traded a forecast of who would arrive ? a sharp drop tempts intraday sellers in, so the market should rise to take them out ? and the selling simply continued.
Hunting inventory that exists is the strategy. Predicting inventory that might arrive is a weaker activity wearing the same vocabulary. When nobody is seated on either side, the output is HOLD.
Knowledge changes (all prose)
OPENING_DRIVE? WHICH CROWD THE OPEN RECRUITS DECIDES HOW BIG THE TRAP IS: a gap-down recruits positional sellers who hold overnight; a flat open recruits intraday sellers only, so the same-shaped trap is smaller and perishable. A genuine advance on v4d, independent of the outcome. Plus A FORECAST OF WHO WILL ARRIVE IS NOT EVIDENCE OF WHO IS SEATED and A SHARP FIRST SLIDE BAITS; A SLOW ONE MEANS IT.RISK? NAME THE LAST POINT, NOT ONLY THE STOP and DISCIPLINE IS ASYMMETRIC BETWEEN WINNERS AND LOSERS.The sharp-slide rule ships as a weak prior with its own counter-example attached ? it is the read that lost him the session, so it is recorded as a tie-breaker and explicitly barred from being a trade premise.
Prompt 96,627 ? 101,087 chars (headroom 18,913).
Tests
Two markers. The second is a drift guard asserting the forecasting rule still resolves to HOLD and still reconciles with v4c's MANUFACTURES MORE, so a later edit cannot quietly turn it into a licence to predict a crowd into existence.
Our agent on the same session
The doc addendum records it: the agent was SHORT all morning ? directionally right where IH was wrong ? and still lost more than any other strategy, because it took four short entries in 47 minutes and released three on premise-stall judgements rather than stops (two held 1 and 2 minutes; a 24400-target trade was let go at 24469?24492 having never been stopped).
That is the inverse of the failure v3y guards against. It is flagged as a lessons-loop candidate, not encoded as IH knowledge, because it is a property of our agent and not of the method.
P&L is marked provisional ? the runner was still live at 13:07 when this was written. SL Hunting's ?2,647.75 cross-checks exactly against its own
Result summary; noteMIRROR EXITlines do not contain| EXIT, which under-counted the agent by 948.00 on a first pass.Gates
SL Hunting pytest 173 passed ? full pytest 1136 passed ? ruff clean.
Bugfix: session state was stalling trading threads
41a78f8? unrelated to the knowledge work above, but found by running it.The crash-durable session state added in #115 rewrote the whole JSON document with
fsyncon every write, including the 30-second supervisor snapshot. The first live session with it (2026-08-11) measured 223 writes over the 250 ms warning threshold:The file was only 79 KB, so this is disk contention, not payload size ? shrinking the document would not have helped.
Why the obvious fix is unsafe on its own
Dropping
fsyncfrom the snapshot path looks like a one-line win. It isn't:os.replaceis atomic for the name, not the data, so a hard kill during an un-fsynced rewrite can publish a present-but-garbage file. Because that single document also heldtrades[]and the P&L rollup, one torn snapshot could destroy the record ADR-0012 exists to protect ? turning a latency fix into a reintroduction of the original incident.The split
session_state.jsontrades[], P&L rollup, session date, shutdown flagssession_state.marks.jsonopen_positionwithlast_mark_ltpThe supervisor no longer touches the durable file at all, so it cannot corrupt it however it fails, and 210 of the 223 slow writes stop paying
fsync? including the 8.7s outlier. Losing the marks file costs at most one snapshot interval of mark data, which ADR-0012 already documented as acceptable.load_session_statemerges the pair back into the single-document shape, soresumable_open_positions,recorded_realized_pnland the runner's resume path are unchanged. The merge is deliberately asymmetric: a corrupt durable file means no recovery (None); a corrupt or missing marks file still returns the P&L and simply offers no positions for resume.Deliberately not done
Moving trade-event writes to a queue and a writer thread. It would remove the remaining 13 stalls, but only by weakening durability from "guaranteed before the call returns" to a sub-second window ? the exact guarantee ADR-0012 was written to provide. At a 0.414s median, 13 times a session, that trade is not worth making.
Two defects found while building it
ADR-0012 gains an amendment section and
docs/lld/reporting-and-observability.mdis updated in the same commit..gitignorenow globssession_state*.jsonso a future sidecar cannot be committed by accident.Verification
10 new tests (45 total in the suite), covering fsync-per-path, the durable file staying byte-identical across five snapshots, corrupt/missing/stale marks, and both files archiving under one timestamp.
CI:
session_state.pybranch coverage 91.6% against its 90% data-safety budget; total 69.6%; coverage policy gate passed.Docs: staleness gates over the committed architecture set
A third, unrelated commit rides along here (
8c24fb3). It closes the follow-up recorded in ADR-0011 whendocs/was first filled in, and it is worth reading separately from the two changes above ? it touches no runtime code.docs/now holds an HLD, 12 LLDs and 12 ADRs. Nothing enforced that any of it stayed true, and stale architecture documentation is worse than none because it is believed. Three gates inTests/Dependencies/test_repository_policy.pynow cover three distinct ways it rots:docs/hld/system-overview.mdjoinstest_current_architecture_docs_distinguish_core_from_optional_agentstest_every_committed_design_document_is_linked_from_the_docs_index? checked in both directionstest_relative_links_inside_the_committed_docs_resolve? 97 links todaydocs/superpowers/is excluded from all three: gitignored session working material, not product documentation.The HLD did not actually pass
Worth calling out, because it is a real documentation gap rather than test plumbing. The HLD named the two optional agents only as class names inside an ASCII diagram (
SLHuntingAIWorker) and as file links, and said "two independently opt-in AI agents" without naming either one. A reader of the system overview genuinely could not tell what they were. Section 1 now carries a short table ? each agent, its provider, its default-off flag, and its LLD.Every gate was proven to fail
Each was verified against a deliberate mutation before being committed, then restored: an unlinked ADR, a dangling-only index link, a renamed cross-reference, and an agent dropped from the HLD. All four were caught, with file and line. A policy test that cannot fail is worse than no test, because it reads like coverage.
One caveat for reviewers
test_current_architecture_docs_distinguish_core_from_optional_agentsreads architecture files from the working tree, so it is not safe to run while switching branches ? it will read one commit's docs against another's tests and fail spuriously. It is stable in CI and in any single checkout.Suite goes 11 ? 13 tests. No runtime code touched.
?? Generated with Claude Code
CPR AI: R2/S2 continuation boundary
97f9ac6adds one conservative CPR-agent rule: never enter a bullishTRENDING_VWAP_CONTINUATIONabove R2 and never enter a bearish continuation below S2.The rule is enforced in both places that matter:
cpr-srsi-vwap-context-v3tells the adviser to returnHOLD/NONEinstead of chasing price beyond the final CPR boundary.CPRHostPolicyindependently rejects those proposals withcontinuation_outside_r2_s2, so model reasoning cannot waive the rule.The comparisons are deliberately strict (
close > R2for longs andclose < S2for shorts). Existing milestone, final-target, stop-width, and minimum-1R geometry remain unchanged, and no sideways, reversal, sizing, order, or broker behavior was modified.Verification
TDD RED first showed the missing prompt knowledge and generic geometry rejection. GREEN verification: CPR AI 70 passed; master 508 passed / 52 skipped; market health 26 passed; repository suites 1,142 passed; branch coverage 70.1% with every threshold passing; compileall, Ruff, mypy, Bandit, pre-commit, fake order-free smoke, and all four pinned dependency audits passed.
Implemented with Codex; the commit includes the required co-author trailer. No authenticated model or broker call was made.
CPR AI bugfix: master lazy imports
6f29e86fixes the production-master failure that recordedModuleNotFoundError: No module named 'cpr_ai_prompt'before Codex or MCP could run.The standalone smoke and focused CPR tests could import sibling modules because Python kept the CPR AI source folder on
sys.path. The master loader exposed that spaced-name folder only while each startup module executed, then removed it. The first completed-bar turn therefore could not perform the agent's intentionally lazy prompt/schema import; the delayed Codex runner had the same boundary and could also create incompatible duplicate result classes if loaded naively.The fix retains only the already collision-checked CPR AI source directory after startup and aliases canonical
cpr_ai_agentto the exact module object the master already loaded. Two production-style master tests prove that a delayed turn reaches its injected runner and that the Codex runner reuses the master's exactCPRAgentRunResultandCPRToolCallRecordtypes.Combined-tree verification
The import-boundary tests passed 2/2; CPR AI passed 70/70; master passed 510/510 with 52 expected skips; market health passed 26/26; repository suites passed 1,142/1,142. Branch coverage is 70.1% and every threshold passed. Compileall, Ruff, mypy, Bandit, pre-commit, fake order-free smoke, and all four pinned dependency audits passed.
The change does not authenticate Codex, call a broker, or alter execution/risk behavior. Implemented with Codex; the commit retains its co-author trailer.