Compare commits

..

126 Commits

Author SHA1 Message Date
Gud Boi e929fdc2d5 Record chart disconnect E2E qualification handoff
Preserve the manual IB detach, reattach, shared-consumer and startup
cancellation evidence as a historical snapshot for the QA worker.
Specify independent service ownership and deterministic phase gates,
including terminal-order resurrection during second-chart bootstrap.

Keep service continuity distinct from clean chart exit and retain the
unresolved cancellation, debugger, order-correlation and native-crash
findings. The linked Taken tasks own the worker assignments; this
document provides supporting context, not acceptance or executed tests.

(this patch was generated in some part by `opencode` using
`gpt-6.1-sol` (`openai`))
2026-10-21 16:16:52 -04:00
Gud Boi 29752c509c Select legacy AI workflow state paths
Record the legacy workflow backend and ignore its private runtime
state/config paths. Keep existing repository-local commit and test
guidance selected without tracking generated runtime receipts.

(this commit msg was generated in some part by `opencode` using
`gpt-6.1-sol` (`openai`))
2026-10-21 16:16:34 -04:00
Gud Boi a89887e2a4 Pin the qualified modern `tractor` runtime
Select Tractor b64c89f0, including final-result disconnect containment,
through an immutable Git source instead of an absolute editable path.
Require Python 3.13 and permit msgspec >=0.20 for the evaluated stack.

Regenerate the lock offline and retain its existing evaluation delta:
msgspec 0.21.1, Polars/runtime 2.0.0, Git-pinned multiaddr, setproctitle
and removal of Python 3.12 wheel variants. The local `piker_pin` ref
was fast-forwarded separately; publication is not implied by this lock.

Prompt-IO: ai/prompt-io/opencode/20261021T185636Z_eed58566_portable_pin_prompt_io.md

(this patch was generated in some part by `opencode` using
`gpt-6.1-sol` (`openai`))
2026-10-21 16:16:24 -04:00
Gud Boi 5adf77a94b Fix package logger names for daemon startup
Remove only the exact `piker.` prefix before delegating to Tractor's
logger factories; `lstrip()` treated it as a character set and renamed
submodules such as `piker.paper` to `piker.aper`.

Drop stale parent-namespace assertions in service, data, broker and EMS
startup while retaining console setup. Exercise both factories with
service and prefix-sensitive submodule names.

Prompt-IO: ai/prompt-io/opencode/20261019T123725Z_eed58566_logger_compat_prompt_io.md

(this patch was generated in some part by `opencode` using
`gpt-6.1-sol` (`openai`))
2026-10-21 16:07:53 -04:00
Gud Boi eed5856622 Retry incomplete IB schedule coverage
A one-day MNQ schedule reply can start in the next session,
leaving the gap's earlier trading hours uncovered. Widen
`Client.history_schedule()` lookbacks up to 31 days, keeping the
requested end fixed and sharing one timeout across attempts.
Preserve unknown coverage when widening cannot cover the range.
Share `SECONDS_PER_DAY` between query and reservation arithmetic,
with one explanation of elapsed versus calendar/session days.

Replay the captured MNQ replies through the allocator: reserve
51,824 candidate slots while excluding the maintenance closure.
Cover bounded retries and timeout cancellation, and document the
query contract. This does not repair existing SHM or parquet.

Prompt-IO: ai/prompt-io/codex/20260924T232405Z_441358e2_schedule_coverage_prompt_io.md

(this patch was generated in some part by `codex` using
`gpt-6` (`openai`))
2026-09-24 20:20:20 -04:00
Gud Boi 441358e2e7 Use IB schedules to reserve missing SHM history
Interrupted backfill can leave stored observations separated by
missing trading time. Query dated IB sessions before prepending
stored history and reserve synthetic samples only during covered
open sessions. Keep venue closures compressed and preserve the
indexes of already published rows.

Deats,
- Add `Client.history_schedule()` with UTC coverage, extended hours,
  bounded duration, timeout, and rejection of ambiguous DST times.
- Keep schedule normalization in `venues` and allocation in
  `.tsp._reserve`, with the project AGPL headers.
- Expose schedule and synthetic-range queries through `datad.ib`.
- Bound reservations by available SHM capacity and inspect only the
  newest 32 gaps by default via `newest_gaps`. Zero disables
  queries; leave unknown and oversized ranges unresolved.
- Record synthetic ranges separately from OHLCV. Automatic storage
  writes retain provider observations; placeholders copy close.
- Group the new tests under `tests/providers`.
- Cover request formatting, DST, failure and capacity cases, plus
  real SHM/NativeDB startup placement with a fake provider.
- Document a common CLI/chart segment-repair interface and defer
  coordinated insertion, repair execution, and reader integration.

Validation: 55 targeted tests passed in the working tree; live IB
schedule coverage remains unqualified. After the module moves,
33 affected tests passed again; the local review follow-up passed
18 affected cases, including four new request-budget cases.
The commit runner repeats
lint and isolated regression checks against this exact boundary.

Prompt-IO: ai/prompt-io/codex/20260924T185456Z_1da9b760_schedule_prompt_io.md

(this patch was generated in some part by `codex` using
`gpt-6` (`openai`))
2026-09-24 19:09:22 -04:00
Gud Boi de58fa2bd8 Factor and bound synthetic SHM null repair
Extract `_copy_close_to_null_rows()` with explicit mutation
bounds, searchable close-copy comments, and preserved activity
fields. Type and document `maybe_fill_null_segments()` including
its distinction from packed timestamp-gap repair.

Disable relative epoch tolerance when validating synthetic
stamps: an overfull reservation must not duplicate the right
boundary. Cover modern epochs, typed empty replies, packed gaps,
and unchanged rejected inputs.

Prompt-IO: ai/prompt-io/codex/20260924T165757Z_1da9b760_prompt_io.md

(this patch was generated in some part by `codex` using
`gpt-6` (`openai`))
2026-09-24 14:58:18 -04:00
Gud Boi 1da9b760a3 Preserve IB history request bounds on no data
Complete HMDS no-data replies without silently retrying a day
older. Let `open_history_client()` classify the original request
and leave frame stepping to `start_backfill()`.

Distinguish `request_end_dt` from `oldest_shm_dt` so the first
frame after an empty interval retains its unwritten end bar.
Inline the storage time cutoff and drop the unused two-direction
`diff_history()` helper. Explain both dates and filtering rules.

Add request-boundary tests and real SHM/NativeDB regressions:
all 2,004 supplied bars survive, while the closure stays empty.

Prompt-IO: ai/prompt-io/codex/20260923T164656Z_efa72980_prompt_io.md

(this patch was generated in some part by `codex` using
`gpt-6` (`openai`))
2026-09-23 14:35:21 -04:00
Gud Boi efa72980f8 Raise on IB socket disconnects in proxy and trades
Propagate the authoritative `disconnectedEvent` as a
`ConnectionError` so proxy callers and trade consumers fail loudly
instead of waiting on a dead socket. Preserve the last `apiError`
text as the reason without treating numeric farm/order errors as
socket failures.

Deats,
- Disconnect the one-shot callback before sending the exception.
- Raise when `recv_trade_updates()` loses its socket, and relay
  disconnect exceptions through both Trio-side consumers.
- Exercise real `eventkit` error/disconnect callbacks and preserve
  the supplied commission fill while testing bridge termination.
- Record the aborted integration stage as historical provenance;
  its test passes do not validate this post-abort boundary.

Prompt-IO: ai/prompt-io/opencode/20260916_ib_connectivity_integration.md

(this patch was generated in some part by `opencode` using
`gpt-6-astra` (`openai`))
2026-09-17 18:24:17 -04:00
Gud Boi 5eb4fc9d8a Fix IB fill accounting by execution identity
Keep each commission callback's exact `Fill` paired with its
report, so overlapping executions on one contract cannot borrow
each other's fees. Cache positions by account and `conId`, queue
commissioned fills by execution ID until a position arrives, and
accept zero commissions. Reject mismatched execution/report IDs.

Keep `Execution.execId` authoritative when flattening ledger
entries; an empty commission ID must not erase a valid fill. Add
regressions for empty commission IDs, reversed fee arrival, and
positions arriving before or after overlapping executions.

The human live-tested fills and reports the accounting fix works.
Exact-boundary automated checks remain pending; the earlier nine
passes covered the mixed working tree. Included Prompt-IO logs
retain whole-session context, including separate roaming work.

Prompt-IO: ai/prompt-io/opencode/20260916T194052Z_749ca1f0_prompt_io.md

(this patch was generated in some part by `opencode` using
`gpt-6-astra` (`openai`))
2026-09-16 17:12:01 -04:00
Gud Boi 749ca1f09c Add `piker-fsp-expert` skill
Document FSP lifecycle, SHM publication, backfill ordering,
causal signal-processing semantics, and deterministic tests.
Record research directions separately from implementation and
expose the package through the relative Claude skill symlink.

(this commit msg was generated in some part by `codex` using
`gpt-6` (`openai`))
2026-09-15 13:04:17 -04:00
Gud Boi 224399147d Honor M4 renderer cache invalidation
Preserve the renderer's `reset_cache` result when publishing
rebuilt paths so zoom changes invalidate Qt's cached graphics.
Retain the earlier live-debug provenance for cadence, negative
volume, repaint suppression, and downsample cache repair.

Prompt-IO: ai/prompt-io/opencode/20260903T232525Z_fadab3d2_prompt_io.md

(this commit msg was generated in some part by `codex` using
`gpt-6` (`openai`))
2026-09-15 12:49:53 -04:00
Gud Boi 5b4622fb39 Skip unchanged realtime FSP redraws
Cache destination bounds and timestamp/value bytes on `Viz`.
Skip identical quote-driven refreshes, including stable NaNs,
while changed samples and forced history repairs still redraw.
Add a regression covering each invalidation path.

(this commit msg was generated in some part by `codex` using
`gpt-6` (`openai`))
2026-09-15 12:49:42 -04:00
Gud Boi 20406210b3 Clamp negative provider volume before FSP updates
Clamp negative trade sizes and cumulative bar volume in sampling,
and sanitize history and realtime volume in `tina_vwap()` and
`dolla_vlm()`. Provider resets must not create negative outliers
that hide the derived curve. Cover the history and tick paths.

(this commit msg was generated in some part by `codex` using
`gpt-6` (`openai`))
2026-09-15 12:47:07 -04:00
Gud Boi 07d8cf5a3d Fix FSP history and sample synchronization
Bootstrap FSP output and timestamps from one source snapshot,
validate the history schema, and compare absolute SHM bounds.
Subscribe before bootstrap, replay history revisions, filter
foreign sampler notices, and append each destination step once.
Close replaced generators before signaling completion.

Use declared `Flume` sample periods so market-closure gaps cannot
be mistaken for the realtime cadence. Cover bounds, snapshot
isolation, backfill routing, sample steps, and targeted redraws.

(this commit msg was generated in some part by `codex` using
`gpt-6` (`openai`))
2026-09-15 12:46:20 -04:00
Gud Boi d5b20ce350 Formalize `FspStream` and fix builtin call contracts
Export `FeedQuote` and `Tick`, describe historical and realtime
FSP yields, and preserve callable parameters through `@fsp`.
Align builtin history arrays and emit named realtime updates.

Keep the staged Tuicr review fixes. Let Numba specialize `ema()`
lazily so omitted defaults work alongside explicit arguments,
retaining nopython/nogil compilation and single-sample seeding.
Cover builtin yields and EMA argument forms with regressions.

Prompt-IO: ai/prompt-io/opencode/20260908T020619Z_fadab3d2_prompt_io.md
Prompt-IO: ai/prompt-io/codex/20260915T155229Z_fadab3d2_prompt_io.md

(this patch was generated in some part by `codex` using
`gpt-6` (`openai`))
2026-09-15 12:17:02 -04:00
Gud Boi fadab3d2bc Target FSP history graphics by SHM token
Replace whole-linked-chart refreshes after FSP history rebuilds
with updates selected by normalized destination `NDToken` identity.

Clear each matching `Viz` formatter and max/min cache, redraw
its path, then rescale only the affected view. This keeps primary
and foreign market curves stable while near-term history is
backfilled.

Record the concrete piker `PlotItem`, `ChartView`, `Axis`, and
`YAxisLabel` relationships used by the redraw path.

Prompt-IO: ai/prompt-io/opencode/20260828T195032Z_9115d24d_prompt_io.md

(this patch was generated in some part by `opencode` using `gpt-5.6-sol` (`openai`))
2026-09-01 22:07:22 -04:00
Gud Boi 9115d24da0 Fix string errors in `.ib.broker` relay
Normalize structured and raw IB errors without indexing strings as
message dictionaries.

Deats,
- match each error form before request-id handling;
- parse `[code N]` and `[Errno N]` prefixes without crashing;
- cover raw errors through `deliver_trade_events()`.

Prompt-IO: ai/prompt-io/opencode/20260828T063126Z_1606122b_prompt_io.md

(this patch was generated in some part by `opencode` using `gpt-5.6-sol` (`openai`))
2026-08-28 02:52:12 -04:00
Gud Boi 1606122b45 Add `piker-clearing-expert` skill
Capture EMS/order-control ownership, identifier boundaries, ledger
and position invariants, backend variation, and current known hazards.

Store the canonical bundle under `.agents/skills/` and bridge Claude
discovery through a portable relative link.

Also,
- add architecture, debugging, and test-map references;
- document the provider-neutral source convention;
- ignore the canonical `wkts/` runtime root.

Prompt-IO: ai/prompt-io/opencode/20260828T021411Z_6ca6ab2a_prompt_io.md

(this patch was generated in some part by `opencode` using `gpt-5.6-sol` (`openai`))
2026-08-27 23:24:54 -04:00
Gud Boi 7b51927f6b Fix `SymbologyCache.search()` key matching
RapidFuzz treated `mktmaps` values as fuzzy choices and called
`len()` on `MktPair` structs during paper-ledger persistence.

Search string keys explicitly and resolve each match back to its
struct so native market-ID aliases survive legacy FQME
qualification.

Also,
- cover alias preservation and the paper-ledger TOML round trip.

Prompt-IO: ai/prompt-io/opencode/20260828T013057Z_6ca6ab2a_prompt_io.md

(this patch was generated in some part by `opencode` using `gpt-5.6-sol` (`openai`))
2026-08-27 23:11:43 -04:00
Gud Boi 6ca6ab2a10 Fix Binance USD-M websocket routing
Binance retired its legacy futures websocket and split quote
traffic across `/public` and `/market`. Route `bookTicker` and
`aggTrade` through dedicated URLs, merge both sockets, and accept
the new `st` stream-type field.

Bound connection and first-quote startup so failed or silent
streams raise `DataUnavailable` instead of hanging `datad` and
chart consumers.

Also,
- derive feed mode from resolved `FutesPair` metadata
- keep direct URL subscriptions stable across reconnects
- cover USD-M routes, payloads, and startup timeouts

Prompt-IO: ai/prompt-io/opencode/20260826T222600Z_5f631e19_prompt_io.md

(this patch was generated in some part by `opencode` using `gpt-5.6-sol` (`openai`))
2026-08-26 19:24:37 -04:00
Gud Boi 5f631e1996 Plan repository-wide branch landing order
Audit every retained worktree, branch family, rebased patch, stash,
and unreachable commit against `main`.

Deats,
- factor useful histories into dependency-ordered PR lanes;
- reduce the DPI branch swarm to one five-commit patch set;
- separate runtime, datad, backfill and chart ownership work;
- record deferred patches and post-landing stale-ref cleanup;
- attach Prompt-IO provenance for the generated audit plan.

Prompt-IO: ai/prompt-io/opencode/20260818T064340Z_09ddcf50_prompt_io.md

(this patch was generated in some part by `opencode` using `gpt-5.6-sol` (`openai`))
2026-08-18 12:47:30 -04:00
Gud Boi 09ddcf5055 Plan `pytest-qt` chart UI audit layers
Distill the gap-overlay E2E findings into a staged audit plan for
the full PyQtGraph iface. Keep Qt and graphics real while replacing
feeds, services and provider search with deterministic seams.

Deats,
- define the headless PyQt6 env and pytest-qt fixture ownership;
- layer primitive, input, SHM, full-window and actor checks;
- guard window SIGINT, provider search and overlapping symbol boot;
- require signal/postcondition waits and lifecycle leak assertions;
- split reviewable PRs before distilling a repo-local UI skill.

(this patch was generated in some part by `opencode` using `gpt-5.6-sol` (`openai`))
2026-08-17 19:55:26 -04:00
Gud Boi 335190a4e3 Test gap dialogs through a real `tractor` actor
Schema checks do not exercise ctx discovery, directional decoding,
stream setup, request correlation or endpoint-finally cleanup.

Boot an isolated child actor around `remote_gap_overlays()` and
drive it through `AnnotClient`. Cancel one published request, leave
its reply queued and prove the next request discards that stale
msg.

Also,
- verify generated request IDs and remote-owner cleanup;
- document automated coverage and broker-backed manual boundaries.

Prompt-IO: ai/prompt-io/opencode/20260817T222036Z_f65df6a1_prompt_io.md

(this patch was generated in some part by `opencode` using `gpt-5.6-sol` (`openai`))
2026-08-17 19:11:03 -04:00
Gud Boi b738c9f4f7 Fix focused gap routing in `ChartView`
The keyboard loop treated every display not owning the focused
history chart as a realtime target. One `Ctrl+G` could therefore
toggle unrelated cached symbols.

Match the focused chart explicitly before selecting its timeframe.
Factor startup registration and toggle routing into testable
helpers, and use scoped PyQt6 enums throughout the input path.

Deats,
- preserve independent 60s startup layers per display;
- toggle only the focused 1s or 60s chart;
- drive the production event relay with a real `QKeyEvent`.

(this patch was generated in some part by `opencode` using `gpt-5.6-sol` (`openai`))
2026-08-17 19:09:46 -04:00
Gud Boi 711666f9c8 Fix Qt indexing in `GapAnnotations.reposition()`
`prepareGeometryChange()` ran after the gap rects and arrow specs
were mutated. Qt could retain the item's old scene index even
though `boundingRect()` returned its new prepended-history
position.

Invalidate cached bounds and notify Qt before changing any
geometry. Cover both scene lookup positions with a real offscreen
item.

(this patch was generated in some part by `opencode` using `gpt-5.6-sol` (`openai`))
2026-08-17 19:08:28 -04:00
Gud Boi 78982d8280 Fix `PlotItem` cleanup in `GapOverlayMngr`
`PlotItem.addItem()` records overlays outside the Qt scene. Direct
scene removal leaves stale plot refs and range bookkeeping behind.

Track each annotation's owning plot and remove it through the same
API used for insertion. Exclude pixel-sized arrows from data
bounds.

Deats,
- centralize replacement, hiding and owner teardown;
- retain scene removal as a defensive fallback;
- cover real Qt registries and duplicate-FQME chart identity.

(this patch was generated in some part by `opencode` using `gpt-5.6-sol` (`openai`))
2026-08-17 19:08:02 -04:00
Gud Boi f65df6a138 Own gap overlays in chart-local `GapOverlayMngr`
Detect positive timestamp gaps from chart OHLCV SHM with one
vectorized pass. Route local UI and remote callers through one
`GapOverlayMngr` renderer, and key each layer by owner, chart,
FQME and timeframe so remote ctx teardown cannot remove local
graphics.

Deats,
- add tagged `SetGapOverlay` requests and typed
  `GapOverlay` responses;
- keep `remote_annotate()` intact and serve its typed dialog from
  a dedicated `remote_gap_overlays()` Tractor ctx;
- admit `SetGapOverlay`, `GapOverlay`, `list[str]` and `None` in
  the Tractor payload spec, then strictly narrow requests to
  `SetGapOverlay` and replies to `GapOverlay`;
- serialize shared-stream dialogs and correlate request IDs so
  stale replies cannot skew later exchanges;
- rename `AnnotCtl` / `open_annot_ctl()` to `AnnotClient` /
  `open_annot_client()` across storage and annotation callers;
- enable 60s history overlays at startup and toggle the focused
  1s or 60s chart directly with `Ctrl+G`;
- guard redraws by chart, FQME and timeframe; tolerate empty
  arrays and out-of-range lookups; update rect and arrow geometry;
- cover detector geometry, msgpack roundtrips, schema rejection,
  typed stale-FQME errors and renamed storage seams;
- document ownership, refresh, stack landing and live
  qualification.

Prompt-IO: ai/prompt-io/opencode/20260814T032716Z_2aa66c2c_prompt_io.md

(this patch was generated in some part by `opencode` using `gpt-5.6-sol` (`openai`))
2026-08-14 19:03:11 -04:00
Gud Boi 2aa66c2cf6 Fix IB `MethodProxy` response routing
`open_client_proxy()` broadcast its one-slot linked channel to an
always-running status relay and an idle method receiver. Status
traffic advanced only the relay, so the first lazy contract lookup
for an uncached order raised `Lagged` before receiving its
response.

Give `relay_client_proxy_messages()` sole receive ownership. Route
tagged responses from concurrent caller tasks into their per-`mid`
slots while each proxy context retains its own channel and relay.

Deats,
- log stale responses with their original method name;
- warn when unresolved-request metadata exceeds its bounded limit;
- drop late responses after caller cancellation;
- preserve the reconnect design note and message-case rationale;
- accept clean channel EOF during proxy teardown;
- cover idle status, concurrency, cancellation and EOF paths.

Prompt-IO: ai/prompt-io/opencode/20260811T205826Z_06d5ea5d_prompt_io.md

(this patch was generated in some part by `opencode` using `gpt-5.6-sol` (`openai`))
2026-08-11 18:46:23 -04:00
Gud Boi 06d5ea5dc0 Fix sparse IB history prepends in `ShmArray`
IB returns trading samples, not one row per wall-clock period.
Converting elapsed time to a physical prepend offset left 42,720
zero rows in NVDA's 60s buffer even though both valid margins
shared the same timestamp.

Use the actual earliest provider sample as the NativeDB boundary.
After reverse retrieval, prepend older stored rows directly beside
it.

Deats,
- start reverse insertion at the current first index;
- update `ShmArray._first` after every provider prepend;
- trim provider/NativeDB overlap by its timestamp;
- publish the final storage prepend to chart and FSP consumers;
- derive the source `time` field from `ohlc_key_map`;
- cover the full lifecycle with a real multi-frame `ShmArray`.

Prompt-IO: ai/prompt-io/opencode/20260805T195001Z_f0f3c806_prompt_io.md

(this patch was generated in some part by `opencode` using `gpt-5.6-sol` (`openai`))
2026-08-05 19:40:07 -04:00
Gud Boi f0f3c80657 Simplify `store` series and SHM discovery
Make `store ls` show every NativeDB FQME and its available sample
periods by default. Keep `store series` as the detailed view of
each exact sub-series, including its period, size and path. Share
the same optional FQME filter and numerically ordered payload.

Use `store shm` as the canonical volatile-buffer ep and make its
default operation a read-only report. Enter the prior persistence
workflow only through `--write-parquet`, require that flag before
`--reload-parquet-to-shm`. Reject read-only formatting options in
repair mode instead of silently ignoring them.

Also,
- update the qualification docs, debug helper and harness refs;
- cover no-flag listings, command help and SHM mode validation;
- retain exact generation and object-name selection for stale SHM.

Prompt-IO: ai/prompt-io/opencode/20260801T015321Z_74ea2152_prompt_io.md

(this patch was generated in some part by `opencode` using `gpt-5.6-sol` (`openai`))
2026-08-01 00:34:55 -04:00
Gud Boi 74ea2152f9 Expose storage state through `piker store`
Make bare storage groups and eps render help, so the CLI is
discoverable without memorizing `Typer` option conventions. Add
read-only `series` and `shm` eps for durable `NativeDB` series and
live history buffers, with `Rich`-table or `JSON` output.

Deats,
- preserve exact `(fqme, period)` identities for `NativeDB`
  series;
- parse exact `(generation, fqme, kind)` names for SHM buffers;
- reconstruct provider dtypes when attaching to live buffers;
- tolerate buffers disappearing between discovery and attach.

Also,
- report expected and observed cadence separately, plus invalid
  rows, ordering defects and ranked gaps;
- add exact `ldshm --shm-name` selection and keep disabled write
  and reload paths from reading undefined state or entering `pdb`;
- document the triage layers and cover help, identity parsing,
  read-only diagnostics, dtype selection and `ldshm` flow.

Prompt-IO: ai/prompt-io/opencode/20260731T165215Z_0846cbd4_prompt_io.md

(this patch was generated in some part by `opencode` using `gpt-5.6-sol` (`openai`))
2026-07-31 17:26:11 -04:00
Gud Boi 0846cbd4a4 Repair IB startup gaps in `ShmArray`
Keep reverse history and stored SHM layout aligned so sparse IB
frames do not expose permanent zero-time reservations.

Deats,
- omit inclusive query endpoints from SHM but retain full storage
- serialize and bound farm resets, blanks, and cancellations
- run the final null sweep after reverse retrieval completes
- require valid timestamp boundaries for synthetic gap rows
- cover the live QQQ 653-row failure and reset interleavings

Prompt-IO: ai/prompt-io/opencode/20260729T204046Z_ad6c560f_prompt_io.md

(this patch was generated in some part by `opencode` using `gpt-5.6-sol` (`openai`))
2026-07-29 16:46:52 -04:00
Gud Boi ad6c560f6b Guard `ldshm` from invalid SHM snapshots
Do not pass unpublished or corrupt timestamp rows into dedupe,
`NativeDB` replacement writes, or live SHM reload.

Deats,
- infer cadence from finite positive observed steps
- choose the smaller observed step when frequency ties
- skip invalid or degenerate snapshots as a whole
- cover the QQQ zero-slot failure with mutation-failing spies

Prompt-IO: ai/prompt-io/opencode/20260729T185406Z_d359b1cb_prompt_io.md

(this patch was generated in some part by `opencode` using `gpt-5.6-sol` (`openai`))
2026-07-29 15:07:50 -04:00
Gud Boi d359b1cbfb Repair first-slot OHLC sentinels in `NativeDB`
Filter known zero-initialized epoch slots before persisted history
hydrates SHM or merges with a fresh provider frame.

Deats,
- reindex the repaired read view for same-session chart publication
- treat sentinel-only files as empty history for fresh backfill
- reject new first-slot sentinels but preserve modern zero-price bars
- classify raw sentinel evidence separately in `store audit`

Prompt-IO: ai/prompt-io/opencode/20260729T041723Z_ce33deb6_prompt_io.md

(this patch was generated in some part by `opencode` using `gpt-5.6-sol` (`openai`))
2026-07-29 13:30:51 -04:00
Gud Boi ce33deb6b9 Use IB UTC syntax for history `endDateTime`
Serialize aware reverse-fill boundaries as `YYYYMMDD-HH:MM:SS`
before handing them to `ib_async`.

This avoids Gateway error 10314 from its trailing-`UTC` format and
drops the unfinished EST comparison breakpoint from `Client.bars()`.

Prompt-IO: ai/prompt-io/opencode/20260729T034106Z_922a5df8_prompt_io.md

(this patch was generated in some part by `opencode` using `gpt-5.6-sol` (`openai`))
2026-07-28 23:44:15 -04:00
Gud Boi 922a5df88d Add `xonsh` backfill qualification workflow
Drive known-bad, fresh, restart and append checks from a private
disposable NativeDB root.

Deats,
- bind child config and imports to the marked worktree root
- preserve checksum-matched reports, snapshots and case metadata
- compare timestamp coverage, seam values and persisted structure
- document manual chart runs, gap triage and the expansion matrix

Prompt-IO: ai/prompt-io/opencode/20260728T003247Z_ed85721c_prompt_io.md

(this patch was generated in some part by `opencode` using `gpt-5.6-sol` (`openai`))
2026-07-28 19:41:55 -04:00
Gud Boi d7d537d531 Add raw OHLCV audit to `NativeDB`
Inspect Parquet bytes directly without opening storage or normalizing
malformed evidence.

Deats,
- report canonical schema, timestamp, index and finite-value state
- retain exact cadence aggregates with bounded endpoint details
- snapshot bytes before parsing and clean up partial copy failures
- reject unsafe FQMEs, links, collisions and non-regular sources

Prompt-IO: ai/prompt-io/opencode/20260728T005034Z_ed85721c_prompt_io.md

(this patch was generated in some part by `opencode` using `gpt-5.6-sol` (`openai`))
2026-07-28 19:38:50 -04:00
Gud Boi 1f30917047 Repair legacy timestamps in `NativeDB` merges
Keep incoming provider frames strict while dropping non-positive rows
from persisted history before merge validation.

This lets known-bad MNQ history repair itself on the next valid IB
frame without losing valid old bars or publishing noncanonical indexes.

Prompt-IO: ai/prompt-io/opencode/20260728T232718Z_ed85721c_prompt_io.md

(this patch was generated in some part by `opencode` using `gpt-5.6-sol` (`openai`))
2026-07-28 19:37:15 -04:00
Gud Boi ed85721cfd Clean test-owned SHM leaks in `pytest`
Track successful `create=True` calls through Tractor's
current-process factory instead of scanning `/dev/shm` or trusting
the attachment cache.

- cooperate with normal actor unlink and restore token-cache state
- verify POSIX object identity before fallback cleanup
- fail after cleaning exact surviving segments
- reproduce the pre-`current_actor()` allocation leak

Prompt-IO: ai/prompt-io/opencode/20260727T215906Z_689df816_prompt_io.md

(this patch was generated in some part by `opencode` using `gpt-5.6-sol` (`openai`))
2026-07-27 18:09:03 -04:00
Gud Boi 19c6d98e6d Normalize provider OHLCV for `NativeDB`
IB history frames omit the derived `index` and append `count`.
Normalize provider data to durable names, order, and dtypes before
merge, and reject fractional epoch truncation.

Also,
- map canonical storage fields into provider-specific SHM on reload
- select Polars columns by name instead of position
- exercise first-start and restart through actor, SHM, and Parquet

Prompt-IO: ai/prompt-io/opencode/20260727T212454Z_689df816_prompt_io.md

(this patch was generated in some part by `opencode` using `gpt-5.6-sol` (`openai`))
2026-07-27 18:08:33 -04:00
Gud Boi 689df816d6 Drop redundant `NativeDB` writer locks
`datad`'s persistent feed task already owns each series writer.
The actor-local and file locks duplicated this SC contract and left
`.parquet.lock` sidecars that upstream indexed as data.

- preserve validated temp writes, `fsync()`, and atomic replacement
- keep exact-suffix indexing for legacy lock and temp sidecars
- prove full and incremental writes create no lock artifacts

Prompt-IO: ai/prompt-io/opencode/20260727T204711Z_84a6d47b_prompt_io.md

(this patch was generated in some part by `opencode` using `gpt-5.6-sol` (`openai`))
2026-07-27 16:58:00 -04:00
Gud Boi 84a6d47b44 Plan known-FQME backfill qualification
- preserve dirty main-tree work while transferring branch ownership
- hold provider, FQME, timeframe, and symptom fixed while debugging
- capture read-only baselines before disposable live runs
- turn each reproduced failure into deterministic coverage
- qualify interruption and restart behavior after the normal lifecycle

(this patch was generated in some part by `opencode` using `gpt-5.6-sol` (`openai`))
2026-07-24 18:33:06 -04:00
Gud Boi b000db94e0 Make `NativeDB` backfills atomic and non-destructive
- merge provider deltas without dropping unaffected history
- validate reopened temp parquet before atomic replacement
- serialize writers with actor-local and cancellable file locks
- publish cache only after disk replacement
- persist full startup/backfill frames before SHM publication
- bound sampler notifications and avoid MarketStore duplicates

Prompt-IO: ai/prompt-io/opencode/20260722T024134Z_3a4f6737_prompt_io.md

(this patch was generated in some part by `opencode` using `gpt-5.6-sol` (`openai`))
2026-07-24 18:33:06 -04:00
Gud Boi c60a5a45f3 .tsp: report non-positive timestamp steps
- keep positive missing-history gaps distinct from ordering faults
- return duplicate and reversed timestamp rows as structured data
- warn before normalization removes the original ordering evidence

Prompt-IO: ai/prompt-io/opencode/20260722T024133Z_3a4f6737_prompt_io.md

(this patch was generated in some part by `opencode` using `gpt-5.6-sol` (`openai`))
2026-07-24 18:33:06 -04:00
Gud Boi 6913e80d1a Preserve gap-annotation profiling handoff
Track the untracked Claude session note with its benchmark, client
bottleneck, and follow-up optimization sketch intact.

(this commit msg was generated in some part by `opencode` using `gpt-5.6-sol` (`openai`))
2026-07-24 18:33:06 -04:00
Gud Boi 931f22d235 `.tsp._history`: drop orphaned backfill events
- let structured nursery joins own child-task completion
- stop empty provider frames before timestamp endpoint indexing
- cover provider exhaustion and empty frames with bounded tests

Prompt-IO: ai/prompt-io/opencode/20260722T001358Z_16f3cd6f_prompt_io.md

(this patch was generated in some part by `opencode` using `gpt-5.6-sol` (`openai`))
2026-07-24 18:33:06 -04:00
Gud Boi 976db6c33c .tsp: normalize gaps after sorted dedupe
- recompute datetime and delta cols after final timestamp ordering
- detect only positive sample-step gaps
- document inclusive null grouping with an updated visual map
- check all small null layouts against an independent test oracle
- honor selected null cols and reject discontinuous frame indexes
- share exact null grouping across NumPy and Polars

Prompt-IO: ai/prompt-io/opencode/20260722T001357Z_16f3cd6f_prompt_io.md

(this patch was generated in some part by `opencode` using `gpt-5.6-sol` (`openai`))
2026-07-24 18:33:06 -04:00
Gud Boi 99fb9d2eab Plan gap-checker and backfiller repairs
Lay out a correctness-first path from deterministic regressions
through normalized gap detection, structured backfill completion,
and crash-safe `NativeDB` persistence.

Deats,
- separate checker, storage, provider, and annotation gates
- record confirmed current defects and data invariants
- define commit boundaries and timeout-wrapped verification

Prompt-IO: ai/prompt-io/opencode/20260721T215055Z_c6ec3d41_prompt_io.md

(this patch was generated in some part by `opencode` using `gpt-5.6-sol` (`openai`))
2026-07-24 18:33:06 -04:00
Gud Boi e766c158de Add Piker `/run-tests` harness
Extract current test env, scope, fixture, and runtime guidance into a
repo-owned override for the canonical Run base.

Deats,
- require `xonsh` plugin disable and approved Nix provisioning
- separate deterministic checks from live, GUI, and container cases
- map source areas and document actor timeout retry behavior

Prompt-IO: ai/prompt-io/opencode/20260724T220156Z_43d7a3ed_prompt_io.md

(this patch was generated in some part by `opencode` using
`gpt-5.6-sol` (`openai`))
2026-07-24 18:22:12 -04:00
Gud Boi 43d7a3edad Use canonical `ai.skillz` provider links
Replace the local `commit-msg` base and OpenCode shim with
canonical ignored links while preserving Piker's tracked style guide.

Also,
- expose the full skill catalog to Claude and OpenCode
- add canonical slash commands, including `run-tests`
- keep hybrid runtime state local

(this patch was generated in some part by `opencode` using
`gpt-5.6-sol` (`openai`))
2026-07-24 17:50:12 -04:00
Gud Boi c6ec3d4144 Share piker skills across coding harnesses
Use portable Agent Skills metadata and one repo-local source for
both Claude Code and OpenCode.

Deats,
- replace harness-only frontmatter and dynamic context
- make `commit-msg` runtime-aware and review-first
- add a thin OpenCode command and integration docs
- guard `np.searchsorted()` guidance before OOB indexing
- record OpenCode prompt provenance

Prompt-IO: ai/prompt-io/opencode/20260721T205228Z_58ffe487_prompt_io.md

(this patch was generated in some part by `opencode` using `gpt-5.6-sol` (`openai`))
2026-07-21 17:09:07 -04:00
Gud Boi 58ffe48775 Bump `flake.lock` after dev-shell fixes
Advance `nixpkgs` to `241313f4`; the refreshed closure passes Qt,
`pyqtgraph` and wksmod-path import checks.

(this commit msg was generated in some part by `opencode` using `gpt-5.6-sol` (`openai`))
2026-07-21 16:44:58 -04:00
Gud Boi ceb806c136 Rebuild stale `uv` envs after flake updates
`uv sync` accepts an existing same-minor Python even when its Nix
store path belongs to an older closure. That can leave a glibc 2.40
interpreter loading Qt built against glibc 2.42.

- derive the Python package once from `cpython`
- clear `py313` when its interpreter target differs from the flake
- ignore inherited `VIRTUAL_ENV` before syncing

(this patch was generated in some part by `opencode` using `gpt-5.6-sol` (`openai`))
2026-07-21 16:37:59 -04:00
Gud Boi 3da8900f68 Fix `flake.nix` dev-shell install
The refreshed Nix closure no longer supplies `libz.so.1` to the
`uv` NumPy wheel, and leaves the synced `py313` env off `PATH`.

- add Nix `zlib` to `LD_LIBRARY_PATH` for native Python deps
- activate `py313` after `uv sync` so `pyqtgraph`, `piker` and
  `xonsh` resolve from the locked env

(this patch was generated in some part by `opencode` using `gpt-5.6-sol` (`openai`))
2026-07-21 15:13:35 -04:00
Gud Boi f6dc218a8e Bump `flake.lock` to avoid nix warnings..
However this breaks `pyqtgraph` due to missing `PyQtX` libs??
2026-07-15 16:29:18 -04:00
Gud Boi b73300c820 Add `piker-conc-expert` claude-code skill
Distilled distributed-runtime + structured-concurrency
expertise for the `tractor` actor-tree, auto-applied (not
user-invocable) when working on daemon/service arch, RPC
eps, `to_asyncio` integration, cancellation semantics or
hang/wedge/skew debugging.

Deats,
- `SKILL.md`: the core mental model incl. the post-split
  actor-tree taxonomy (`datad`/`brokerd`/`emsd`/etc.),
  daemon lifecycle conventions, actor-local-state hazards
  and `tractor` primitive usage as deployed here.
- `gotchas.md`: symptom -> cause -> fix entries distilled
  from this branch's (datad|brokerd)-split debugging (eg.
  un-warmed contract caches, stale IPC resps, double/bare
  log records, ib client-id collisions).
- `debug-recipes.md`: actor-system forensics incl. wedged
  actor triage, hang-proof test gating and regression vs
  pre-existing attribution.

(this patch was generated in some part by [`claude-code`][claude-code-gh])
[claude-code-gh]: https://github.com/anthropics/claude-code

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-06-10 20:21:48 -04:00
Gud Boi 0404e4230e .ib.api: req-id correlate `MethodProxy` calls
The (single, shared) relay chan has NO ordering guarantee
when a caller task is cancelled (eg. by a search-req
timeout) after sending its request but before consuming the
response: the "orphaned" response gets mis-delivered to the
next caller, off-by-one skewing every result thereafter!

Deats,
- tag each request with a `mid` from an `itertools.count()`
  on the proxy and echo it back in both the result and
  exception resps from `open_aio_client_method_relay()`.
- drop (w/ a warning) any stale resp whose `mid` doesn't
  match the current caller's and keep waiting for ours.
- rewrite the resp-wait loop as a `match:` (resolving the
  old "py3.10 syntax" TODO) incl. the prior inline
  `('error', ...)`/`('event', ...)` relay cases.

(this patch was generated in some part by [`claude-code`][claude-code-gh])
[claude-code-gh]: https://github.com/anthropics/claude-code

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-06-10 20:19:41 -04:00
Gud Boi ba32f286b9 datad: console-log the backend's mod subtree
Pre the (datad|brokerd)-split this was done by `brokerd`'s
daemon fixture, so ALSO `get_console_log()` the provider
backend's mod subtree (eg. `piker.brokers.ib.*`) in
`_setup_persistent_datad()`; without it all backend records
emitted in the `datad` actor fall through to the stdlib's
bare (non-colorized) `logging.lastResort` handler.

(this patch was generated in some part by [`claude-code`][claude-code-gh])
[claude-code-gh]: https://github.com/anthropics/claude-code

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-06-10 20:13:08 -04:00
Gud Boi b0766764f0 .ib.broker: eagerly pre-qualify pp/order mkts
Follow-up to the lazy order-req qualify: pre-qualify (and
cache) contracts for ALL already-open pps and orders during
`open_trade_dialog()` startup so live submissions NEVER pay
a first-request qualification delay; the warmup runs before
the order handler task starts so early reqs just buffer in
the ems IPC stream. Any brand-new mkt still lazily
qualifies on its first submission.

Deats,
- factor the `Client` lookup-table writes out of
  `.symbols.get_mkt_info()` into a new `cache_contract()`
  helper which now ALSO keys `_contracts` by `mkt.fqme`
  (read by the fill-time pp-update path in
  `emit_pp_update()`) alongside `mkt.bs_fqme` (read by
  `Client.submit_limit()`); resolves the old "post-split
  mktmap lookup" TODO.
- explicitly `cache_contract()` in
  `handle_order_requests()` since the lifo-cache may
  deliver a hit (body skipped) when another acct/client
  already qualified the fqme.
- filter `None` entries (ambiguous contracts) from
  `qualifyContractsAsync()` results in
  `Client.find_contracts()` before any attr access + raise
  a better "use a (more) venue-qualified fqme" error msg.
- relay ALL (non-cancel) errors from the aio method-relay
  task to the `trio`-side caller instead of crashing the
  whole proxy/dialog; critical post-`datad`-split where eg.
  qualification failures are expected to be caught
  per-request by order/warmup code.
- handle inline `('event', ...)` api-farm status msgs in
  `MethodProxy._run_method()` at info-level instead of the
  "UNKNOWN IB MSG" warning.
- only `log.setLevel()` in `open_trade_dialog()`;
  attaching a handler via `get_console_log()` double-prints
  every record since the daemon fixture already enables the
  console handler on the parent subsys logger.

(this patch was generated in some part by [`claude-code`][claude-code-gh])
[claude-code-gh]: https://github.com/anthropics/claude-code

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-06-10 20:09:27 -04:00
Gud Boi 456c6a5567 .ib.broker: lazily qualify contracts on order req
Post (datad|brokerd)-split the trading actor's
`Client._contracts` cache is never warmed by in-proc
feed setup (that now happens in the `datad.ib` sibling)
so ALL live submissions failed with "no live feed?" at
`Client.submit_limit()`; `brokerd` must be able to
submit orders without any feed registered in its own
subactor.

Deats,
- thread the acct `proxies` table into
  `handle_order_requests()` and, on a `_contracts`
  cache-miss for the req's fqme, lazily run the same
  `get_mkt_info(fqme, proxy=...)` symbology ep the
  feed-side uses; it writes the `mkt.bs_fqme` key
  `submit_limit()` looks up (and warms `_cons2mkts`
  which the position-audit path also needs) on exactly
  the same aio `Client` instance.
- guard `submit_limit()` w/ a try/except ->
  `BrokerdError` relay so a single bad submission
  degrades to an EMS error msg instead of crashing the
  dialog (and causing the `TrioTaskExited` teardown
  storm seen in testing).
- fix the (non-f-string..) raise msg in
  `Client.submit_limit()` and doc the new lazy-qualify
  contract; the bug was foretold by the TODO in
  `.symbols.get_mkt_info()` B)

(this patch was generated in some part by [`claude-code`][claude-code-gh])
[claude-code-gh]: https://github.com/anthropics/claude-code

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Prompt-IO: ai/prompt-io/claude/20260610T213549Z_f084e899_prompt_io.md
2026-06-10 17:36:51 -04:00
Gud Boi f084e89991 Add `datad`-split design plan + provenance docs
Commit the AI-authored design doc driving this branch's
7-commit `brokerd` -> (`datad` + `brokerd`) split; all
prior commits' `Prompt-IO:` entries reference its
stages so this makes those refs resolvable in-repo for
PR review.

Also,
- add the doc's own prompt-io entry pair (scope: docs)
  incl. a 4-item implementation-deviation log vs. the
  plan as written.

(this patch was generated in some part by [`claude-code`][claude-code-gh])
[claude-code-gh]: https://github.com/anthropics/claude-code

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Prompt-IO: ai/prompt-io/claude/20260610T173309Z_f15f8178_prompt_io.md
2026-06-10 13:38:50 -04:00
Gud Boi f15f8178a3 brokerd: slim RPC caps + `ib` client-id offset
Caps-sec tightening now that `brokerd` is trading-only: NO
`piker.data.*` (feed) mods are RPC-enabled in the (live,
credentialed) trading actor anymore.

Deats,
- drop `_data_mods` for a minimal `_brokerd_service_mods`
  (just `piker.brokers._daemon`); dedup-compose with the
  backend's set in `spawn_brokerd()`.
- `broker_init()` reads the backend's `_brokerd_mods`
  (fallback: `__enable_modules__` for flat backends).
- fail fast in `spawn_brokerd()` via `validate.get_eps()`
  when a backend offers NO live order-ctl eps (eg.
  `kucoin`, `deribit`) -> tells the caller to use
  paper-mode instead of booting a dead actor; analogous
  warning in `datad_init()` for datad-ep-less backends.
- offset `ib`'s default `client_id` per daemon-kind in
  `load_aio_clients()`: post-split BOTH `datad.ib` and
  `brokerd.ib` connect to the same gw/tws endpoint and the
  shared default (6116 + linear retry incrs) would collide
  and burn the full conn-timeout retry cycle; datad gets
  +16, ad-hoc (test/cli) actors +32.
- drop the import-cleanup leftovers (`exceptiongroup`,
  `_FeedsBus` type-only import) and the now-resolved
  "expose datad" TODO in `.cli`.

(this patch was generated in some part by [`claude-code`][claude-code-gh])
[claude-code-gh]: https://github.com/anthropics/claude-code

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

Prompt-IO: ai/prompt-io/claude/20260610T171344Z_eee19de0_prompt_io.md
2026-06-10 13:14:24 -04:00
Gud Boi eee19de090 .data: cut feed layer over to `datad` actors
The topology flip: all data-feed consumers now route to the
new `datad.<broker>` sibling daemon; `brokerd` becomes
trading-only and is ONLY ever booted lazily by `emsd`'s
`open_brokerd_dialog()` (see prior commit). Chart-only and
paper sessions run with zero (live, credentialed) `brokerd`
procs B)

Deats,
- `open_feed()` -> `maybe_spawn_datad()` (NB: imported
  relative-direct from `._daemon` to dodge a partial-init
  cycle via `piker.service`); flip the `open_feed_bus()`
  actor-name assert to `'datad'`; comment sweep.
- slim `_setup_persistent_brokerd()` to a trading-only
  fixture: console logging + pinned-open ctx; the feed-bus
  alloc moves to `_setup_persistent_datad()` and backend
  `open_trade_dialog()` ctxs own their own task trees.
  (the `piker ledger` ad-hoc actor enters this same slimmed
  fixture - exactly what it needs.)
- repoint data-flavoured spawn sites to `maybe_spawn_datad`:
  `.ui._app` symbol-search (+ rename
  `install_brokerd_search` -> `install_datad_search`),
  `.brokers.core.symbol_search()`, `.brokers.cli`
  `brokercheck`/`record`, legacy kivy `.ui.cli` +
  `option_chain`'s `wait_for_actor()`.
- invert `tests.test_services` expectations: feed/EMS-paper
  flows must spawn `datad.kraken` and `paperboi.kraken`
  with an explicit negative assert that NO `brokerd.kraken`
  service task exists.

(this patch was generated in some part by [`claude-code`][claude-code-gh])
[claude-code-gh]: https://github.com/anthropics/claude-code

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

Prompt-IO: ai/prompt-io/claude/20260610T171259Z_59d5d9a6_prompt_io.md
2026-06-10 13:13:44 -04:00
Gud Boi 59d5d9a66d .clearing: lazily spawn `brokerd` from `emsd`
Drop the ONE coupling that forces feed + trading eps into
the same actor: `Router.open_trade_relays()` pulling its
trades-dialog portal from `feed.portals[brokermod]`.
Instead `open_brokerd_dialog()` now (maybe) spawns/finds
`brokerd.<broker>` itself via `maybe_spawn_brokerd()` and
ONLY when a live trades-ep will actually be opened; the
paper-mode short-circuit never touches it, so post
feed-cutover paper sessions will run with zero `brokerd`
procs.

Pre-cutover this is a pure refactor: the registry lookup
just finds the same feed-spawned daemon.

Deats,
- new `open_brokerd_dialog()` sig: portal acquisition moves
  inside via an `acquire_live_portal()` helper; keep an
  explicit `portal: Portal|None` override for the
  `piker ledger` cli which boots its own ad-hoc actor.
- `Router.maybe_open_brokerd_dialog()` drops its `portal`
  param; `open_trade_relays()` drops the `feed.portals`
  lookup entirely.
- `.accounting.cli`: pass `portal=` by keyword.

(this patch was generated in some part by [`claude-code`][claude-code-gh])
[claude-code-gh]: https://github.com/anthropics/claude-code

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

Prompt-IO: ai/prompt-io/claude/20260610T171226Z_64181219_prompt_io.md
2026-06-10 13:12:59 -04:00
Gud Boi 6418121923 Add `datad` daemon machinery to `.data`
First half of the `brokerd` split: a new per-provider
data-feed-only daemon-actor `datad.<broker>` to (soon) host
all `validate._eps['datad']` eps (live quotes, history
loading, symbology search) leaving `brokerd` for live order
ctl only. Purely additive; nothing routes through it yet.

Deats,
- new `piker.data._daemon` mod mirroring the
  `.brokers._daemon` conventions (and the `samplerd`
  sub-daemon precedent):
  - `_setup_persistent_datad()` lifetime fixture owning the
    actor-global `_FeedsBus` alloc.
  - `datad_init()` building `enable_modules` from the
    backend's `_datad_mods` (falling back to
    `__enable_modules__` for not-yet-split backends) and
    copying `_spawn_kwargs` (critical for `ib`'s
    `infect_asyncio`).
  - `spawn_datad()`/`maybe_spawn_datad()` wrapping
    `Services` + `maybe_spawn_daemon()`.
- add `piker.data._daemon` to `_root_modules` so `pikerd`
  can run `spawn_datad()` requests.
- re-export the spawn eps from `piker.service`.
- add `test_datad_spawn` verifying actor boot + service
  registration via `ensure_service('datad.kraken')`.

Note the `Services`-based impl style deliberately mirrors
`spawn_brokerd()` so the eventual `tractor.hilevel`
`ServiceMngr` port (see the `service_mng_to_tractor`
branch's d8c21d44 prep work) lands symmetrically on both.

(this patch was generated in some part by [`claude-code`][claude-code-gh])
[claude-code-gh]: https://github.com/anthropics/claude-code

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

Prompt-IO: ai/prompt-io/claude/20260610T171142Z_119d2c04_prompt_io.md
2026-06-10 13:12:26 -04:00
Gud Boi 119d2c0495 Declare per-daemon-kind backend mod groups
Prep for the `brokerd` -> (`datad` + `brokerd`) actor split
by having each (split-style) backend declare which of its
submods host which daemon-kind's eps, exactly per the
`piker.data.validate._eps` groupings; `ib` already had
`_brokerd_mods`/`_datad_mods` so extend the convention to
`kraken`, `binance` and `deribit` (and add `'api'` to ib's
datad set since both kinds need the `Client` layer).

`__enable_modules__` stays as the (deduped) union so this
is a ZERO behavior change; flat backends (`kucoin` etc.)
just don't declare the split yet.

Also,
- add `validate.get_eps()` returning a backend's defined
  eps per daemon-kind for spawn-time introspection.
- import `NoBsWs`/`open_autorecon_ws` from
  `piker.data._web_bs` directly in `.kraken.broker` (they
  were only re-exported via `.kraken.feed`) so the trading
  mod doesn't depend on the feed mod for ws primitives.

(this patch was generated in some part by [`claude-code`][claude-code-gh])
[claude-code-gh]: https://github.com/anthropics/claude-code

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

Prompt-IO: ai/prompt-io/claude/20260610T171105Z_bc6e18d7_prompt_io.md
2026-06-10 13:11:42 -04:00
Gud Boi bc6e18d7b4 Port service+tests to latest `tractor` APIs
Continue the `repair_tests`-branch mission (already merged
in this stack's ancestry, see f4c4f1e2 which ported
`conftest.py`) by fixing the remaining drift breakage vs.
`tractor` git `main`; without these NOTHING boots since the
`tractor.Address` port in 604e5fcf.

Deats,
- normalize reg addrs via `wrap_address()` in
  `open_pikerd()` before `.unwrap()`-ing; entries may be
  raw `tuple`s when passed in from (test) client code.
- port `check_for_service()` to `query_actor(regaddr=)`
  (was `arbiter_sockaddr=`) incl. its 2-tuple yield and
  the now-required `open_registry(addrs=)` arg.
- `wait_for_actor(registry_addr=)` + `.chan.raddr.unwrap()`
  raw-tuple compares in `test_runtime_boot` and
  `ensure_service()`.
- update `run_test_w_cancel_method()` for modern `tractor`
  cancel semantics: self-requested sub-service cancels are
  absorbed (no `ContextCancelled` raised to the opener) and
  single-exc groups collapse to a bare KBI.
- `RemoteActorError.boxed_type` (was `.type`) and
  `Position.cumsize` (was `.size`) renames in tests.
- bump the paper-EMS startup budget 9 -> 19s; it includes
  a live (kraken) symbology fetch so needs net headroom.
- woops, add the missing comma in `.deribit.api`'s
  `tractor.trionics` import tuple..

(this patch was generated in some part by [`claude-code`][claude-code-gh])
[claude-code-gh]: https://github.com/anthropics/claude-code

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

Prompt-IO: ai/prompt-io/claude/20260610T171022Z_4485f2b9_prompt_io.md
2026-06-10 13:11:05 -04:00
Gud Boi 4485f2b9ce Fix `pytest` config-dir isolation in subactors
The old (commented-out) `get_app_dir()` override gated on
`'pytest' in sys.modules` which can NEVER work in spawned
subactors (fresh procs, no pytest import); as a result test
`paperboi`/daemon actors were writing into the user's REAL
`~/.config/piker/accounting/` files.. friggin yikes.

Deats,
- add `config._maybe_use_test_dir()` which lazily (at
  conf-path access time, NOT import time) reads the
  `piker_test_dir` entry from
  `tractor.runtime._state._runtime_vars['piker_vars']` as
  pre-loaded by `open_piker_runtime()` from the
  `tests.conftest._open_test_pikerd()` overrides.
- hook it in `get_conf_dir()` and route `get_conf_path()`
  + `load()`'s mkdir through `get_conf_dir()`.
- route `.accounting._ledger` / `._pos` dir derivation
  through `config.get_conf_dir()` (was reading the
  `_config_dir` global directly, bypassing the override);
  also `mkdir(parents=True, exist_ok=True)` for nested
  tmp-dir creation.

(this patch was generated in some part by [`claude-code`][claude-code-gh])
[claude-code-gh]: https://github.com/anthropics/claude-code

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

Prompt-IO: ai/prompt-io/claude/20260610T170859Z_75cefe10_prompt_io.md
2026-06-10 13:10:09 -04:00
Gud Boi 75cefe10f1 ib: attempt to REPL closed connection events 2026-06-09 14:03:41 -04:00
Gud Boi b547a33da4 ib: attempt to REPL closed connection events 2026-06-09 14:03:34 -04:00
Gud Boi 0df3943f3c Pin `tractor` to git `main`, bump `xonsh` deps
Switch the `tractor` dep from the local editable checkout
(`../tractor`) back to the upstream git `main` branch so a
fresh clone can `uv sync` without a sibling repo.

Also,
- add `xonsh>=0.23.8` as a core dep
- bump `repl` group pins: `xonsh>=0.23.0` and
  `prompt-toolkit>=3.0.50` (was `==3.0.40`)
- relock `uv.lock` accordingly

(this commit msg was generated in some part by [`claude-code`][claude-code-gh])
[claude-code-gh]: https://github.com/anthropics/claude-code
2026-06-09 14:00:20 -04:00
Gud Boi 5466acb764 Add `tpt_bind_addrs` and separate registry eps
Thread a `tpt_bind_addrs` param through
`open_piker_runtime()` and `open_pikerd()` so
pikerd's bind addrs can differ from the registry
endpoint (support a dedicated `regd` service).
Simplify `load_trans_eps()` to delegate entirely
to `tractor.discovery.parse_endpoints()`.

Deats,
- `conf.toml`: fix maddr prefixes to proper `/ip4/` and `/unix/`, add
  `chart` endpoints section, add commented `regd` example.
- `cli/__init__.py`: replace `parse_maddr` with `parse_endpoints`,
  rename `--maddr` -> `--maddrs`, parse `regd` key from eps falling back
  to `pikerd` addrs.
- `_actor_runtime.py`: thread `tpt_bind_addrs` through runtime open fns
  to `open_root_actor`.
- `ui/cli.py`: activate `network` config parsing for chart CLI, extract
  `chart` eps for bind and `regd`/`pikerd` for registry, pass
  `tpt_bind_addrs` through runtime config.

(this commit msg was generated in some part by [`claude-code`][claude-code-gh])
[claude-code-gh]: https://github.com/anthropics/claude-code
2026-04-16 13:32:04 -04:00
Gud Boi 604e5fcf9c Use `tractor.Address` for endpoint resolution
Replace manual `layers['ipv4']['addr']` / `layers['tcp']['port']` tuple
extraction with direct `tractor.Address` objects returned from
`load_trans_eps()`. In `open_pikerd()` call `addr.unwrap()` for the
raw-tuple comparison against `root_actor.accept_addrs`.

Deats,
- `conf.toml`: update maddr prefix `/ipv4/` ->
  `/ipv/`, add commented UDS socket path example.
- `cli/__init__.py`: wrap endpoint loading in
  `maybe_open_crash_handler`, append `addr`
  objects directly to `regaddrs`.
- `ui/cli.py`: restructure `chart()` body into
  `maybe_open_crash_handler` scope, switch to
  `registry_addrs` from config, comment out the
  `network`-based `load_trans_eps` path (WIP
  `multiaddr` transition).
- `_actor_runtime.py`: use `addr.unwrap()` for
  accept-addr membership check.
- `uv.lock`: add `multiaddr >= 0.2.0` and its
  transitive deps.

(this commit msg was generated in some part by [`claude-code`][claude-code-gh])
[claude-code-gh]: https://github.com/anthropics/claude-code
2026-04-16 13:32:04 -04:00
Gud Boi 887e1ea6b7 Use walrus `getattr()` over `hasattr()` in `_window`
Replace all nested `hasattr()` + re-access chains
with `:= getattr(..., None)` walrus assigns
throughout the zoom UI methods; flattens deeply
nested `if hasattr` / `if hasattr` / `if hasattr`
pyramids into single chained `and` conditions.

Also,
- apply multiline code style per `py-codestyle`
  (list literals, fn sigs, `except` clauses,
  comments, docstrings)
- replace bare `pass` in `except` handlers with
  `log.exception()` calls
- fix `_qt_win` annotation to `QMainWindow|None`

(this patch was generated in some part by [`claude-code`][claude-code-gh])
[claude-code-gh]: https://github.com/anthropics/claude-code
2026-04-16 13:31:09 -04:00
Gud Boi 54da297304 Tighten logging and annotations in `_web_bs`
Split multi-value log msgs onto separate f-str
lines, add `!r` to URL and error format refs,
and fix `response_type` annotation from bare
`type` to `Type[Struct]`.

Also,
- Use `X|Y` union style (no spaces).
- Add `-> None` return hint to `proxy_msgs()`.
- Single backticks in `fixture` docstring ref.
- Expand `Callable` return type across lines.

(this commit msg was generated in some part by [`claude-code`][claude-code-gh])
[claude-code-gh]: https://github.com/anthropics/claude-code
2026-04-16 13:31:09 -04:00
Gud Boi 9f7c38a37b Use `ppfmt()` in EMS and guard `brokerd_msg` set
Replace all `pformat()` calls with `ppfmt()` from
`tractor.devx.pformat` and drop the `pprint`
import. Guard `status_msg.brokerd_msg = msg` with
an `if not` check to avoid clobbering a value
already set by earlier processing.

Also,
- Add `!r` to `broker` in a couple log msgs.

(this commit msg was generated in some part by [`claude-code`][claude-code-gh])
[claude-code-gh]: https://github.com/anthropics/claude-code
2026-04-16 13:31:09 -04:00
Gud Boi 8299c65818 Clean up `TooFastEdit` remnants and ws-token flow
Drop all commented-out `TooFastEdit` class,
`reg_err_types`, and `isinstance()` references.
Replace the hard ws-token `assert` in
`subscribe()` with a soft mismatch log that
updates the local `token` ref; cache the result
as `latest_token` for use in sub msgs.

Also,
- Comment out the `reg_err_types` import.
- Switch `pformat` -> `ppfmt` in `openOrders` update log.

(this commit msg was generated in some part by [`claude-code`][claude-code-gh])
[claude-code-gh]: https://github.com/anthropics/claude-code
2026-04-16 13:30:00 -04:00
Gud Boi 170c95da28 Fall back to `con.exchange` in IB ledger fill loop
Use `con.primaryExchange or con.exchange` so
`pexch` is populated even when `primaryExchange`
is empty (e.g. for certain combo/forex fills).

(this commit msg was generated in some part by [`claude-code`][claude-code-gh])
[claude-code-gh]: https://github.com/anthropics/claude-code
2026-04-16 13:30:00 -04:00
Gud Boi e1cd3fd955 Replace `TooFastEdit` sentinel with `set` tracker
Drop the pattern of storing a `TooFastEdit` exc
instance in `reqids2txids` as a sentinel value;
instead track affected reqids in a dedicated
`toofastedit: set[int]` and check membership
via `reqid in toofastedit`.

Deats,
- Comment out `TooFastEdit` class and its `reg_err_types()` call.
- Add `toofastedit` param to both `handle_order_requests()` and
  `handle_order_updates()`, threaded from `open_trade_dialog()`.

Also,
- Use `partial()` with kwargs for the `tn.start_soon()` call to the
  order handler.
- Add `await tractor.pause()` on the too-fast edit path for runtime
  debugging; will remove once confident this all works.
- Expand comments explaining the cancel/edit race condition.

(this commit msg was generated in some part by [`claude-code`][claude-code-gh])
[claude-code-gh]: https://github.com/anthropics/claude-code
2026-04-16 13:29:55 -04:00
Gud Boi 8cefc1bdf8 Guard `brokerd_msg` set in order-mode dialog loop
Use `msg.setdefault('brokerd_msg', msg)` instead of blind assignment and
log a warning when the field was already populated.

Specifically, this avoids a self-reference field recursion which causes
crashes when using `tractor.devx.pformat.ppfmt()`..

(this commit msg was generated in some part by [`claude-code`][claude-code-gh])
[claude-code-gh]: https://github.com/anthropics/claude-code
2026-04-15 18:54:33 -04:00
Gud Boi 709269fcf7 Cache ws-token on `Client` and auto-refresh
Add `_ws_token` cache attr to `Client` with a
`force_renewal` flag on `get_ws_token()`. Drop
the `token` param threading through
`handle_order_requests()` and
`handle_order_updates()` — all call sites now
use `await client.get_ws_token()` instead.

Deats,
- `api.py`: add `_ws_token: str|None = None`,
  return cached token unless `force_renewal`,
  comment out `InvalidKey`/`InvalidSession`
  classes and `reg_err_types()` call (WIP move).
- `broker.py`: drop `token` param from
  `handle_order_requests()`,
  `handle_order_updates()`, and call sites;
  replace all `token` refs with
  `await client.get_ws_token()`.
- `subscribe()`: rework `InvalidSession` handling
  to match on `(etype_str, ev_msg)` tuple, call
  `get_ws_token(force_renewal=True)` and
  `continue` the sub-ack loop; extract `fmt_msg`
  var to avoid repeated `ppfmt()` calls.

(this commit msg was generated in some part by [`claude-code`][claude-code-gh])
[claude-code-gh]: https://github.com/anthropics/claude-code
2026-04-14 14:29:26 -04:00
Gud Boi 5387538ba9 Add `InvalidSession` exc and ws-token refresh
Introduce `InvalidSession` for stale ws auth sessions (err-msg
'ESession:Invalid session') and factor the token-fetch into a new
`Client.get_ws_token()`. In `subscribe()`, dynamically resolve the exc
type from kraken's error-type str via `getattr()` on the `api` mod and
begin handling `InvalidSession` with a token refresh attempt.

Deats,
- `.kraken.api`: add `InvalidSession(RuntimeError)` with `subscription`
  attr, register it alongside `InvalidKey` in `reg_err_types()`, add
  `get_ws_token()` method.
- `.broker`: import `api` mod instead of individual names (`Client`,
  `BrokerError`), rework ws sub error handling to parse the kraken
  error-type prefix and resolve the matching exc class, add catch-all
  `case _:` for unknown ws events, pass `client` to `subscribe()`
  fixture, replace inline token fetch with `client.get_ws_token()`.

Also,
- Rename `nurse` -> `tn` for "task nursery" convention.
- Use `ppfmt()` for ws msg formatting.

(this commit msg was generated in some part by [`claude-code`][claude-code-gh])
[claude-code-gh]: https://github.com/anthropics/claude-code
2026-04-01 16:45:01 -04:00
Gud Boi d4dc8854e0 Use `tn` for nursery vars in UI modules
Rename `root_n` -> `tn` in `_app.py` and
`ln` -> `tn` in `_display.py` to match the `trio` nursery naming
convention used elsewhere. Drop a couple stray blank lines.

(this commit msg was generated in some part by [`claude-code`][claude-code-gh])
[claude-code-gh]: https://github.com/anthropics/claude-code
2026-04-01 14:05:08 -04:00
Gud Boi a0586f8219 Always re-raise in `maybe_spawn_daemon()` handler
Move bare `raise` outside the `if lock.owner` guard so the error
propagates regardless of whether the stale-lock branch runs. Also add
a blank-line separator in the crash log msg.

(this commit msg was generated in some part by [`claude-code`][claude-code-gh])
[claude-code-gh]: https://github.com/anthropics/claude-code
2026-03-31 19:43:27 -04:00
Gud Boi 923b4de296 Improve `load_accounts()` logging and defaults
Move `'paper'` default entry into the initial `bidict` instead of
appending post-loop. Add per-provider branch logging: an `info`-level
msg accumulating each loaded `account_alias` and a `debug`-level msg
(using `ppfmt()`) when a provider is skipped bc it wasn't requested.

Also,
- Early-`continue` when `accounts_section is None` instead of nesting
  inside an `else`.
- Import `ppfmt` from `tractor.devx.pformat`.
- Tighten union-type annotations to `X|Y` style.
- De-structure loop vars for readability.

(this commit msg was generated in some part by [`claude-code`][claude-code-gh])
[claude-code-gh]: https://github.com/anthropics/claude-code
2026-03-31 14:16:40 -04:00
Gud Boi ebc5bbd42b Fix kraken account-alias config mismatch
Rename `Client._name` -> `Client._key_descr` so the attr actually
describes what it holds (the `key_descr` field from `brokers.toml`). In
`open_trade_dialog()` look up the account-alias via `conf['accounts']`
and raise a `ConfigurationError` with a config-file example when no
matching entry exists.

Deats,
- `api.py`: rename `name` param/attr to `key_descr`, add docstring to
  `get_client()`, pull `conf['key_descr']` into a named local.
- `broker.py`: replace `acc_name` with `fqan` (fully-qualified account
  name), add accounts dict validation with actionable error msg.
- `brokers.toml`: add `src_fiat`, `accounts.spot` entry, and comments
  explaining the required field relationships.

(this commit-msg was generated in some part by [`claude-code`][claude-code-gh])
[claude-code-gh]: https://github.com/anthropics/claude-code
2026-03-31 14:02:32 -04:00
Tyler Goodlet 4cfe0a9dac Drop `.cancel_actor()` from `maybe_spawn_daemon()`
Since `tractor`'s new and improved inter-actor cancellation semantics
are much more pedantic, AND bc we use the `ServiceMngr` for spawning
service actors on-demand, the caller of `maybe_spawn_daemon()` should
NEVER conduct a so called "out of band" `Actor`-runtime cancel request
since this is precisely the job of our `ServiceMngr` XD

Add a super in depth note explaining the underlying issue and adding
a todo list of how we should prolly augment `tractor` to make such cases
easier to grok and fix in the future!
2026-03-30 20:06:43 -04:00
Gud Boi f210a478c6 Move `reg_err_types` imports to module-level
Hoist inline `from tractor._exceptions import reg_err_types` calls up to
the module-level import block across 5 files so they follow normal
import ordering.

Other,
- `kraken/broker.py`: same; also add `ConfigurationError` import and
  raise on missing `src_fiat` config field instead of `KeyError`.
- `storage/__init__.py`: same; also switch from relative to absolute
  `piker.*` imports and reorder the import block.
- comment out stray `await tractor.pause()` in binance `feed.py`.

(this commit msg was generated in some part by [`claude-code`][claude-code-gh])
[claude-code-gh]: https://github.com/anthropics/claude-code
2026-03-30 13:25:41 -04:00
Gud Boi 79be47635f Harden `.ib.venues` against unknown exchange cals
Deats,
- catch `InvalidCalendarName` in `has_holiday()` so
  venues without an `exchange_calendars` entry (eg.
  `IDEALPRO` for forex, `PAXOS` for crypto) gracefully
  return `False` instead of raising.
- add `log` via `get_logger()` to emit a warning when
  skipping the holiday check for an unmapped venue.
- fix `std_exch` type annot from `dict` -> `str`.
- guard `is_expired()` against empty
  `.realExpirationDate` strings.
- fill in `is_expired()` docstring.

(this patch was generated in some part by [`claude-code`][claude-code-gh])
[claude-code-gh]: https://github.com/anthropics/claude-code
2026-03-24 20:21:29 -04:00
Gud Boi 26aaec2e0c Add `ls` caps for claudy 2026-03-24 20:21:29 -04:00
Gud Boi 4bfe8d99b3 Swap `open_channel_from()` yield-pair order
Port deribit and IB `asyncio` bridge callables to the new
`to_asyncio.open_channel_from()` signature where the `LinkedTaskChannel`
is the first param and `started_nowait()` replaces the old
`to_trio.send_nowait()` sync handshake.

Deats,
- deribit `api.py`: update `aio_price_feed_relay()` and
  `aio_order_feed_relay()` signatures to take `chan: LinkedTaskChannel`
  as first arg; drop `from_trio`/`to_trio` params; replace
  `to_trio.send_nowait()` with `chan.send_nowait()` and
  `chan.started_nowait()`.
- drop `functools.partial()` wrapping in both `open_price_feed()` and
  `open_order_feed()`; pass `fh=`/`instrument=` as kwargs directly.
- IB `broker.py`: same `chan` + `started_nowait()` port for
  `recv_trade_updates()`.

Other styling,
- rewrap `recv_trade_updates()` docstring to 67 chars.

(this patch was generated in some part by [`claude-code`][claude-code-gh])
[claude-code-gh]: https://github.com/anthropics/claude-code
2026-03-24 20:21:29 -04:00
Gud Boi 611597ee18 Update `tractor` private-API refs across codebase
Port internal `tractor._<mod>` references to their
new public or reorganized paths after `tractor`
refactored its subpkg layout.

Deats,
- `tractor._portal.Portal` -> `tractor.Portal`.
- `tractor._supervise.ActorNursery` -> `tractor.ActorNursery`.
- `tractor._multiaddr` -> `tractor.discovery._multiaddr`.
- `tractor._addr` -> `tractor.discovery._addr`.
- `tractor._state._runtime_vars` -> `tractor.runtime._state._runtime_vars`.
- `tractor._state.is_debug_mode()` -> `tractor.runtime._state.is_debug_mode()`.

Files touched: `brokers/data.py`, `cli/__init__.py`, `data/feed.py`,
`service/_actor_runtime.py`, `service/_mngr.py`, `storage/cli.py`,
`tsp/_annotate.py`, `ui/kivy/monitor.py`, `ui/kivy/option_chain.py`.

(this commit msg was generated in some part by [`claude-code`][claude-code-gh])
[claude-code-gh]: https://github.com/anthropics/claude-code
2026-03-24 20:21:29 -04:00
Gud Boi 94ac2ee82a Use pre-set reg addrs in `maybe_spawn_daemon()`
Pull actor-runtime `registry_addrs` from (the new)
`tractor.get_runtime_vars()` (over the previous hardcoding of
`('127.0.0.1', 6116)`..)) so that underlying `find_service()` and
`maybe_open_pikerd()` calls use the local actor's assigned registrar
endpoints.

Note, this is particularly necessary to get the `pytest` harness workin
again alongside any local running `pikerd` instance(s).

(this commit msg was generated in some part by [`claude-code`][claude-code-gh])
[claude-code-gh]: https://github.com/anthropics/claude-code
2026-03-24 20:21:29 -04:00
Gud Boi bf43036fe1 Add `MarketNotFound` exc and improve binance fqme error
Add a `MarketNotFound(SymbolNotFound)` subclass for
mkt-pair-specific lookup failures; use it in binance
`get_mkt_info()` with a detailed expected-form hint.

Deats,
- add `MarketNotFound` in `brokers/_util.py`.
- re-export from `brokers/__init__.py`.
- binance `feed.py`: swap `SymbolNotFound` import
  for `MarketNotFound`; build `expected` string
  showing the `<pair>.<venue>.<broker>` format
  and suggest `".spot."` if venue is missing.

(this commit msg was generated in some part by [`claude-code`][claude-code-gh])
[claude-code-gh]: https://github.com/anthropics/claude-code
2026-03-24 20:21:29 -04:00
Gud Boi 8424c368f6 Update feed test fqmes to use `.<venue>` format
Switch all `test_feeds.py` parametrized fqmes from
legacy `<pair>.<broker>` to the current
`<pair>.<venue>.<broker>` schema (e.g.
`btcusdt.spot.binance`).

Deats,
- update binance fqmes: `btcusdt.binance` ->
  `btcusdt.spot.binance`, same for `ethusdt`.
- update kraken fqmes: `ethusdt.kraken` ->
  `ethusdt.spot.kraken`, `xbtusd.kraken` ->
  `xbtusd.spot.kraken`.
- update cross-broker set similarly.
- comment out old fqmes with `!TODO` to later
  validate raising on bad/legacy formats.

Also,
- reformat `if ci_env and not run_in_ci` to
  multiline style.
- reformat `pytest.skip()` msg to multiline.
- add `?TODO` for symbology helper fn.
- drop stray `await tractor.pause()` in
  `conftest.py`.

(this commit msg was generated in some part by [`claude-code`][claude-code-gh])
[claude-code-gh]: https://github.com/anthropics/claude-code
2026-03-24 20:21:29 -04:00
Gud Boi 84c742a885 Register all custom excs with `tractor` IPC
Call `reg_err_types()` for every piker-defined
exception so they can be marshalled and re-raised
across actor boundaries.

Deats,
- `brokers/_util.py`: auto-register `BrokerError` +
  all `__subclasses__()` (6 types).
- `config.py`: `ConfigurationError` +
  `__subclasses__()` (`NoSignature`).
- `data/validate.py`: `FeedInitializationError`.
- `service/_ahab.py`: `DockerNotStarted`,
  `ApplicationLogError`.
- `service/marketstore.py`: `MarketStoreError`.
- `storage/__init__.py`: `TimeseriesNotFound`,
  `StorageConnectionError`.
- `brokers/kraken/api.py`: `InvalidKey`.
- `brokers/kraken/broker.py`: `TooFastEdit`.
- `brokers/questrade.py`: `QuestradeError`.

Also,
- uncomment `execution_venue` field on kraken `Pair`.

(this patch was generated in some part by [`claude-code`][claude-code-gh])
[claude-code-gh]: https://github.com/anthropics/claude-code
2026-03-24 20:21:29 -04:00
Gud Boi 7f8a198c98 Factor `Pair` schema-mismatch handling to `_util`
Add `get_or_raise_on_pair_schema_mismatch()` helper
and `SchemaMismatch` error type in `brokers._util`
to standardize the "provider changed their API" error
reporting across backends.

Deats,
- add `SchemaMismatch(BrokerError)` exc type.
- `get_or_raise_on_pair_schema_mismatch()`: catch
  `TypeError` on `Pair` ctor, build `ppfmt()`-ed
  report with provider name, fall back to
  `pair_type._api_url` if no explicit URL passed,
  then raise `SchemaMismatch`.
- binance `api.py`: replace inline `try/except` +
  `e.add_note()` with the new helper.
- kraken `api.py`: replace bare `Pair(...)` ctor
  with the new helper inside crash handler.

Also,
- add `_api_url: ClassVar[str]` to binance
  `FutesPair` and kraken `Pair` structs.
- binance `feed.py`: warn on missing `.<provider>`
  in fqme; raise `SymbolNotFound` on empty venue.
- reformat `start_dt`/`end_dt` unions to
  `datetime|None` style in binance `Client`.
- wrap binance `_pairs` lookup in
  `maybe_open_crash_handler()`.

(this commit msg was generated in some part by [`claude-code`][claude-code-gh])
[claude-code-gh]: https://github.com/anthropics/claude-code
2026-03-24 20:21:29 -04:00
Gud Boi de82df727d Add `is_expired()` and harden `.ib.venues` helpers
Add an expiry-date predicate and guard venue session
lookups against expired contracts and empty session
lists in `.ib.venues`; use in `api.py` to skip gap
detection for expired tracts.

Deats,
- add `is_expired()` predicate using
  `pendulum.parse()` on `realExpirationDate`.
- `sesh_times()`: raise `ValueError` if contract is
  expired or has no session intervals (instead of
  `StopIteration` from `next(iter(...))`).
- `is_venue_closure()`: handle `None` return from
  `sesh_times()` with guard + `breakpoint()`.

Also in `api.py`,
- import and call `is_expired()` from `.venues`.
- gate gap-detection on `not _is_expired`.
- default `timeZoneId` to `'EST'` when IB returns
  empty/`None`.

(this commit msg was generated in some part by [`claude-code`][claude-code-gh])
[claude-code-gh]: https://github.com/anthropics/claude-code
2026-03-24 20:21:29 -04:00
Gud Boi 5df511f7d2 Handle `[Errno ...]` str-errors in `.ib.broker`
Extend the str-type error code parser to also match
`[Errno <N>]` prefixed msgs (not just `[code <N>]`)
by iterating a list of prefix patterns and
`int()`-casting the extracted code on first match.

(this commit msg was generated in some part by [`claude-code`][claude-code-gh])
[claude-code-gh]: https://github.com/anthropics/claude-code
2026-03-24 20:21:29 -04:00
Gud Boi f4c4f1e2d5 Update `conftest.py` for `tractor` runtime API changes
Port test fixtures to match `tractor`'s updated
registry and channel APIs.

Deats,
- use `registry_addrs=[reg_addr]` (list) instead of
  `registry_addr=reg_addr` in `maybe_open_pikerd()`.
- `wait_for_actor()` now takes `registry_addr=`
  kwarg instead of `arbiter_sockaddr=`.
- access `portal.chan` (not `.channel`) and unwrap
  remote addr via `raddr.unwrap()`.
- yield `raddr._host`/`raddr._port` instead of
  tuple-indexing.
- drop random port generation; accept `reg_addr`
  fixture from `tractor`'s builtin pytest plugin.

Also,
- add `reg_addr: tuple` param to `open_test_pikerd()`
  fixture (sourced from `tractor._testing.pytest`).
- type-narrow `reg_addr` to `tuple[str, int|str]`.
- drop unused `import random`.

(this commit msg was generated in some part by [`claude-code`][claude-code-gh])
[claude-code-gh]: https://github.com/anthropics/claude-code
2026-03-24 20:21:29 -04:00
Gud Boi 24f2712d58 Gate `size_to_values()` on macOS in `_axes.py`
NOTE, this reversion was discovered as needed by @goodboy after
extensively manually testing the new zoom-by-font-size feats introduced
alongside macOS support.

Use class-body `if _friggin_macos:` branching to
conditionally define `size_to_values()` for both
`PriceAxis` and `DynamicDateAxis` — macOS gets the
new `_updateWidth()`/`_updateHeight()` + geometry
recalc path, other platforms fall back to the
original `setWidth()`/`setHeight()` calls.

Deats,
- add `platform` import and module-level
  `_friggin_macos: bool` flag.
- `PriceAxis.size_to_values()`: macOS branch calls
  `_updateWidth()` + `updateGeometry()`; else branch
  uses `self.setWidth(self.typical_br.width())`.
- `DynamicDateAxis.size_to_values()`: macOS branch
  calls `_updateHeight()` + `updateGeometry()`; else
  uses `self.setHeight(self.typical_br.height() + 1)`.
- reorder imports: `platform` before `typing`.

(this commit msg was generated in some part by [`claude-code`][claude-code-gh])
[claude-code-gh]: https://github.com/anthropics/claude-code
2026-03-24 20:21:29 -04:00
Gud Boi f9979956a0 EDITABLE `tractor` 2026-03-18 15:06:37 -04:00
Gud Boi 31859e9d03 Exclude crypto futes from `without_src` sym key
Extend the `col_sym_key` asset-type check in `start_backfill()`
to also exclude crypto-denominated futures (where `src` is
`'crypto_currency'` and `dst` is `'future'`) from the
`without_src=True` fqme path.

Also in `.brokers.binance` backend (it being the guilty culprit in the
discovery of this bug; and why i touched styling this code),

- reformat `make_sub()` fn sig to multiline style in
  `.binance.feed`.
- add backtick around `dict` in `make_sub()` docstring.
- reformat `or` conditionals to multiline style in
  `.binance.feed.get_mkt_info()`.

(this commit msg was generated in some part by [`claude-code`][claude-code-gh])
[claude-code-gh]: https://github.com/anthropics/claude-code
2026-03-17 21:15:31 -04:00
Gud Boi e30957b62f Drop `Flume.feed`, it's unused yet causes import cycles.. 2026-03-17 21:15:31 -04:00
Gud Boi b87710e999 Just warn on single-bar nulls instead of bping
Replace the debug breakpoint with a warning-log when a single-bar
null-segment is detected in `get_null_segs()`. This lets the gap
analysis continue while still alerting about the anomaly.

Deats,
- extract the 3-bar window (before, null, after) and calculate
  a `gap: pendulum.Interval` for the warning msg.
- comment-out the old breakpoint block for optional debugging as needed.

(this commit msg was generated in some part by [`claude-code`][claude-code-gh])
[claude-code-gh]: https://github.com/anthropics/claude-code
2026-03-17 21:15:31 -04:00
Gud Boi 392ea6162a Lul, drop long unused poetry lock file 2026-03-17 21:15:31 -04:00
Gud Boi d98c1706d6 Pin `pg` at latest official `0.14.0` release
Keep in masked GH sources lines for easy hackin against upstream
`master` branch when needed as well!
2026-03-17 21:15:31 -04:00
Gud Boi 88d309a613 .ui._editors: log multiline styling and re-leveling 2026-03-17 21:15:31 -04:00
Gud Boi 87707e4239 .ui._lines: drop unused graphics-item import 2026-03-17 21:15:31 -04:00
Gud Boi 073176a4c2 Add batch-submit API for gap annotations
Introduce `AnnotCtl.add_batch()` and `serve_rc_annots()` batch
handler to submit 1000s of gaps in single IPC msg instead of
per-annot round-trips. Server builds `GapAnnotations` from specs
and handles vectorized timestamp-to-index lookups.

Deats,
- add `'cmd': 'batch'` handler in `serve_rc_annots()`
- vectorized timestamp lookup via `np.searchsorted()` + masking
- build `gap_specs: list[dict]` from rect+arrow specs client-side
- create single `GapAnnotations` item for all gaps server-side
- handle `GapAnnotations.reposition()` in redraw handler
- add profiling to batch path for perf measurement
- support optional individual arrows for A/B comparison

Also,
- refactor `markup_gaps()` to collect specs + single batch call
- add `no_qt_updates()` context mgr for batch render ops
- add profiling to annotation teardown path
- add `GapAnnotations` case to `rm_annot()` match block

(this patch was generated in some part by [`claude-code`][claude-code-gh])
[claude-code-gh]: https://github.com/anthropics/claude-code
2026-03-17 21:15:31 -04:00
Gud Boi 8c08ddd38c Add a `GapAnnotations` path-renderer
For a ~1000x perf gain says ol' claudy, our boi who wrote this entire
patch! Bo

Introduce `GapAnnotations` in `.ui._annotate` for batch-rendering gap
rects/arrows instead of individual `QGraphicsItem` instances. Uses
upstream's `pyqtgraph.Qt.internals.PrimitiveArray` for rects and
a `QPainterPath` for arrows. This API-replicates our prior annotator's
in view shape-graphics but now using (what we're dubbing)
"single-array-multiple-graphics" tech much like our `.ui._curve`
extensions to `pg` B)

Impl deats,
- batch draw ~1000 gaps in single paint call vs 1000 items
- arrows render in scene coords to maintain pixel size on zoom
- add vectorized timestamp-to-index lookup for repositioning
- cache bounding rect, rebuild on `reposition()` calls
- match `SelectRect` + `ArrowItem` visual style/colors
- skip reposition when timeframe doesn't match gap's period

Other,
- fix typo in `LevelMarker` docstring: "graphich" -> "graphic"
- reflow docstring in `qgo_draw_markers()` to 67 char limit

(this patch was generated in some part by [`claude-code`][claude-code-gh])
[claude-code-gh]: https://github.com/anthropics/claude-code
2026-03-17 21:15:31 -04:00
Gud Boi b4c905b592 Add info log for shm processing in `ldshm` CLI cmd
Log shm file name and detected period before null segment
processing to aid debugging.

(this patch was generated in some part by [`claude-code`][claude-code-gh])
[claude-code-gh]: https://github.com/anthropics/claude-code
2026-03-17 21:15:31 -04:00
Gud Boi 494bc4ce85 Bump to latest official `pyqtgraph` release 2026-03-17 21:15:31 -04:00
Gud Boi ce8ad59f7c Improve styling and logging for UI font-size zoom
Refine zoom methods in `MainWindow` and font helpers
in `_style` to return `px_size` up the call chain and
log detailed zoom state on each change.

Deats,
- make `_set_qfont_px_size()` return `self.px_size`.
- make `configure_to_dpi()` and `_config_fonts_to_screen()`
  return the new `px_size` up through the call chain.
- add `font_size` to `log.info()` in `zoom_in()`,
  `zoom_out()`, and `reset_zoom()` alongside
  `zoom_step` and `zoom_level(%)`.
- reformat `has_ctrl`/`_has_shift` bitwise checks and
  key-match tuples to multiline style.
- comment out `Shift` modifier requirement for zoom
  hotkeys (now `Ctrl`-only).
- comment out unused `mn_dpi` and `dpi` locals.

Also,
- convert all single-line docstrings to `'''` multiline
  style across zoom and font methods.
- rewrap `configure_to_dpi()` docstring to 67 chars.
- move `from . import _style` to module-level import
  in `_window.py`.
- drop unused `screen` binding in `boundingRect()`.

(this commit msg was generated in some part by [`claude-code`][claude-code-gh])
[claude-code-gh]: https://github.com/anthropics/claude-code
2026-03-17 21:15:19 -04:00
di1ara 2e42d7e1c4 improve ui zoom defaults 2026-03-17 21:15:19 -04:00
Gud Boi 17e7232d12 Fix chart axis scaling on UI zoom level change
Again a patch (vibed) from our very own @dnks
(just a commit msg reworking using his new `/commit-msg` skill added by
@goodboy B)

Deats,
- add `Axis.update_fonts()` to recalculate tick font, text offset,
  bounding rect and `pyqtgraph`'s internal text-width/height tracking
  after a zoom change; store `_typical_max_str` at init for later reuse.
- rework `PriceAxis.size_to_values()` and
  `DynamicDateAxis.size_to_values()` to use pyqtgraph's
  `_updateWidth()`/`_updateHeight()` with `updateGeometry()` instead of
  raw `setWidth()`/ `setHeight()` so auto-expand constraints are
  respected.
- fix `GlobalZoomEventFilter` to mask out `KeypadModifier` and
  explicitly require both Ctrl+Shift, letting plain Ctrl+Plus/Minus pass
  through to chart zoom.
- add `_update_chart_axes()` to walk all plot-item axes during
  `_apply_zoom()` and call `splits.resize_sidepanes()` to sync subplot
  widths.

(this commit msg, and likely patch, was generated in some part by
[`claude-code`][claude-code-gh])
[claude-code-gh]: https://github.com/anthropics/claude-code
2026-03-17 21:15:19 -04:00
Gud Boi 88f66baeed Add global UI font-size zoom scaling (from @dnks)
Add `Ctrl+Shift+Plus/Minus/0` shortcuts for zooming all
UI widget font sizes via a `GlobalZoomEventFilter`
installed at the `QApplication` level.

Deats,
- `.ui._window`: add `GlobalZoomEventFilter` event
  filter class and `MainWindow.zoom_in/out/reset_zoom()`
  methods that reconfigure `DpiAwareFont` with a
  `zoom_level` multiplier then propagate to all child
  widgets.
- `.ui._style`: extend `DpiAwareFont.configure_to_dpi()`
  and `_config_fonts_to_screen()` to accept a
  `zoom_level` float multiplier; cast `px_size` to `int`.
- `.ui._forms`: add `update_fonts()` to `Edit`,
  `Selection`, `FieldsForm` and `FillStatusBar` for
  stylesheet regen.
- `.ui._label`: add `FormatLabel.update_font()` method.
- `.ui._position`: add `SettingsPane.update_fonts()`.
- `.ui._search`: add `update_fonts()` to `CompleterView`
  and `SearchWidget`.
- `.ui._exec`: install the zoom filter on window show.
- `.ui.qt`: import `QObject` from `PyQt6`.

(this commit msg was generated in some part by [`claude-code`][claude-code-gh])
[claude-code-gh]: https://github.com/anthropics/claude-code
2026-03-17 21:15:19 -04:00
Tyler Goodlet 3f77180b63 Add `.xsh` script mentioned in gitea #50
Note since it's actually `xonsh` code run with either,
- most pedantically: `xonsh ./snippets/calc_ppi.xsh`
- or relying on how shebang: `./snippets/calc_ppi.xsh`
  * an sheboom.
2026-03-17 21:15:19 -04:00
Tyler Goodlet ccc5a745de Reorder imports in `qt_screen_info.py` ??
For wtv reason on nixos importing `pyqtgraph` first is causing `numpy`
to fail import?? No idea, but likely something to do with recent
`flake.nix`'s ld-lib-linking with `<nixpkgs>` marlarky?
2026-03-17 21:15:19 -04:00
Tyler Goodlet 5b1c80a8a5 Add some Qt DPI extras to `qt_screen_info.py`
- set `QT_USE_PHYSICAL_DPI='1'` env var for Qt6 high-DPI
  * we likely want to do this in `piker.ui` as well!
- move `pxr` calc from widget to per-screen in loop.
- add `unscaled_size` calc using `pxr * size`.
- switch from `.availableGeometry()` to `.geometry()` for full
  bounds.
- shorten output labels, add `!r` repr formatting
- add Qt6 DPI rounding policy TODO with doc links

(this patch was generated in some part by [`claude-code`][claude-code-gh])
[claude-code-gh]: https://github.com/anthropics/claude-code
2026-03-17 21:15:19 -04:00
Tyler Goodlet e600d61eef Re-fmt and `.info()` the `.configure_to_dpi()` DPI calcs for now 2026-03-17 21:15:19 -04:00
di1ara 5c7f00a3b8 fixed spacing 2026-03-17 21:15:19 -04:00
di1ara 3b0c27ec44 fixed pytest test for dpi font auto calculation 2026-03-17 21:15:19 -04:00
di1ara 574fb80d79 added pytest, moved dependencies 2026-03-17 21:15:19 -04:00
di1ara cdb0e8411a fix DpiAwareFont default size calculation 2026-03-17 21:15:19 -04:00
277 changed files with 27453 additions and 4085 deletions

View File

@ -0,0 +1,174 @@
---
name: piker-clearing-expert
description: >
Piker clearing, EMS/order-control, accounting, position, and
trade-ledger expertise. Apply when changing or debugging Order,
Status, Brokerd* protocols, live or paper fills, dark orders,
order IDs, TransactionLedger persistence, Account/Position/PPU,
FQME/bs_mktid identity, SymbologyCache lookups, or broker trade
normalization.
compatibility: >
Requires a piker checkout and familiarity with Python, trio,
tractor, msgspec Structs, and backend-specific trading APIs.
metadata:
author: goodboy
version: "1.0"
---
# Piker Clearing Expert
Use this mental model before touching order control or accounting:
```text
OrderClient / UI
-> emsd routes intent and owns order-dialog state
-> live brokerd OR paperboi executes and reports events
-> executing backend owns fill-to-accounting integration
-> emsd translates and broadcasts broker events
-> UI consumes order state and position summaries
```
The EMS is not the trade ledger. It correlates IDs, runs dark
predicates, and relays lifecycle events. Paper and accounting-aware
live backends may normalize fills into transactions, update local
ledger/account state, and emit `BrokerdPosition` summaries. Inspect
each live backend; this integration is not uniform.
## Runtime ownership
- `OrderClient` owns client intent and local sent-order tracking.
- `emsd` owns `Router`, `DarkBook`, active `Status` dialogs,
subscribers, and `oid <-> reqid` correlation.
- `brokerd.<broker>` owns credentialed live order control.
- `paperboi.<broker>` owns simulated execution and paper accounting.
- `TransactionLedger` owns trade-record persistence, not order state.
- `Account` owns positions keyed by backend-system market identity.
- UI `Position.update_from_msg()` is a summary reset, not durable
transaction accounting.
For daemon placement, context streams, cancellation, and actor-local
state, also load `piker-conc-expert`.
## Event lifecycle
1. A client sends `Order` or `Cancel` through `OrderClient`.
2. `process_client_order_cmds()` creates or updates EMS `Status`.
3. EMS routes a `BrokerdOrder` or stores a dark trigger predicate.
4. The executing backend acknowledges with `BrokerdOrderAck`.
5. EMS records the current `oid <-> reqid` relation.
6. Accepted, ack-correlated status/fill events become client updates.
7. The backend integrates fills into accounting when supported.
8. A resulting `BrokerdPosition` is relayed through EMS to clients.
Cancellation may arrive before acknowledgement. Preserve
`Status.cancel_called`; once the ack supplies `reqid`, EMS can issue
the deferred `BrokerdCancel`.
## Identifier namespaces
Never substitute one namespace merely because two values happen to
match in a backend:
| ID | Owner and invariant |
|---|---|
| `oid` | Stable client/EMS order-dialog ID across submit, modify, cancel |
| `reqid` | Backend order-control ID established by an ack; it may change |
| venue order ID | Native API identity; may differ from both IDs above |
| `tid` | Stable clear ID used to deduplicate position events and TOML keys |
| `fqme` | Piker-normalized market address and symcache lookup key |
| `bs_fqme` | FQME without its final broker suffix |
| `bs_mktid` | Backend-system market key and `Account.pps` key |
Normalize every durably persisted `tid` to a stable string. Although
`Transaction.tid` permits integers, TOML mapping keys do not.
Paper transactions commonly set `Transaction.bs_mktid` to the full
FQME. The embedded `Position.mkt.bs_mktid` can still retain the
provider-native market ID. Check the concrete object and table owner
before asserting key equality.
## Accounting invariants
- `Transaction.size` is signed; preserve side semantics during
broker normalization.
- `Transaction` is the normalized in-memory interchange. Live ledger
records may remain backend-native and normalize through
`mod.norm_trade()` when read.
- `Position._events` is keyed by `tid`; `add_clear()` is the
idempotence boundary.
- `Position.ppu` and `Position.cumsize` are derived from transaction
events, not independent mutable truth.
- Normalized accounting transactions need a stable, non-null
`bs_mktid`; `Account.pps` uses it as the position key.
- `Account.update_from_ledger()` resolves market metadata from
`SymbologyCache.mktmaps` or its explicit fallback table.
- UI position messages are projections. Do not feed their synthetic
reset event back into durable broker accounting.
## Persistence contracts
Trade ledgers live under the accounting ledger directory as
`trades_<broker>_<account>.toml`. Top-level keys are transaction IDs;
record schemas are backend-specific unless the paper engine wrote a
normalized transaction dictionary.
`open_trade_ledger()` writes on context exit when its data comparison
detects a change or `rewrite=True`. Its snapshot is shallow, so nested
in-place record mutation can evade dirty detection. Explicit writes can
also fail inside fill handlers. Treat all persistence points as part of
the live execution path.
Accounts persist active positions. `dump_active()` separates open and
net-zero positions. Do not assume `minimized_clears()` safely preserves
size/PPU: current sign-transition and longer net-zero slicing lacks
round-trip coverage and can retain the wrong event subset.
## Market identity and symcache
`SymbologyCache` has three distinct key-addressed mappings:
- `assets`: provider asset ID to `Asset`;
- `pairs`: native `bs_mktid` to backend pair `Struct`;
- `mktmaps`: searchable FQME or explicit alias to normalized
`MktPair`.
Search mapping keys, then resolve values. RapidFuzz treats mapping
values as choices; passing `mktmaps` directly makes it operate on
`MktPair` objects. Preserve in-memory aliases because IB can store both
native IDs and FQMEs for one `MktPair`. Do not persist such aliases
without fixing reload: `from_dict()` currently requires every key to
equal `mkt.fqme`.
## Change workflow
Before editing:
1. Identify the owning layer: client, EMS, executor, accounting, or UI.
2. Write down every ID/key transition across that boundary.
3. Trace both normal and cancel-before-ack event ordering.
4. Determine whether records are native, normalized, or summaries.
5. Check actor-local caches and context-exit writes.
6. Add a deterministic regression at the narrowest broken contract.
7. Run live/backend tests only when their credentials and side effects
are explicitly authorized.
Do not fix an accounting defect by adding state to EMS, or fix an EMS
correlation defect by rewriting durable transaction history.
## Canonical source map
- `piker/clearing/_messages.py`: client and brokerd protocol types.
- `piker/clearing/_client.py`: `OrderClient` and `open_ems()`.
- `piker/clearing/_ems.py`: router, dark book, ID mapping, translation.
- `piker/clearing/_paper_engine.py`: simulated execution/accounting.
- `piker/accounting/_ledger.py`: transactions and ledger persistence.
- `piker/accounting/_pos.py`: positions, accounts, PPU event state.
- `piker/accounting/_mktinfo.py`: `Asset`, `MktPair`, FQME schema.
- `piker/data/_symcache.py`: provider symbology mappings and search.
- `piker/accounting/calc.py`: transaction ordering and PPU/PnL math.
- `piker/brokers/*/broker.py`: backend order-control integration.
- `piker/brokers/*/ledger.py`: backend trade normalization.
See [architecture.md](architecture.md) for complete flows,
[gotchas.md](gotchas.md) for symptom-driven diagnosis, and
[test-map.md](test-map.md) for verification choices.

View File

@ -0,0 +1,210 @@
# Clearing And Accounting Architecture
## Protocol direction
Client-facing messages and broker-facing messages intentionally differ.
```text
client -> EMS
Order, Cancel
EMS -> executor
BrokerdOrder, BrokerdCancel
executor -> EMS
BrokerdOrderAck, BrokerdStatus, BrokerdFill,
BrokerdError, BrokerdPosition
EMS -> subscribed clients
Status, BrokerdPosition
```
`Order` carries the client `oid`, account, market, price, size, action,
and execution mode. `BrokerdOrderAck` establishes the executor's
`reqid`; subsequent broker messages should carry that ID so EMS can
resolve the client dialog.
`Status` is mutable current dialog state, not a history envelope. Exact
client states include `pending`, `open`, `dark_open`, `triggered`,
`fill`, `closed`, `canceled`, and `error`. Audit existing translation
before adding a state; backend `BrokerdStatus` vocabulary is narrower
and current producers are not fully conformant.
## EMS routing
`_emsd_main()` serves each client/market context. The actor-local
`Router` owns books, subscribers, order dialogs, and cached live trades
relays. Paper mode opens per-symbol relay contexts under the same broker
key, a current overwrite/pop hazard for concurrent paper symbols.
`open_brokerd_dialog()` is the session live-versus-paper selection
point:
- live execution talks to `brokerd.<broker>`;
- paper execution talks to `paperboi.<broker>`;
- chart-only and paper sessions should not boot credentialed brokerd.
Do not infer paper execution from each order's `exec_mode`. Session
mode selects the executor; per-order mode primarily distinguishes dark
from immediately routed orders, despite incomplete literal annotations.
`process_client_order_cmds()` handles submit, modify, cancel, dark,
and alert intent. It updates EMS dialog state before routing broker
commands. `translate_and_relay_brokerd_events()` applies acks, statuses,
fills, errors, and positions to dialogs and subscribers.
The `DarkBook` stores local predicates and watches quote ticks. When a
predicate triggers, `clear_dark_triggers()` emits the live broker order
and updates the existing client dialog rather than inventing a second
order identity.
## Ack and cancel ordering
Required actionable ordering is submit, ack, open, zero or more fills,
then terminal status. EMS handles cancel-before-ack, but it does not
buffer arbitrary status-before-ack events; those currently fall through
as unhandled. Backends must ack before actionable statuses.
The important race is:
```text
client Cancel(oid)
-> EMS has no reqid yet
-> mark Status.cancel_called
BrokerdOrderAck(oid, reqid)
-> install mapping
-> send deferred BrokerdCancel(reqid)
```
Do not drop a pre-ack cancel and do not send a broker cancel with an
`oid` unless the backend explicitly uses that value as its `reqid`.
## Paper execution
`PaperBoi` maintains simulated orders, opens the market feed, and fills
orders when L1 ask/bid quotes cross their prices. `trade`/`last` ticks
zip buy and sell iterators, so a lone resting side currently may not
fill from those tick types. `fake_fill()` is the core fill-to-account
path:
1. emit `BrokerdFill`;
2. emit terminal `BrokerdStatus` for the simulated order;
3. construct a signed normalized `Transaction`;
4. `TransactionLedger.update_from_t()`;
5. `Account.update_from_ledger()`;
6. persist the ledger and account;
7. emit `BrokerdPosition` with current size and PPU.
The protocol notifications and persistence occur in one task but are
not a database transaction. If persistence fails after fill/status
publication, restart/reconciliation behavior matters. Do not reorder
the sequence casually.
Partial fills currently reuse `oid` as `tid`. A later partial therefore
overwrites the paper ledger record and is rejected by position-event
dedupe. Fix unique fill IDs before relying on paper partial-fill math.
`open_trade_dialog()` opens symcache, paper ledger, account, and feed
resources under structured concurrency. An exception from any nested
resource, including ledger context exit, cancels the dialog and its feed
stream.
## Live backend variation
There is no universal live-broker accounting implementation:
- IB normalizes execution/commission events, updates ledger/account
state, and emits position updates.
- Kraken has backend trade normalization and position reconstruction,
but its live path writes account state without inserting every new
trade into `TransactionLedger`.
- Binance currently relays venue order/position events and does not
share every local `TransactionLedger`/`Account` path.
Inspect the backend's `broker.py`, `ledger.py`, and `norm_trade()` before
moving generic logic. A paper behavior is not automatically a live
backend contract.
## Ledger normalization
`TransactionLedger.data` is the durable record mapping. For paper it
contains transaction-like dictionaries. For live brokers it can contain
raw provider records.
`iter_txns()` sorts records and chooses normalization:
- paper uses `_paper_engine.norm_trade()`;
- live accounts use `brokermod.norm_trade()`.
Normalizer output should carry datetime, signed size, price, cost,
FQME, backend market ID, and transaction ID. Some normalizers return
`None` for unusable records; `iter_txns()` does not filter it, while
`to_txns()` does. Audit direct iterator consumers.
`write_config()` sorts records for stable persistence. Paper rewrite
logic migrates old symbol fields and qualifies legacy FQMEs through the
symcache. Search can return no matches; callers which select the first
result need an explicit fallback or a descriptive failure policy.
## Position event model
`Position` holds a market and a transaction-event mapping. New clears
are deduplicated by `tid`. Current size and PPU are recomputed from the
event sequence.
`Account.update_from_ledger()`:
1. receives normalized transactions;
2. resolves each market through `mktmaps` or a fallback table;
3. looks up or creates the position by `Transaction.bs_mktid`;
4. adds unseen clears;
5. returns visited positions, including duplicates whose clear was
rejected by `add_clear()`.
`minimized_clears()` intends to discard irrelevant history after
net-zero or sign transitions, but current slicing is not proven safe
and can reconstruct the wrong active size/PPU. Treat it as a known bug
surface until sign-transition round trips are covered.
`Position.update_from_msg()` serves UI summaries by replacing local
event state with a synthetic clear representing reported size and PPU.
That projection is intentionally lossy.
## Symbology contracts
`MktPair.fqme` is a normalized endpoint address assembled from pair,
venue, expiry/contract information, and broker. `MktPair.bs_fqme`
drops the broker suffix. `MktPair.bs_mktid` is the provider-native
market identity.
`SymbologyCache.load()` attempts to obtain provider assets and pair
structs, calls `get_mkt_info()`, and populates conventional mappings.
Assets are optional; missing/empty pair support returns early.
```text
assets[provider_asset_id] -> Asset
pairs[pair.bs_mktid] -> backend Pair Struct
mktmaps[mkt.fqme] -> MktPair
```
Some backends add aliases such as `mktmaps[bs_mktid]`. Search must
return the matched mapping key rather than rebuilding a key from the
value, otherwise aliases silently collapse. Current serialization then
writes aliases which reload rejects because `from_dict()` asserts each
key equals `mkt.fqme`.
## Persistence boundaries
`open_trade_ledger()` loads account TOML and compares a shallow snapshot
in `finally`. Top-level additions/replacements trigger writes; nested
in-place mutation may not. `open_account()` similarly mediates account
state. These contexts run inside actor tasks, so serialization errors
are runtime failures, not merely offline maintenance failures.
When changing schemas:
- preserve backend-native records unless migration is explicit;
- keep old paper field migration deterministic;
- avoid fuzzy qualification when an exact `bs_mktid`/FQME exists;
- test context exit, not only in-memory updates;
- inspect emitted positions after disk round trips.

View File

@ -0,0 +1,146 @@
# Clearing And Ledger Gotchas
## Trade dialog dies immediately after a fill
Likely cause: an explicit ledger/account write in the fill handler, or
a later context-exit write, raised after event publication.
Inspect, in order:
1. the first non-cancellation exception in the actor traceback;
2. `PaperBoi.fake_fill()` or the live backend fill handler;
3. explicit and context-exit `write_config()` calls;
4. FQME/`bs_mktid` qualification and TOML-compatible values;
5. the persisted ledger/account files for a partial update.
Tractor cancellation logs are often the consequence. Root-cause the
inner serialization, lookup, or normalization exception first.
## `MktPair` has no `len()` in RapidFuzz
Cause: a mapping of string keys to `MktPair` values was passed directly
to `rapidfuzz.process.extract()`. Mapping values become fuzzy choices.
Fix: search a list/view of string keys and resolve matched keys back
through the original mapping. Do not add sequence methods to `MktPair`
and do not use `processor=str` when aliases must be preserved.
## Fuzzy qualification raises `IndexError`
Cause: the caller selected the first match from an empty result after
the score cutoff rejected every key.
Decide explicitly whether to retain the input identity, reject the
record with a descriptive exception, or obtain missing market metadata.
Do not lower the cutoff blindly; the wrong market corrupts accounting.
## Cancel is ignored or sent with the wrong ID
Cause: confusion between client `oid` and backend `reqid`, commonly
during cancel-before-ack ordering.
Verify the ack installed both mapping directions and that
`Status.cancel_called` survives until `reqid` exists. Log both IDs and
the actor/backend account at every transition.
## Fills appear but positions do not move
Check these boundaries:
- the backend emitted `BrokerdFill` but never normalized a transaction;
- transaction size lost its buy/sell sign;
- `tid` collided with an existing position event;
- `Transaction.bs_mktid` selected a different `Account.pps` key;
- `mktmaps` lacked the exact transaction identity;
- position publication happened before accounting update;
- the backend intentionally reports venue positions instead of local
ledger-derived positions.
## Duplicate or inflated position size
Do not deduplicate by price/time alone. Confirm stable transaction IDs,
chronological sorting, and `Position.add_clear()` idempotence. Broker
execution IDs and order IDs are not interchangeable.
For partial fills, several `tid`s may correctly share one order
`reqid`. Collapsing them loses clears; replaying one `tid` twice
inflates nothing only if the dedupe boundary is preserved.
Current paper fills use `tid=oid`, so later partials overwrite the
ledger record and are deduplicated out of the position. Treat paper
partial fills as broken until each clear receives a stable unique ID.
## Paper and live state disagree
Paper uses normalized local transactions and can use the full FQME as
its account position key. A live backend may use native IDs, raw ledger
records, or venue-reported positions.
Compare normalized `Transaction`s and emitted `BrokerdPosition`s, not
raw TOML dictionaries across backends.
## Account appears under the wrong name
Separate UI/EMS account aliases from backend account IDs and ledger file
names. Backends may prefix account names for routing while persistence
uses an unprefixed native account key.
Trace account identity through `open_ems()`, broker dialog startup,
ledger opening, and `BrokerdPosition.account` before changing naming.
## A module-global cache is unexpectedly empty
Symcache, clients, contracts, and order tables are actor-local. Data
loaded in `datad` is not automatically available in `brokerd` or
`paperboi`. Also, `_symcache._caches` is currently read but never
populated, so do not assume repeated `open_symcache()` calls hit it.
Load `piker-conc-expert` and audit actor-local globals, async caches,
and dialog startup warming. Never rely on an import side effect from a
sibling actor.
## Teardown emits a cancellation storm
Find the first application exception. Structured cancellation then
closes feed streams, broker streams, and nested contexts. Avoid masking
the originating exception with broad cancellation handling.
For stream ownership and context cancellation semantics, use
`piker-conc-expert`.
## Broker status is logged as unhandled
EMS currently accepts only ack-correlated `open`, `closed`, and
`canceled` broker statuses. Declared `pending` and backend-produced
`fill`/`filled` variants fall through, as do statuses arriving before
ack. Verify producer vocabulary and ack ordering before adding another
consumer branch.
## Another client sees order or position events
Current translation broadcasts order updates by FQME and positions to
all attached clients; `Router.dialogs` is not used as an event privacy
filter. Treat subscriber isolation as incomplete, not guaranteed.
## Debug breadcrumb
Capture one row per event with:
```text
actor, backend, account, fqme, bs_mktid,
oid, reqid, venue_order_id, tid,
message_type, status, action, signed_size, price, timestamp
```
Then compare:
1. client command order;
2. EMS status history;
3. broker event order;
4. normalized transactions;
5. position event IDs and computed size/PPU;
6. ledger/account disk state;
7. emitted position summaries.
This separates protocol loss, identity skew, accounting math, and
persistence failure without guessing from UI state.

View File

@ -0,0 +1,84 @@
# Clearing And Accounting Test Map
## Deterministic first-pass tests
Use the narrowest test that owns the broken contract:
| Area | First target |
|---|---|
| Ledger/account persistence | targeted node in `tests/test_accounting.py` |
| EMS bad-backend handling | `tests/test_ems.py::test_ems_err_on_bad_broker` |
| IB request/response routing | `tests/test_ib_method_proxy.py` |
| IB history normalization | `tests/test_ib_history.py` |
| Service/actor startup | targeted node in `tests/test_services.py` |
Known missing high-value regressions include paper partial-fill IDs,
status-before-ack handling, `minimized_clears()` sign-transition round
trips, nested ledger dirty detection, and symcache alias reload.
For `SymbologyCache.search()` and paper ledger qualification, cover:
- real `Asset` and `MktPair` values;
- a native-ID alias and canonical FQME key;
- the actual `TransactionLedger.write_config()` path;
- persisted TOML fields after the round trip;
- no use of live APIs or user configuration.
## Side-effectful tests
Do not run these without explicit authorization and the repository test
harness guidance:
- paper EMS tests which open live market/symbology feeds;
- live Binance/Kraken feed suites;
- IB account tests using configured accounts;
- tests which write tracked ledger/account fixtures;
- broker dialogs requiring credentials or venue connectivity.
After any accounting test using `tests/_inputs`, inspect its diff. Some
context managers can rewrite fixture ledgers on exit.
## Regression design
Prefer a direct contract regression over a full actor test when the
failure is synchronous and deterministic. Use an actor-level test when
the bug depends on:
- ack/cancel or fill/status interleaving;
- context cancellation or stream ownership;
- actor-local state/cache separation;
- daemon selection or startup;
- publication ordering across EMS and broker streams.
Every regression should document the original failure, triggering
state, violated invariant, arrangement, and proof of the fix.
## Verification layers
1. `git diff --cached --check` for the exact staged boundary.
2. Ruff with the repository's intended rule profile.
3. A deterministic unit/filesystem regression.
4. Adjacent accounting or EMS tests which do not require live state.
5. Explicitly authorized backend/actor integration tests.
Do not interpret a passing ellipsis-body or hard-skipped test as
behavioral coverage. Read the selected node before using it as a gate.
## Manual reconciliation check
For production-only failures, compare one fill end to end:
```text
BrokerdFill
-> normalized Transaction
-> ledger record
-> Position._events[tid]
-> Position.cumsize / ppu
-> account TOML
-> BrokerdPosition
-> UI summary
```
All identities, sign, price, and account values should remain explainable
at each arrow. A mismatch identifies the owning boundary for the next
focused test.

View File

@ -0,0 +1,78 @@
---
name: piker-fsp-expert
description: Piker financial signal processing expertise. Apply when changing or debugging FSP definitions, Cascade execution, derived shared-memory arrays, history/realtime handoff, backfill synchronization, FSP graph composition, online financial algorithms, or FSP chart lifecycle and scaling.
---
# Piker FSP Expertise
Treat financial signal processing as causal, revision-aware processing of
event-time market data, not as generic batch array manipulation.
## Start Here
1. Identify the source `Flume`, timeframe, event clock, and writer actor.
2. State whether the operation is historical bootstrap, backfill revision,
current-sample mutation, or sample-step append.
3. Record source and destination absolute SHM bounds before reasoning from
lengths or `ShmArray.index`.
4. Separate compute, SHM publication, IPC wakeup, and graphics costs before
optimizing.
5. Preserve causality, warm-up behavior, missing-data semantics, and output
schema in every implementation and test.
Read [architecture.md](architecture.md) before modifying runtime code. Read
[signal-processing.md](signal-processing.md) before adding an algorithm. Use
[roadmap-and-research.md](roadmap-and-research.md) for design and dependency
work, and [test-map.md](test-map.md) for verification.
## Core Contracts
- `piker.data` owns provider ingestion, source `Flume`s, history publication,
quote fan-out, OHLC mutation, and `samplerd` step events.
- `piker.fsp` owns operator declarations, historical computation, realtime
state updates, and destination SHM publication.
- `piker.ui._fsp.FspAdmin` currently owns FSP worker selection, destination
allocation, cascade lifetime, and chart attachment.
- One logical writer owns each destination FSP array.
- Output event time and absolute indices derive from the source series, not
task scheduling or quote arrival order.
- A source prepend changes historical provenance even when the current sample
index is unchanged. Recompute or patch the affected destination range.
- One-step destination lag is normal between source sampling and destination
append. A destination lead or unexplained bound shift is not.
- Backfill notifications are source and timeframe specific even though the
current sampler transport broadcasts them globally.
- Only graphics backed by a rebuilt destination should refresh. Never use an
FSP update as an unconditional whole-chart redraw request.
## Editing Workflow
1. Trace the operator from `@fsp` declaration through `cascade()`,
`connect_streams()`, destination SHM, `FspAdmin`, and every consuming
`Viz`.
2. Write an event-order table covering source writes, bound publication,
notifications, cancellation, recomputation, and destination publication.
3. Add deterministic tests for history and realtime phases independently.
4. Add a controlled interleaving test for every race fix.
5. Profile representative visible ranges and feed rates before adding a
dependency or native boundary.
## Companion Skills
- Use `piker-conc-expert` for Tractor contexts, actor topology, cancellation,
service ownership, and cross-actor state.
- Use `timeseries-optimization` for NumPy/Polars lookup and vectorization.
- Use `piker-profiling` for distributed performance measurements.
- Use `pyqtgraph-optimization` for renderer and graphics batching work.
- Use `piker-clearing-expert` when a derived signal reaches EMS or accounting.
## Do Not
- Do not hide a historical mismatch merely to stop a resync log.
- Do not infer a complete synchronization state from array length alone.
- Do not run Python loops over historical numeric arrays without evidence.
- Do not apply regular-grid DSP blindly to irregular tick arrival times.
- Do not introduce an external stream runtime that duplicates Tractor before
measuring an isolated protocol or kernel prototype.
- Do not route strategy signals directly around EMS order-state, risk, or
accounting contracts.

View File

@ -0,0 +1,117 @@
# FSP Runtime Architecture
## Current Topology
```text
provider backend
-> datad feed bus
-> source Flume
-> rt ShmArray (typically 1 second)
-> hist ShmArray (typically 60 seconds)
-> samplerd sample and backfill events
chart actor
-> FspAdmin
-> chart.fsp_N worker actor
-> cascade()
-> connect_streams()
-> destination Flume / ShmArray
-> targeted Viz updates
```
The source path is implemented mainly by `piker.data.feed`,
`piker.data.flows`, `piker.data._sampling`, and `piker.tsp._history`. The FSP
path is implemented by `piker.fsp._api`, `piker.fsp._engine`, and operator
modules such as `_volume` and `_momo`. Worker and graphics lifecycle currently
live in `piker.ui._fsp` and `piker.ui._display`.
## Operator Contract
`@fsp` wraps an async generator. Its first yield is historical output computed
from the source array. Later yields are `(field, value)` realtime mutations.
`connect_streams()` converts the first yield into the destination structured
array, aligns time and absolute bounds, then updates the current destination
row from realtime quote events.
`samplerd` owns row advancement. At a new source sample, the cascade copies or
zeros the previous destination row and aligns destination timestamps to the
source. Quote-time mutations and sample-time appends are distinct operations.
## SHM Synchronization
Treat each array as an absolute half-open range:
```text
[first, last)
```
For a normal source history prepend:
```text
before: src=[10000, 12000) dst=[10000, 12000)
after: src=[ 8000, 12000) dst=[10000, 12000)
```
The current step is unchanged, but destination history is missing 2,000 rows.
The destination must be recomputed or incrementally repaired.
For a normal one-step source lead:
```text
src=[8000, 12001) dst=[8000, 12000)
```
The cascade should append exactly one destination row, not recompute history.
Use `_first.value` and `_last.value` for synchronization. `ShmArray.index` is
the last bound modulo capacity and is not a complete absolute position.
Snapshot bounds once per check; shared first and last counters do not form a
transactional pair.
## Backfill Event Ordering
1. `start_backfill()` receives an older provider frame.
2. `shm_push_in_between()` writes the frame and publishes the earlier first
bound.
3. `notify_backfill()` asks `samplerd` to broadcast the market and timeframe.
4. Relevant cascades treat the event as a history revision. A prepend changes
bounds; an in-place gap repair may not.
5. Each cascade cancels its current compute task and waits for completion.
6. The historical phase recomputes against the newest readable source range.
7. Destination bounds are published and the UI receives an `fsp_update`.
8. Only `Viz` objects backed by that destination token redraw.
Multiple provider frames may arrive while a recomputation runs. The cascade
must reject mixed-bound bootstrap results, replay in-place revisions, recheck
bounds after restart, and converge on the newest source range.
## Actor-Local State
`Fsp._flow_registry`, attached SHM handles, caches, and nurseries are local to
each actor. Never assume worker siblings share warmed registries or Python
objects. Graph dependencies currently rely on startup order, as demonstrated
by `flow_rates` starting only after `dolla_vlm`.
## Cross-Subsystem Boundaries
- Data ingest should normalize identity and event-time semantics before an FSP
consumes a stream.
- UI renderers should consume revision/range information without controlling
computation correctness.
- Clearing may consume a derived signal, but EMS remains responsible for order
intent, status, routing, fills, positions, and accounting.
- FSP output exposed as a feed needs the same fan-out, identity, lifecycle,
and backpressure contracts as provider feeds.
## Architectural Gaps
The current protocol infers history revisions from shared bounds. A future
revision message should carry at least:
```text
(fqme, timeframe, revision, first, last, start_ts, end_ts)
```
Destination publication should identify the source revision used. This allows
coalescing, range repair, stale-result rejection, and deterministic graph
dependency startup without turning every prepend into a global wakeup.

View File

@ -0,0 +1,104 @@
# Roadmap and Research
Issue states change. Re-read each tracker before planning work and preserve
human ownership of issue and checklist state.
## Guiding Roadmap
- [piker #270](https://github.com/pikers/piker/issues/270): typed,
stream-composable FSP graph and DSL direction.
- [piker #107](https://github.com/pikers/piker/issues/107): cross-actor shared
memory tick/ring-buffer direction.
- [piker #216](https://github.com/pikers/piker/issues/216): exposing FSP output
through feed-like interfaces.
- [piker #325](https://github.com/pikers/piker/issues/325): faster streaming
y-range computation and overlap with FSP reductions.
- [piker #536](https://github.com/pikers/piker/issues/536): Arrow/Parquet data
ingest and columnar boundaries.
- [piker #98](https://github.com/pikers/piker/issues/98): older realtime feed
architecture discussion. Treat it as likely superseded; compare every idea
against current `Flume`, feed-bus, history, and sampler code.
- [Tractor #339](https://github.com/goodboy/tractor/issues/339): shared-memory
array and localhost IPC evolution.
- [hotbaud issues](https://github.com/guilledk/hotbaud/issues): experimental
high-throughput local transport and ring-buffer work.
## Near-Term Protocol Research
Prototype an explicit source revision envelope and measure it with two FSPs
and two overlaid feeds. The prototype should prove source-specific wakeups,
coalescing of consecutive prepends, stale-result rejection, and targeted UI
range updates. Keep the protocol independent of the final IPC transport.
Characterize historical repair strategies:
1. Full replay from the new first bound.
2. Prefix-only calculation for pointwise operators.
3. Replay from an operator checkpoint for stateful recurrences.
4. Bounded overlap repair for finite-window operators.
An FSP declaration eventually needs repair metadata describing locality,
state checkpointability, warm-up/overlap, and whether old inputs can alter all
later outputs.
## Candidate Libraries
Candidates are experiments, not dependency recommendations.
### Bounded Near-Term Prototypes
- [Bottleneck](https://github.com/pydata/bottleneck): compare rolling min/max,
rank, and moving-window kernels against NumPy/Numba for chart y-ranges and
finite-window FSPs.
- [River](https://github.com/online-ml/river): study stateful online estimator
APIs, drift detectors, and reference semantics. Do not assume its object
model belongs in the hottest tick path.
- [nanoarrow](https://github.com/apache/arrow-nanoarrow): evaluate lightweight
Arrow C data interfaces at native/process boundaries already motivated by
`pyarrow` and piker #536.
- [DuckDB](https://github.com/duckdb/duckdb): use for historical Parquet query,
replay slicing, and validation, outside realtime tick execution.
### Watchlist
- [hotbaud](https://github.com/guilledk/hotbaud): track API and correctness
maturity for localhost SHM/ring transport.
- [iceoryx2](https://github.com/eclipse-iceoryx/iceoryx2): study zero-copy
pub/sub ownership and lifecycle, but avoid competing with Tractor without a
narrow adapter benchmark.
- [DataFusion Python](https://github.com/apache/datafusion-python): Arrow-native
historical query and expression execution if existing Polars paths cannot
meet a demonstrated need.
- [Feldera](https://github.com/feldera/feldera): study DBSP/incremental-view
semantics for revision-aware graphs; its service/SQL runtime is not a direct
piker integration target.
- [Bytewax](https://github.com/bytewax/bytewax) and
[Arroyo](https://github.com/ArroyoSystems/arroyo): study dataflow semantics,
watermarking, and recovery, not as replacements for the actor tree.
GPU stacks such as JAX and CuPy are poor defaults for low-batch per-tick work
because transfer, compilation, and scheduling can dominate. Reconsider only
for measured large-batch research or many-market matrix workloads.
## Benchmark Design
Report separate distributions for:
- provider decode and normalization;
- source SHM write and bound publication;
- historical kernel and realtime recurrence;
- actor notification and stream fan-out;
- destination publication;
- visible-range formatting and Qt paint.
Use p50, p95, p99, maximum, allocation rate, and resident-memory growth. Test
steady state, burst load, backfill overlap, cancellation, and slow consumers.
Throughput without event-time correctness and bounded teardown is not a win.
## Medium-Term Sequence
First establish revisioned range messages and operator repair semantics. Then
decouple FSP orchestration from chart ownership and expose destination flows
through feed-like subscriptions. Next benchmark localhost transport adapters
behind the same protocol. Only after those contracts stabilize should graph
placement, fusion, worker scaling, and remote partitioning be automated.

View File

@ -0,0 +1,103 @@
# Financial Signal Processing
## Model the Clock First
Market inputs can be quote events, trades, book changes, fixed-time bars, or
revised historical bars. Name both event time and processing time. For
irregular events, either use time-aware recurrences or define the resampling
and interpolation policy explicitly.
Never let backfilled information leak into a value presented as historically
tradable. Distinguish corrected analysis history from the signal values that a
live strategy could have observed.
## Useful Technique Families
### Online Moments and Robust Statistics
Use Welford/Chan recurrences for stable online moments, compensated summation
for long accumulations, and mergeable summaries when partitioning actors.
Rolling median, quantiles, MAD, and winsorized measures are useful under heavy
tails, but exact rolling order statistics need more state than moments.
### Time-Aware Exponential Filters
For irregular event intervals, derive decay from elapsed time:
```text
alpha(dt) = 1 - exp(-dt / tau)
y_t = y_prev + alpha(dt) * (x_t - y_prev)
```
Record initialization, session reset, gap, and backfill behavior. A fixed
sample alpha is valid only after an explicit regularization policy.
### State-Space Models
Kalman and robust state-space filters fit latent price, spread, volatility,
and lead/lag estimates. Prefer square-root or Joseph-form covariance updates
when conditioning is poor. State revisions after backfill require replay from
a checkpoint or a bounded smoother; silently prepending observations without
replaying state is incorrect.
### Point Processes and Microstructure
Trade and quote arrivals are events, not merely sampled amplitudes. Consider
intensity, duration, signed order flow, imbalance, spread, queue change, and
self-excitation models. Validate against provider-specific aggregation and
duplicate/out-of-order behavior before interpreting a statistic.
### Volatility and Covariance
Realized variance, bipower variation, EW covariance, and range estimators can
be updated online. Asynchronous cross-market covariance needs synchronization
or estimators designed for non-synchronous observations; naive row alignment
creates lead/lag and Epps-effect artifacts.
### Spectral and Multiscale Methods
FFT methods assume a regular grid and a window. Declare detrending, tapering,
overlap, normalization, and latency. For tick data, resample deliberately or
use irregular-time methods. Wavelets and multiresolution filters can separate
horizons, but boundary handling and causal delay must be visible to strategy
code.
### Change and Anomaly Detection
CUSUM, Page-Hinkley, sequential likelihood ratios, and robust z-scores are
cheap online tools. Calibrate false alarms under dependence and regime shifts;
do not treat IID thresholds as market guarantees.
## Numerical Implementation Order
1. Establish a scalar reference with explicit state and semantics.
2. Vectorize historical bootstrap with NumPy.
3. Keep realtime updates as O(1) recurrences where possible.
4. Use Numba for measured numeric kernels with stable dtypes and no Python
object traffic.
5. Use Polars or Arrow for columnar historical transforms and interchange,
not automatically for each tick.
6. Partition by independent market/operator state before adding threads or a
new distributed runtime.
## Existing Stack
- NumPy 2.x: canonical dense and structured-array kernels.
- Numba: compiled CPU loops and recurrences that do not vectorize cleanly.
- Polars: parallel/lazy columnar history preparation and validation.
- PyArrow: columnar interchange, storage, and ingest boundaries.
- Tractor/Trio: structured distributed execution and lifecycle.
- PyQtGraph: visible-range rendering, not a compute scheduler.
## Algorithm Acceptance Criteria
Every new FSP should define:
- input fields, clock, ordering, duplicate, gap, and revision policy;
- output dtype, units, identity, and parameterization;
- warm-up length and initialization bias;
- causal latency and strategy-visible publication point;
- historical/realtime equivalence tolerance;
- reset and session-boundary behavior;
- complexity, state size, and representative throughput;
- replay behavior when historical source data changes.

View File

@ -0,0 +1,52 @@
# FSP Test Map
## Operator Tests
- Historical output schema, dtype, units, and source timestamp alignment.
- Realtime update equivalence with the final row of a batch recomputation.
- Warm-up, NaN, zero, gap, duplicate, and out-of-order behavior.
- Session reset and elapsed-time behavior.
- Parameter and source identity isolation.
## Engine Tests
- Equal absolute source/destination bounds.
- Normal one-step destination lag.
- Destination lead and lag greater than one step.
- Source prepend by one, two, and a full provider frame.
- In-place gap repair with unchanged source bounds.
- Equal lengths with shifted absolute bounds.
- Source change during historical recomputation and convergence afterward.
- Cancellation while the quote stream, sample stream, or compute generator is
blocked.
- Foreign FQME and timeframe backfill broadcasts.
## Graph and Actor Tests
- Dependency startup before downstream operator lookup.
- Actor-local flow registry isolation.
- One destination writer and multiple readers.
- Consumer cancellation without orphaned cascade or worker tasks.
- Worker failure propagation and SHM cleanup.
- Feed-like fan-out with a slow or disconnected consumer.
## UI Tests
- An `fsp_update` redraws every field visualization sharing its destination
token and no unrelated market, OHLC, or FSP visualization.
- A prepend outside the visible range does not move or re-range the live view.
- A revised value inside the visible range invalidates only required formatter
and graphics caches.
- Hidden charts defer rendering without stopping compute correctness.
## Performance Tests
- Historical bootstrap across realistic SHM capacities.
- O(1) realtime recurrence cost under bursty quotes.
- Consecutive provider-frame prepends with two or more cascades.
- Multi-market sampler broadcasts and targeted wakeups.
- Visible-range redraw cost at several units-per-pixel levels.
Prefer deterministic local arrays and controlled Trio task interleavings.
Use live providers only as a separate integration layer because credentials,
rate limits, sessions, and changing history make poor regression oracles.

View File

@ -1 +1 @@
{"version": 1, "backend": "neutral"}
{"version": 1, "backend": "legacy"}

View File

@ -0,0 +1,12 @@
{
"permissions": {
"allow": [
"Bash(chmod:*)",
"Bash(/tmp/piker_commits.txt)",
"Bash(python:*)",
"Bash(ls:*)"
],
"deny": [],
"ask": []
}
}

View File

@ -0,0 +1 @@
../../.agents/skills/piker-clearing-expert

View File

@ -0,0 +1,155 @@
---
name: piker-conc-expert
description: >
Distributed-runtime and structured-concurrency
expertise for piker's `tractor` actor-tree. Apply
when working on daemon/service architecture, actor
spawning/discovery, cross-actor RPC (ctx/stream
eps), `to_asyncio` integration, cancellation
semantics, or debugging hangs/wedges/skews in the
actor system.
compatibility: >
Requires a piker checkout and familiarity with Python,
trio, and tractor.
metadata:
author: goodboy
version: "1.0"
---
# Piker Concurrency & Runtime Expertise
The distilled mental model for piker's distributed
runtime: a `trio`-structured actor tree supervised by
`tractor` (pinned to git main) where every long-lived
subsystem is a named daemon-actor talking over
ctx/stream IPC.
## Actor tree & daemon taxonomy
```
pikerd root supervisor + registry
├── datad.<broker> feed bus, shm writers, tsp
│ history, symbol search
├── brokerd.<broker> live order-ctl ONLY; lazily
│ spawned by emsd, credentialed
├── emsd dark-clearing + order routing
│ └── paperboi.<broker> sim-clearing (paper mode)
└── samplerd singleton OHLC clock/increment
```
Key invariants:
- `datad` hosts all `piker.data.validate._eps['datad']`
eps; `brokerd` only the `['brokerd']` (order-ctl)
ones. The `_eps` table in `piker/data/validate.py`
is the authoritative contract; `get_eps(mod, kind)`
introspects a backend's support.
- `brokerd.<broker>` is booted in EXACTLY one place:
`open_brokerd_dialog()` in `piker/clearing/_ems.py`
(with a `portal:` override for the `piker ledger`
ad-hoc actor). Chart-only + paper sessions run with
ZERO brokerd procs. Never add a data-path spawn!
- backends declare per-daemon-kind submods via
`_datad_mods`/`_brokerd_mods` in their
`__init__.py` (fallback: `__enable_modules__`).
## Daemon lifecycle conventions
Every daemon-kind follows the same trio of fns (see
`piker/brokers/_daemon.py` + `piker/data/_daemon.py`
as the canonical pair):
- `_setup_persistent_<kind>()`: a `@tractor.context`
"lifetime fixture" run via
`Services.start_service_task()`; does console-log
setup ONCE for the actor, allocs any actor-global
state (eg. datad's `_FeedsBus`), then
`await ctx.started()` + `trio.sleep_forever()`.
- `<kind>_init()`: builds `enable_modules` + actor
name `f'<kind>.{brokername}'` and copies backend
`_spawn_kwargs` (CRITICAL: `ib` needs
`infect_asyncio=True` in EVERY daemon-kind).
- `spawn_<kind>()` + `maybe_spawn_<kind>()`: thin
wrappers over `Services.actor_n.start_actor()` and
`piker.service.maybe_spawn_daemon()` (registry
find-or-spawn w/ per-service-name locking).
Caps-sec model: `enable_modules` gates RPC entry ONLY
— python imports are unrestricted in-proc. Keep each
daemon's enable set minimal; the (credentialed)
`brokerd` must never RPC-enable `piker.data.*` feed
mods.
## Actor-local state: the #1 split hazard
Module-globals and instance caches are PER-ACTOR.
Anything that "just worked" because two subsystems
shared a process will break when they're split into
sibling actors. Canonical example: `ib`'s
`Client._contracts` was warmed by feed-side
`get_mkt_info()` in-proc; post datad/brokerd-split
the trading actor must warm it itself (eagerly at
`open_trade_dialog()` startup for open pps/orders +
lazily per order request via
`symbols.cache_contract()`).
When moving code across actor boundaries ALWAYS audit:
- module-global registries (`feed._bus`,
`_accounts2clients`, `_client_cache`, ..)
- `@async_lifo_cache`/`maybe_open_context` caches
(NOTE: `async_lifo_cache` keys on POSITIONAL args
only; a cache-hit SKIPS the fn body and thus any
side-effect writes!)
- logging handler placement (see gotchas.md)
## tractor primitives as used here
- `@tractor.context` eps: `await ctx.started(val)`
unblocks the caller w/ `val`; long-lived eps then
`ctx.open_stream()` or `sleep_forever()`.
- discovery: `tractor.find_actor()` via
`piker.service.find_service()`;
`wait_for_actor(name, registry_addr=...)`;
`query_actor(name, regaddr=...)` yields
`(sockaddr, portal)`. Addrs are wrapped
`tractor.discovery._addr.Address` types — use
`wrap_address()` to normalize raw tuples and
`.unwrap()` for comparisons.
- runtime-vars: `_runtime_vars['piker_vars']` is
inherited down the spawn tree; used eg. for
`piker_test_dir` config isolation — read LAZILY at
use-time, never at import time (subactors only get
vars post runtime-boot).
- cancellation semantics (modern tractor): a
`ContextCancelled` whose `.canceller` is your own
actor is ABSORBED (clean exit, nothing raised);
single-exc groups collapse (`collapse_eg`) so eg.
a KBI propagates bare. Exc attrs:
`RemoteActorError.boxed_type` (not `.type`).
## `to_asyncio` (infect-asyncio) integration
For `ib` (and `deribit`) the backend client runs on
an embedded `asyncio` loop via
`tractor.to_asyncio.open_channel_from()` +
`LinkedTaskChannel`.
Rules learned the hard way:
- a shared req/resp channel MUST correlate responses
to requests (see `MethodProxy._run_method()`'s
`mid` protocol in `piker/brokers/ib/api.py`):
caller cancellation (eg. `move_on_after` timeouts)
otherwise orphans a response and silently skews
every later result off-by-one.
- the aio-side relay must catch + ship back ALL
(non-cancel) exceptions as `{'exception': err}`
resps; an escaping error kills the relay task ->
channel -> proxy nursery -> the whole dialog,
bypassing every caller-side guard.
- `TrioTaskExited` ("child asyncio task is still
running?") on teardown is a known wart family;
prefer upstream `tractor` fixes over piker-side
bandaids.
See [gotchas.md](gotchas.md) for the symptom->cause
registry and [debug-recipes.md](debug-recipes.md) for
forensics techniques.

View File

@ -0,0 +1,100 @@
# Debug recipes: actor-system forensics
Field-tested techniques for diagnosing hangs, wedges
and cross-actor state bugs WITHOUT a debugger attached
(or when `py-spy` ain't installed).
## Wedged actor triage (no REPL)
1. find the tree:
`ps -eo pid,etime,args | grep -E 'pytest|tractor._child'`
— long-`etime` `tractor._child` procs w/ a stuck
parent = wedge.
2. kernel state:
`cat /proc/<pid>/wchan` + `status | grep -E
'State|Threads'` — `do_epoll_wait` + sleeping =
idle event loop, NOT cpu-spin.
3. **the money read** — socket queues:
`ss -tnp | grep <pid>`
- `Recv-Q > 0` on the parent-IPC conn = the actor
STOPPED CONSUMING its msg loop (runtime bug),
parent is waiting on it.
- zero external (api/ws) conns = wedged before/
without provider IO; don't blame the network.
- `CLOSE-WAIT` lingerers = unclean peer teardown.
4. cleanup: `pkill -f tractor._child` (NB: in
compound shell cmds `pkill`'s exit code poisons
`&&` chains — run it standalone).
## Hang-proof test gating
- per-suite, never combined (cross-suite session
state interacts w/ the 2nd-boot wedge):
`timeout -k 5 300 python -m pytest tests/<one>.py -q`
- rc 124/143 = hang-kill -> retry ONCE before
investigating.
- isolate a flaky test w/ a 3x loop; ~50% hit-rate
signatures match the known 2nd-boot wedge (see
gotchas.md).
## Regression vs pre-existing attribution
When a failure appears mid-refactor:
1. `git stash -u` (or checkout the file subset) and
re-run the EXACT failing case at baseline.
2. if baseline can't even run, selectively revert
ONLY the suspect layer:
`git diff <files> > /tmp/x.patch;
git checkout <files>` -> test ->
`git apply /tmp/x.patch`.
3. flake-rate compare (3x runs) beats single-shot
conclusions.
## Off-by-one / stale IPC resp detection
Mismatched query->result content in logs (resp
payload obviously for a prior request) = shared
req/resp channel w/o correlation + a cancelled
caller. Grep the ep for `move_on_after`/`fail_after`
around proxied calls. Fix = req-id (`mid`) tagging,
never "just a lock" (cancellation still orphans).
## Logging-chain audits
When records double-print or go bare (see gotchas.md):
```python
import logging
l = logging.getLogger('piker.brokers.ib.broker')
while l:
print(l.name, l.level, l.handlers, l.propagate)
l = l.parent
```
Exactly ONE stderr handler should exist in the chain,
attached by the actor's daemon fixture.
## Live actor-tree smoke (headless)
Boot against an ALT registry port so a user's running
stack is untouched; script in a REAL file (tractor
children re-exec `__main__` from path — stdin scripts
crash w/ `FileNotFoundError: .../<stdin>`):
```python
async with maybe_open_pikerd(
registry_addrs=[('127.0.0.1', 6979)],
):
async with open_feed(['xbtusdt.kraken']) as feed:
assert await check_for_service('datad.kraken')
assert not await check_for_service(
'brokerd.kraken'
)
```
## In-proc fail-fast unit checks
Spawn-path guards that raise BEFORE touching the
runtime can be tested w/ a bare `trio.run()` (eg
`spawn_brokerd('kucoin')` raising the datad-only
error) — no pikerd needed.

View File

@ -0,0 +1,116 @@
# Known gotchas: symptom -> cause -> fix
A registry of distributed-runtime failure modes hit
(and diagnosed) in the field; check here FIRST when a
log/traceback matches.
## "Can not order ..., no qualified contract cached"
- **Symptom**: `RuntimeError` from
`ib.api.Client.submit_limit()` w/ empty
`Client._contracts` in `brokerd.ib`.
- **Cause**: per-actor cache never warmed; feed-side
qualification now lives in `datad.ib`.
- **Fix(ed)**: eager warmup at `open_trade_dialog()`
start + lazy per-order `get_mkt_info()` +
`cache_contract()` (writes BOTH `mkt.bs_fqme` and
`mkt.fqme` keys; different consumers read each!).
## Search returns results for the WRONG pattern
- **Symptom**: fqme search for 'gld' returns nvda
results; next query returns the prior query's set.
- **Cause**: `MethodProxy` channel off-by-one — a
caller cancelled (search `move_on_after` timeout)
after sending its request orphans the response;
every later caller consumes the previous resp.
- **Fix(ed)**: `mid` req-id correlation in
`_run_method()` + relay; stale resps are dropped w/
a "Dropping stale method-resp" warning. If that
warning spams, some caller is being cancelled
mid-call habitually — find + fix its timeout.
## One bad request crashes a whole dialog/actor
- **Symptom**: `TrioTaskExited` storm + nursery
teardown after a single method error (eg ambiguous
contract `AttributeError`).
- **Cause**: exception escaped the aio-side relay
loop (`open_aio_client_method_relay()`) killing
channel + proxy nursery; caller-side `try/except`
CANNOT catch it.
- **Fix(ed)**: relay catches `Exception` -> ships
`{'exception': err, 'mid': ...}` resp; order
handler converts to EMS `BrokerdError` msgs.
## Ambiguous ib contracts -> `NoneType` attr errors
- **Symptom**: `'NoneType' object has no attribute
'primaryExchange'` in `find_contracts()`.
- **Cause**: `qualifyContractsAsync()` returns `None`
entries for ambiguous (eg venue-less stonk fqme
matching multiple listings: 'gld' -> ARCA/USD +
VENTURE/CAD).
- **Fix(ed)**: filter `None`s + raise descriptive
`ValueError` ("use 'gld.arca.ib'").
## Double-printed log records (same task id, 2x)
- **Symptom**: every record from some subsys printed
twice w/ identical task ids.
- **Cause**: stderr handlers attached at TWO levels
of one logger-propagation chain (eg daemon fixture
on `piker.brokers.ib` + an ep calling
`get_console_log(name=__name__)` on the child).
tractor's handler-dedup only checks the SAME
logger, not ancestors.
- **Rule**: console handlers are attached ONCE per
actor in the `_setup_persistent_*()` fixture; eps
needing a different level use `log.setLevel()`
ONLY, never `get_console_log()`.
## Bare/non-colorized log lines
- **Symptom**: records w/ no timestamp/actor prefix.
- **Cause**: NO handler anywhere in the emitting
logger's chain -> stdlib `logging.lastResort`. Post
actor-splits, a daemon fixture may only cover its
own subsys subtree (eg datad's `piker.data.*` but
not the backend's `piker.brokers.<broker>.*`).
- **Fix(ed)**: `_setup_persistent_datad()` enables
BOTH `piker.data.<broker>` and
`piker.brokers.<broker>` subtrees.
## 2nd in-proc runtime boot wedges (~50%)
- **Symptom**: test hangs when one test proc boots a
2nd `pikerd` (eg `test_multi_fill_positions`'s
persistence re-check); a zombie `*.{broker}` child
lingers w/ unread bytes in its parent-IPC Recv-Q.
- **Cause**: pre-existing `tractor`-main runtime
teardown bug (confirmed independent of piker-layer
changes via revert-testing 2026-06).
- **Mitigation**: run suites per-file wrapped in
`timeout -k 5 300 ...`; retry once on rc 124/143.
Do NOT chase as a regression of unrelated changes.
## ib client-id collisions post-split
- **Symptom**: 2nd ib daemon burns the full
conn-timeout retry cycle connecting to gw/tws.
- **Cause**: `datad.ib` + `brokerd.ib` both default
`client_id=6116` w/ linear `+i` retries.
- **Fix(ed)**: role-based offsets in
`load_aio_clients()`: datad +16, ad-hoc (test/cli)
actors +32.
## `async_lifo_cache` skipped side-effects
- **Symptom**: a fn's cache-write side effect
(eg `get_mkt_info()` -> `_contracts`) missing for
a 2nd client/proxy.
- **Cause**: cache keys on POSITIONAL args only; a
hit skips the body entirely.
- **Rule**: never rely on cached-fn side effects;
perform required writes explicitly at the call
site (eg `cache_contract()` after `get_mkt_info`).

View File

@ -0,0 +1 @@
../../.agents/skills/piker-fsp-expert

View File

@ -1 +0,0 @@
../../.agents/skills/piker-profiling

View File

@ -5,7 +5,11 @@ description: >
across distributed actor systems. Apply when
adding profiling, debugging perf regressions, or
optimizing hot paths in piker code.
user-invocable: false
compatibility: >
Requires a piker checkout and Python profiling work.
metadata:
author: goodboy
version: "1.0"
---
# Piker Profiling Subsystem

View File

@ -1 +0,0 @@
../../.agents/skills/piker-slang

View File

@ -5,7 +5,11 @@ description: >
ethos. Apply when communicating with piker devs,
writing commit messages, code review comments, or
any collaborative interaction.
user-invocable: false
compatibility: >
Harness-independent communication guidance.
metadata:
author: goodboy
version: "1.0"
---
# Piker Slang & Communication Style

View File

@ -47,7 +47,7 @@ that's why it's like 1000x faster ya know?"
- Wrap all code symbols: `function()`,
`ClassName`, `field_name`
- File paths: `piker/ui/_remote_ctl.py`
- Commands: `git status`, `piker store ldshm`
- Commands: `git status`, `piker store shm`
**Explain like you're pair programming:**
```

View File

@ -1 +0,0 @@
../../.agents/skills/pyqtgraph-optimization

View File

@ -5,7 +5,12 @@ description: >
for piker's UI. Apply when optimizing graphics
performance, adding new chart annotations, or
working with `QGraphicsItem` subclasses.
user-invocable: false
compatibility: >
Requires a piker checkout with PyQtGraph and Qt source
available for inspection.
metadata:
author: goodboy
version: "1.0"
---
# PyQtGraph Rendering Optimization

View File

@ -1 +0,0 @@
../../.agents/skills/timeseries-optimization

View File

@ -6,7 +6,12 @@ description: >
with OHLCV arrays, timestamp lookups, gap
detection, or any array/dataframe operations in
piker.
user-invocable: false
compatibility: >
Requires a piker checkout with NumPy and optionally
Polars.
metadata:
author: goodboy
version: "1.0"
---
# Timeseries Optimization: NumPy & Polars
@ -61,11 +66,14 @@ ts_array = np.array(timestamps)
# binary search for all timestamps at once
indices = np.searchsorted(time_arr, ts_array)
# bounds check and exact match verification
valid_mask = (
(indices < len(array))
&
(time_arr[indices] == ts_array)
# bounds check before indexing: searchsorted may return
# len(time_arr) for values above the final timestamp
in_bounds = indices < len(time_arr)
valid_mask = np.zeros(indices.shape, dtype=bool)
valid_mask[in_bounds] = (
time_arr[indices[in_bounds]]
==
ts_array[in_bounds]
)
valid_indices = indices[valid_mask]

264
.gitignore vendored
View File

@ -107,11 +107,14 @@ ENV/
.git/
# any commit-msg gen tmp files
.claude/*_commit_*.md
.claude/*_commit*.toml
# ai.skillz/commit-msg
.claude/skills/commit-msg/msgs/
.claude/skills/commit-msg/conf.toml
.claude/git_commit_msg_LATEST.md
.claude/*_commit_*.md
.claude/*_commit*.toml
.opencode/skills/commit-msg/SKILL.md
# nix develop --profile .nixdev
.nixdev*
@ -134,26 +137,6 @@ gitea/
# LLM conversations that should remain private
docs/conversations/
# BEGIN ai.skillz: direct:symlink:claude:gish
/.claude/skills/gish
# END ai.skillz: direct:symlink:claude:gish
# BEGIN ai.skillz: direct:symlink:opencode:gish
/.opencode/skills/gish
# END ai.skillz: direct:symlink:opencode:gish
# BEGIN ai.skillz: runtime:open-wkt
/wkts/
# END ai.skillz: runtime:open-wkt
# BEGIN ai.skillz: direct:symlink:claude:open-wkt
/.claude/skills/open-wkt
# END ai.skillz: direct:symlink:claude:open-wkt
# BEGIN ai.skillz: direct:symlink:opencode:open-wkt
/.opencode/skills/open-wkt
# END ai.skillz: direct:symlink:opencode:open-wkt
# BEGIN ai.skillz: direct:symlink:claude:run-tests
/.claude/skills/run-tests/SKILL.md
# END ai.skillz: direct:symlink:claude:run-tests
@ -162,6 +145,10 @@ docs/conversations/
/.opencode/skills/run-tests/SKILL.md
# END ai.skillz: direct:symlink:opencode:run-tests
# BEGIN ai.skillz: direct:symlink:opencode:command:run-tests
/.opencode/commands/run-tests.md
# END ai.skillz: direct:symlink:opencode:command:run-tests
# BEGIN ai.skillz: direct:symlink:claude:close-wkt
/.claude/skills/close-wkt
# END ai.skillz: direct:symlink:claude:close-wkt
@ -170,23 +157,9 @@ docs/conversations/
/.opencode/skills/close-wkt
# END ai.skillz: direct:symlink:opencode:close-wkt
# BEGIN ai.skillz: runtime:code-review
.ai/code-review/reports/
# END ai.skillz: runtime:code-review
# BEGIN ai.skillz: direct:symlink:claude:code-review
/.claude/skills/code-review
# END ai.skillz: direct:symlink:claude:code-review
# BEGIN ai.skillz: direct:symlink:opencode:code-review
/.opencode/skills/code-review
# END ai.skillz: direct:symlink:opencode:code-review
# BEGIN ai.skillz: runtime:code-review-changes
.ai/state/review/
.claude/review_context.md
.claude/review_regression.md
.claude/review_replies/
# END ai.skillz: runtime:code-review-changes
# BEGIN ai.skillz: direct:symlink:claude:code-review-changes
@ -198,10 +171,8 @@ docs/conversations/
# END ai.skillz: direct:symlink:opencode:code-review-changes
# BEGIN ai.skillz: runtime:commit-msg
.ai/state/commit-msg/
.ai/state/migrations/
.ai/commit-msg/conf.toml
.claude/skills/commit-msg/msgs/
.claude/skills/commit-msg/conf.toml
.claude/git_commit_msg_LATEST.md
# END ai.skillz: runtime:commit-msg
@ -213,14 +184,6 @@ docs/conversations/
/.opencode/skills/commit-msg/SKILL.md
# END ai.skillz: direct:symlink:opencode:commit-msg
# BEGIN ai.skillz: direct:symlink:claude:commit-plan
/.claude/skills/commit-plan
# END ai.skillz: direct:symlink:claude:commit-plan
# BEGIN ai.skillz: direct:symlink:opencode:commit-plan
/.opencode/skills/commit-plan
# END ai.skillz: direct:symlink:opencode:commit-plan
# BEGIN ai.skillz: direct:symlink:claude:dep-supersede-scan
/.claude/skills/dep-supersede-scan
# END ai.skillz: direct:symlink:claude:dep-supersede-scan
@ -229,29 +192,13 @@ docs/conversations/
/.opencode/skills/dep-supersede-scan
# END ai.skillz: direct:symlink:opencode:dep-supersede-scan
# BEGIN ai.skillz: direct:symlink:claude:resolve-conflicts
/.claude/skills/resolve-conflicts
# END ai.skillz: direct:symlink:claude:resolve-conflicts
# BEGIN ai.skillz: direct:symlink:claude:gish
/.claude/skills/gish
# END ai.skillz: direct:symlink:claude:gish
# BEGIN ai.skillz: direct:symlink:opencode:resolve-conflicts
/.opencode/skills/resolve-conflicts
# END ai.skillz: direct:symlink:opencode:resolve-conflicts
# BEGIN ai.skillz: direct:symlink:claude:git-mgmt
/.claude/skills/git-mgmt
# END ai.skillz: direct:symlink:claude:git-mgmt
# BEGIN ai.skillz: direct:symlink:opencode:git-mgmt
/.opencode/skills/git-mgmt
# END ai.skillz: direct:symlink:opencode:git-mgmt
# BEGIN ai.skillz: direct:symlink:claude:harness-perf
/.claude/skills/harness-perf
# END ai.skillz: direct:symlink:claude:harness-perf
# BEGIN ai.skillz: direct:symlink:opencode:harness-perf
/.opencode/skills/harness-perf
# END ai.skillz: direct:symlink:opencode:harness-perf
# BEGIN ai.skillz: direct:symlink:opencode:gish
/.opencode/skills/gish
# END ai.skillz: direct:symlink:opencode:gish
# BEGIN ai.skillz: direct:symlink:claude:inter-skill-review
/.claude/skills/inter-skill-review
@ -261,13 +208,19 @@ docs/conversations/
/.opencode/skills/inter-skill-review
# END ai.skillz: direct:symlink:opencode:inter-skill-review
# BEGIN ai.skillz: direct:symlink:claude:opencode-cleaning
/.claude/skills/opencode-cleaning
# END ai.skillz: direct:symlink:claude:opencode-cleaning
# BEGIN ai.skillz: runtime:open-wkt
.claude/wkts/
claude_wkts
/wkts/
# END ai.skillz: runtime:open-wkt
# BEGIN ai.skillz: direct:symlink:opencode:opencode-cleaning
/.opencode/skills/opencode-cleaning
# END ai.skillz: direct:symlink:opencode:opencode-cleaning
# BEGIN ai.skillz: direct:symlink:claude:open-wkt
/.claude/skills/open-wkt
# END ai.skillz: direct:symlink:claude:open-wkt
# BEGIN ai.skillz: direct:symlink:opencode:open-wkt
/.opencode/skills/open-wkt
# END ai.skillz: direct:symlink:opencode:open-wkt
# BEGIN ai.skillz: direct:symlink:claude:plan-io
/.claude/skills/plan-io
@ -278,8 +231,6 @@ docs/conversations/
# END ai.skillz: direct:symlink:opencode:plan-io
# BEGIN ai.skillz: runtime:pr-msg
.ai/state/pr-msg/
.ai/pr-msg/conf.toml
.claude/skills/pr-msg/msgs/
.claude/skills/pr-msg/pr_msg_LATEST.md
# END ai.skillz: runtime:pr-msg
@ -312,6 +263,14 @@ docs/conversations/
/.opencode/skills/py-codestyle
# END ai.skillz: direct:symlink:opencode:py-codestyle
# BEGIN ai.skillz: direct:symlink:claude:resolve-conflicts
/.claude/skills/resolve-conflicts
# END ai.skillz: direct:symlink:claude:resolve-conflicts
# BEGIN ai.skillz: direct:symlink:opencode:resolve-conflicts
/.opencode/skills/resolve-conflicts
# END ai.skillz: direct:symlink:opencode:resolve-conflicts
# BEGIN ai.skillz: runtime:taken-export
.ai/taken/exports/
# END ai.skillz: runtime:taken-export
@ -332,161 +291,22 @@ docs/conversations/
/.opencode/skills/yt-url-lookup
# END ai.skillz: direct:symlink:opencode:yt-url-lookup
# BEGIN ai.skillz: direct:symlink:opencode:command:run-tests
/.opencode/commands/run-tests.md
# END ai.skillz: direct:symlink:opencode:command:run-tests
# BEGIN ai.skillz: direct:symlink:claude:command:branch-in-new-terminal
/.claude/commands/branch-in-new-terminal.md
# END ai.skillz: direct:symlink:claude:command:branch-in-new-terminal
# BEGIN ai.skillz: direct:symlink:opencode:command:code-review
/.opencode/commands/code-review.md
# END ai.skillz: direct:symlink:opencode:command:code-review
# BEGIN ai.skillz: direct:symlink:opencode:command:code-review-changes
/.opencode/commands/code-review-changes.md
# END ai.skillz: direct:symlink:opencode:command:code-review-changes
# BEGIN ai.skillz: runtime:branch-in-new-terminal
.claude/.current_session
# END ai.skillz: runtime:branch-in-new-terminal
# BEGIN ai.skillz: direct:symlink:opencode:command:commit-msg
/.opencode/commands/commit-msg.md
# END ai.skillz: direct:symlink:opencode:command:commit-msg
# BEGIN ai.skillz: direct:symlink:opencode:command:commit-plan
/.opencode/commands/commit-plan.md
# END ai.skillz: direct:symlink:opencode:command:commit-plan
# BEGIN ai.skillz: direct:symlink:opencode:command:opencode-cleaning
/.opencode/commands/opencode-cleaning.md
# END ai.skillz: direct:symlink:opencode:command:opencode-cleaning
# BEGIN ai.skillz: direct:symlink:opencode:command:pr-msg
/.opencode/commands/pr-msg.md
# END ai.skillz: direct:symlink:opencode:command:pr-msg
# BEGIN ai.skillz: direct:symlink:opencode:command:taken-export
/.opencode/commands/taken-export.md
# END ai.skillz: direct:symlink:opencode:command:taken-export
# BEGIN ai.skillz: direct:symlink:claude:code-nav-refs
/.claude/skills/code-nav-refs
# END ai.skillz: direct:symlink:claude:code-nav-refs
# BEGIN ai.skillz: direct:symlink:opencode:code-nav-refs
/.opencode/skills/code-nav-refs
# END ai.skillz: direct:symlink:opencode:code-nav-refs
# BEGIN ai.skillz: direct:symlink:opencode:command:gish
/.opencode/commands/gish.md
# END ai.skillz: direct:symlink:opencode:command:gish
# BEGIN ai.skillz: direct:symlink:opencode:command:resolve-conflicts
/.opencode/commands/resolve-conflicts.md
# END ai.skillz: direct:symlink:opencode:command:resolve-conflicts
# BEGIN ai.skillz: direct:symlink:opencode:command:git-mgmt
/.opencode/commands/git-mgmt.md
# END ai.skillz: direct:symlink:opencode:command:git-mgmt
# BEGIN ai.skillz: direct:symlink:opencode:command:open-wkt
/.opencode/commands/open-wkt.md
# END ai.skillz: direct:symlink:opencode:command:open-wkt
# BEGIN ai.skillz: direct:symlink:opencode:command:close-wkt
/.opencode/commands/close-wkt.md
# END ai.skillz: direct:symlink:opencode:command:close-wkt
# BEGIN ai.skillz: direct:symlink:opencode:command:dep-supersede-scan
/.opencode/commands/dep-supersede-scan.md
# END ai.skillz: direct:symlink:opencode:command:dep-supersede-scan
# BEGIN ai.skillz: direct:symlink:opencode:command:harness-perf
/.opencode/commands/harness-perf.md
# END ai.skillz: direct:symlink:opencode:command:harness-perf
# BEGIN ai.skillz: direct:symlink:opencode:command:yt-url-lookup
/.opencode/commands/yt-url-lookup.md
# END ai.skillz: direct:symlink:opencode:command:yt-url-lookup
# BEGIN ai.skillz: direct:symlink:agents:gish
/.agents/skills/gish
# END ai.skillz: direct:symlink:agents:gish
# BEGIN ai.skillz: direct:symlink:agents:resolve-conflicts
/.agents/skills/resolve-conflicts
# END ai.skillz: direct:symlink:agents:resolve-conflicts
# BEGIN ai.skillz: direct:symlink:agents:git-mgmt
/.agents/skills/git-mgmt
# END ai.skillz: direct:symlink:agents:git-mgmt
# BEGIN ai.skillz: direct:symlink:agents:open-wkt
/.agents/skills/open-wkt
# END ai.skillz: direct:symlink:agents:open-wkt
# BEGIN ai.skillz: direct:symlink:agents:run-tests
/.agents/skills/run-tests
# END ai.skillz: direct:symlink:agents:run-tests
# BEGIN ai.skillz: direct:symlink:agents:close-wkt
/.agents/skills/close-wkt
# END ai.skillz: direct:symlink:agents:close-wkt
# BEGIN ai.skillz: direct:symlink:agents:code-review
/.agents/skills/code-review
# END ai.skillz: direct:symlink:agents:code-review
# BEGIN ai.skillz: direct:symlink:agents:code-nav-refs
/.agents/skills/code-nav-refs
# END ai.skillz: direct:symlink:agents:code-nav-refs
# BEGIN ai.skillz: direct:symlink:agents:code-review-changes
/.agents/skills/code-review-changes
# END ai.skillz: direct:symlink:agents:code-review-changes
# BEGIN ai.skillz: direct:symlink:agents:commit-msg
/.agents/skills/commit-msg
# END ai.skillz: direct:symlink:agents:commit-msg
# BEGIN ai.skillz: direct:symlink:agents:commit-plan
/.agents/skills/commit-plan
# END ai.skillz: direct:symlink:agents:commit-plan
# BEGIN ai.skillz: direct:symlink:agents:dep-supersede-scan
/.agents/skills/dep-supersede-scan
# END ai.skillz: direct:symlink:agents:dep-supersede-scan
# BEGIN ai.skillz: direct:symlink:agents:harness-perf
/.agents/skills/harness-perf
# END ai.skillz: direct:symlink:agents:harness-perf
# BEGIN ai.skillz: direct:symlink:agents:inter-skill-review
/.agents/skills/inter-skill-review
# END ai.skillz: direct:symlink:agents:inter-skill-review
# BEGIN ai.skillz: direct:symlink:agents:opencode-cleaning
/.agents/skills/opencode-cleaning
# END ai.skillz: direct:symlink:agents:opencode-cleaning
# BEGIN ai.skillz: direct:symlink:agents:plan-io
/.agents/skills/plan-io
# END ai.skillz: direct:symlink:agents:plan-io
# BEGIN ai.skillz: direct:symlink:agents:pr-msg
/.agents/skills/pr-msg
# END ai.skillz: direct:symlink:agents:pr-msg
# BEGIN ai.skillz: direct:symlink:agents:prompt-io
/.agents/skills/prompt-io
# END ai.skillz: direct:symlink:agents:prompt-io
# BEGIN ai.skillz: direct:symlink:agents:py-codestyle
/.agents/skills/py-codestyle
# END ai.skillz: direct:symlink:agents:py-codestyle
# BEGIN ai.skillz: direct:symlink:agents:taken-export
/.agents/skills/taken-export
# END ai.skillz: direct:symlink:agents:taken-export
# BEGIN ai.skillz: direct:symlink:agents:yt-url-lookup
/.agents/skills/yt-url-lookup
# END ai.skillz: direct:symlink:agents:yt-url-lookup
# BEGIN ai.skillz: workflow-state
/.ai/state/
/.ai/commit-msg/conf.toml

View File

@ -17,6 +17,21 @@ track new integration ideas and proposals in
| Tool | Directory | Status |
|------|-----------|--------|
| [Claude Code](https://github.com/anthropics/claude-code) | [`claude-code/`](claude-code/) | active |
| [OpenCode](https://opencode.ai/) | [`opencode/`](opencode/) | active |
## Shared Skills
Provider-neutral skill bundles should use the portable Agent Skills
subset and live under `.agents/skills/` as their single source.
OpenCode discovers that project directory natively. Relative links in
provider directories such as `.claude/skills/` may bridge clients which
do not yet scan `.agents/skills/`; never copy the skill body.
Existing repo skills under `.claude/skills/` predate this convention
and can migrate separately without coupling their history to new skills.
Harness-specific command files should only delegate to a
shared skill. They must not duplicate the skill body.
## Adding a New Integration
@ -32,7 +47,7 @@ ai/
├── README.md # <- you are here
├── claude-code/
│ └── README.md
├── opencode/ # future
├── opencode/
│ └── README.md
└── <your-tool>/
└── README.md
@ -45,6 +60,7 @@ ai/
- Each integration doc should describe **what**
the skill does, **how** to invoke it, and any
**output** artifacts it produces
- Keep docs concise; link to the actual skill
source files (under `.claude/skills/`, etc.)
- Keep docs concise; link to the shared skill source files
rather than duplicating content
- Keep skill bodies harness-neutral; isolate slash-command
syntax and permissions in each harness integration

View File

@ -1,15 +1,18 @@
# Claude Code Integration
[Claude Code](https://github.com/anthropics/claude-code)
skills and workflows for piker development.
integration for piker's shared coding-harness skills.
## Skills
| Skill | Invocable | Description |
|-------|-----------|-------------|
| [`commit-msg`](#commit-msg) | `/commit-msg` | Generate piker-style commit messages |
| `piker-clearing-expert` | auto | Clearing, order-control, and accounting invariants |
| `piker-conc-expert` | auto | Actor-tree and structured-concurrency invariants |
| `piker-profiling` | auto | `Profiler` API patterns for perf work |
| `piker-slang` | auto | Communication style + slang guide |
| `py-codestyle` | auto | Python source conventions |
| `pyqtgraph-optimization` | auto | Batch rendering patterns |
| `timeseries-optimization` | auto | NumPy/Polars perf patterns |
@ -17,8 +20,12 @@ Skills marked **auto** are background knowledge
applied automatically when Claude detects relevance.
Only `commit-msg` is user-invoked via slash command.
Skill source files live under
`.claude/skills/<skill-name>/SKILL.md`.
New portable skill source files live under
`.agents/skills/<skill-name>/SKILL.md`. Relative links under
`.claude/skills/` expose them to Claude Code without copying bodies.
Existing pre-migration skills can remain under `.claude/skills/`.
Claude-specific behavior belongs in command or settings files, not
shared skill bodies.
---
@ -84,8 +91,7 @@ description context. Examples:
**Footer** (always):
```
(this patch was generated in some part by
[`claude-code`][claude-code-gh])
[claude-code-gh]: https://github.com/anthropics/claude-code
`claude-code` using `<model>` (`<provider>`))
```
### Output Files
@ -94,7 +100,8 @@ After generation, the commit message is written to:
```
.claude/
├── <timestamp>_<hash>_commit_msg.md # archived
├── skills/commit-msg/msgs/
│ └── <timestamp>_<hash>_commit_msg.md
└── git_commit_msg_LATEST.md # latest
```
@ -102,18 +109,10 @@ Where `<timestamp>` is ISO-8601 with seconds and
`<hash>` is the first 7 chars of the current
`HEAD` commit.
Use the latest file to feed into `git commit`:
Use the latest file with an editor review before commit:
```bash
git commit -F .claude/git_commit_msg_LATEST.md
```
Or review/edit before committing:
```bash
cat .claude/git_commit_msg_LATEST.md
# edit if needed, then:
git commit -F .claude/git_commit_msg_LATEST.md
git commit --edit --file .claude/git_commit_msg_LATEST.md
```
### Examples
@ -128,56 +127,32 @@ Add `MktPair.fqme` property for symbol resolution
Factor `.claude/skills/` into proper subdirs
Deats,
- `commit_msg/` -> `commit-msg/` w/ enhanced
frontmatter
- all background skills set `user-invocable: false`
- content split into supporting files
- use portable Agent Skills frontmatter
- keep harness commands as thin delegates
- share supporting references across harnesses
(this patch was generated in some part by
[`claude-code`][claude-code-gh])
[claude-code-gh]: https://github.com/anthropics/claude-code
`claude-code` using `<model>` (`<provider>`))
```
### Frontmatter Reference
### Shared Frontmatter
The skill's `SKILL.md` uses these Claude Code
frontmatter fields:
Skills use the Agent Skills fields understood by both
Claude Code and OpenCode:
```yaml
---
name: commit-msg
description: >
Generate piker-style git commit messages...
argument-hint: "[optional-scope-or-description]"
disable-model-invocation: true
allowed-tools:
- Bash(git *)
- Read
- Grep
- Glob
- Write
Generate piker-style git commit messages. Use when...
compatibility: Requires git.
metadata:
author: goodboy
version: "1.0"
---
```
| Field | Purpose |
|-------|---------|
| `argument-hint` | Shows hint in autocomplete |
| `disable-model-invocation` | Only user can trigger via `/commit-msg` |
| `allowed-tools` | Tools the skill can use |
### Dynamic Context
The skill injects live data at invocation time
via `!`backtick`` syntax in the `SKILL.md`:
```markdown
## Current staged changes
!`git diff --staged --stat`
## Recent commit style reference
!`git log --oneline -10`
```
This means the staged diff stats and recent log
are always fresh when the skill runs -- no stale
context.
Live git context is gathered by the skill at execution
time. Claude-only dynamic interpolation and tool-policy
frontmatter are intentionally excluded so another harness
can execute the same workflow.

View File

@ -0,0 +1,143 @@
# Split `brokerd.<broker>` into trading-only `brokerd` + new `datad.<broker>`
## Context
Today a single `brokerd.<broker>` actor hosts BOTH concerns:
- **data feed service tasks**: the `_FeedsBus` + `open_feed_bus()` ep (`piker/data/feed.py:464`), per-symbol `allocate_persistent_feed()` tasks (shm writers via `sample_and_broadcast()`, history backfill via `piker.tsp`), plus backend eps `stream_quotes`, `open_history_client`, `open_symbol_search`, `get_mkt_info` from `piker/brokers/<backend>/feed.py`/`symbols.py`,
- **live order-control tasks**: `open_trade_dialog()` from `piker/brokers/<backend>/broker.py`, driven by `emsd`.
The codebase already anticipates this split: `piker/data/validate.py:70-91` groups backend eps into `'datad'` vs `'brokerd'` kinds, `piker/brokers/ib/__init__.py:62-70` already declares `_brokerd_mods`/`_datad_mods`, and `piker/brokers/_daemon.py:62` carries the literal TODO *"rename the daemon to datad prolly once we split up broker vs. data tasks into separate actors?"*. This work executes that split.
**User-decided constraints:**
- `datad.<broker>` is a **sibling** of `brokerd.<broker>` under `pikerd`, spawned via the existing `Services` + `maybe_spawn_daemon()` machinery (`piker/service/_daemon.py:46`).
- **Hard cutover, staged by layer** — no dual-mode runtime flag; every stage lands fully working.
- Post-split `brokerd` is **trading-only and lazily spawned solely by emsd's** `open_brokerd_dialog` path; UI/CLI/feed code never spawns it. Chart-only and paper sessions run with **zero** brokerd processes.
## Target topology
```
pikerd
├── datad.ib feed bus, shm writers, tsp history, symbol search
├── brokerd.ib open_trade_dialog only (EMS-spawned, lazy)
├── emsd
│ └── paperboi.ib (paper mode; opens its feed via datad)
└── samplerd
```
## Key verified facts (load-bearing)
- `feed.portals` has exactly ONE trading consumer: `piker/clearing/_ems.py:671` (`portal = feed.portals[brokermod]` → handed to `open_brokerd_dialog`). This is the single coupling forcing feed + trading into one actor.
- `assert 'brokerd' in servicename` at `piker/data/feed.py:502` is the only actor-name assert in the tree.
- `piker ledger` (`piker/accounting/cli.py:100-157`) is a hidden consumer: it calls `broker_init()` directly, spawns its own ad-hoc actor, enters `_setup_persistent_brokerd`, then calls `open_brokerd_dialog(brokermod, portal, ...)` **with an explicit portal** — the new signature must keep a `portal:` override param.
- kraken's feed→broker coupling is mild: `kraken/broker.py:77-81` imports `NoBsWs`/`open_autorecon_ws` (really from `piker.data._web_bs`) and `stream_messages` (pure parser) from `feed.py`. In-process imports only — `enable_modules` gates RPC, not imports. No live shared state; each actor opens its own WS.
- ib: default `client_id=6116` (`ib/api.py:1320`) with linear retry `clientId=client_id + i` (`:1403`). Two ib actors connecting concurrently will collide → needs a role-based id offset. Both daemons need `_spawn_kwargs={'infect_asyncio': True}` (copied by both init fns).
- binance has no `symbols.py` (`get_mkt_info`/`open_symbol_search` live in `binance/feed.py`); kucoin/questrade/robinhood are flat single-file with NO `__enable_modules__`; deribit has no `broker.py`.
- `tests/test_services.py:80,185` assert `brokerd` actor names incl. the paper-mode flow asserting `brokerd.kraken` spawns (`:229,:237`) — must invert post-split.
- `_root_modules` (`piker/service/_actor_runtime.py:156`) must gain the new datad daemon mod so `pikerd_portal.run(spawn_datad, ...)` resolves.
- `piker/brokers/core.py:175` `symbol_search` does `portal.run(search_w_brokerd)` where the target fn lives in `piker.brokers.core` → that mod must stay RPC-enabled in datad.
## Module decision
New file **`piker/data/_daemon.py`** hosts all datad machinery (`_setup_persistent_datad`, `datad_init`, `spawn_datad`, `maybe_spawn_datad`, `_datad_service_mods`). Mirrors the `piker.brokers._daemon` / `piker.data._sampling` (samplerd) per-subsystem convention and satisfies the existing TODO at `brokers/_daemon.py:49` ("move this def to the `.data` subpkg"). `piker.brokers._daemon` keeps brokerd-only code, slimmed. Do NOT over-DRY the two ~40-line init fns into a shared factory yet (samplerd precedent accepts the duplication).
---
## Stage 0 — prep: backend module grouping + ep introspection (no topology change)
1. `piker/data/validate.py`: add a `get_eps(mod, kind) -> dict[str, Callable]` helper returning the backend's defined eps for a daemon-kind from `_eps` (missing eps excluded). Used later by both init fns + fail-fast checks.
2. Declare daemon-kind module groups in each split backend's `__init__.py`, keeping `__enable_modules__` as the (deduped) union so behavior is unchanged:
- `kraken`: `_brokerd_mods = ['api', 'broker']`, `_datad_mods = ['api', 'feed', 'symbols']`
- `binance`: `_brokerd_mods = ['api', 'broker']`, `_datad_mods = ['api', 'feed']`
- `deribit`: `_brokerd_mods = []`, `_datad_mods = ['api', 'feed']`
- `ib`: adjust existing groups so each includes `'api'`
- flat backends (kucoin etc.): no attrs — init fns fall back to enabling just `modpath` (existing behavior).
3. Hygiene: `kraken/broker.py:77-81` imports `NoBsWs`/`open_autorecon_ws` from `piker.data._web_bs` directly (keep the `stream_messages` import from `.feed`).
**Gate**: full pytest green, zero behavior change.
## Stage 1 — introduce `datad` daemon machinery (additive)
New `piker/data/_daemon.py`:
- `_datad_service_mods: list[str]` — datad-always-enabled mods (successor to the data side of `_data_mods` from `brokers/_daemon.py:52`): `['piker.brokers.core', 'piker.brokers.data', 'piker.data', 'piker.data.feed', 'piker.data._sampling', 'piker.data._daemon']`.
- `_setup_persistent_datad(ctx, brokername, loglevel)` — `@tractor.context` fixture; logging boilerplate (as `_setup_persistent_brokerd:81-88`), then allocates the actor-global feed bus exactly as the brokerd fixture does today (`brokers/_daemon.py:105-121`): `assert not feed._bus`, open service nursery, `feed.get_feed_bus(brokername, service_nursery)`, `ctx.started()`, `sleep_forever()`.
- `datad_init(brokername, ...)` — mirrors `broker_init()` (`brokers/_daemon.py:132`): actor name `f'datad.{brokername}'`, copies backend `_spawn_kwargs` (**critical for ib infect_asyncio**), builds `enable_modules` from `getattr(brokermod, '_datad_mods', getattr(brokermod, '__enable_modules__', []))`.
- `spawn_datad(brokername, ...)` — mirrors `spawn_brokerd()` (`brokers/_daemon.py:202`): `Services.actor_n.start_actor(dname, enable_modules=_datad_service_mods + backend_mods, ...)` + `Services.start_service_task(dname, portal, _setup_persistent_datad, ...)`.
- `maybe_spawn_datad(brokername, ...)` — wraps `maybe_spawn_daemon(service_name=f'datad.{brokername}', service_task_target=spawn_datad, ...)` exactly like `maybe_spawn_brokerd` (`brokers/_daemon.py:256`).
Supporting edits:
- `piker/service/_actor_runtime.py:156`: add `'piker.data._daemon'` to `_root_modules` (keep `'piker.brokers._daemon'`).
- `piker/service/__init__.py`: re-export `spawn_datad`/`maybe_spawn_datad` next to the brokerd ones (`:56-57`).
- `tests/test_services.py`: new `test_datad_spawn` — `open_test_pikerd` + `maybe_spawn_datad('kraken')` + `ensure_service('datad.kraken')`. (Do NOT route a feed through it yet — `open_feed_bus`'s assert still says brokerd.)
**Gate**: suite green + new spawn test; nothing routes through datad yet.
## Stage 2 — clearing layer: emsd self-spawns brokerd (decouple from `feed.portals`)
Sequenced BEFORE the feed cutover so live trading works at every boundary (otherwise `feed.portals` would hand emsd a datad portal).
`piker/clearing/_ems.py`:
1. `open_brokerd_dialog()` (`:336`) new signature: `(brokermod, exec_mode, fqme=None, portal: tractor.Portal|None = None, loglevel=None)`. Internals: keep trades-ep detection (`:400-417`); acquire the brokerd actor ONLY when a live ep will actually open, via a small inner `@acm _acquire_live_portal()` that yields the passed `portal` if given (the `piker ledger` path) else `async with maybe_spawn_brokerd(brokermod.name, loglevel=loglevel)` — **the single place brokerd boots post-split**. Move the eager `portal.open_context(trades_endpoint, ...)` construction (`:425`) inside that block; the paper short-circuit (`:432`) never touches it.
2. `Router.maybe_open_brokerd_dialog()` (`:581`) — drop the `portal` param; `Router.open_trade_relays()` (`:640`) — delete `portal = feed.portals[brokermod]` (`:671`) and the pass-through (`:685`).
3. `piker/accounting/cli.py:144`: switch to keyword form `open_brokerd_dialog(brokermod, exec_mode=..., portal=portal, loglevel=...)`.
Pre-cutover this is a pure refactor: emsd's `maybe_spawn_brokerd` just *finds* the already-running brokerd via the registry.
**Gate**: `tests/test_ems.py` + services suite green; manual paper order on kraken; `piker ledger` smoke.
## Stage 3 — feed layer hard cutover to datad
1. `piker/data/feed.py`: import `maybe_spawn_datad` (replacing `maybe_spawn_brokerd`, `:56-58`); `open_feed()` `:895` → `maybe_spawn_datad(...)` (rename `brokerd_ctxs` → `datad_ctxs`); `open_feed_bus()` `:502` → `assert 'datad' in servicename`; comment sweep.
2. `piker/brokers/_daemon.py` `_setup_persistent_brokerd()`: slim to trading-only fixture — logging setup, `ctx.started()`, `sleep_forever()`. Drop the bus alloc, `assert not feed._bus`, the service nursery (backend `open_trade_dialog` ctxs own their task trees), the `eg.ExceptionGroup` handler, and the stale `_FeedsBus` TYPE_CHECKING import. (`piker ledger`'s ad-hoc actor enters this same slimmed fixture — exactly what it needs.)
3. Repoint remaining data-flavored spawn sites `maybe_spawn_brokerd` → `maybe_spawn_datad`:
- `piker/ui/_app.py:40,57` (symbol search; optionally rename `install_brokerd_search` → `install_datad_search`, def at `piker/data/feed.py:766`)
- `piker/brokers/core.py:33,175` (`symbol_search`)
- `piker/brokers/cli.py:38,190,320` (`brokercheck`, `record`)
- `piker/ui/cli.py:30,72,121` (legacy kivy monitor/optschain) + best-effort `piker/ui/kivy/option_chain.py:498` `wait_for_actor('brokerd')` → `'datad'`
4. `tests/test_services.py`: `:80,:185` `actor_name = 'datad'`; paper-mode test asserts `datad.kraken` + `paperboi.kraken` + `emsd` AND adds the headline negative check: `assert 'brokerd.kraken' not in services.service_tasks`.
**Gate**: full suite with inverted assertions; manual: chart on binance/kraken shows `datad.<broker>` in `piker services` and NO brokerd; paper order fills; symbol search works; history backfill sane.
## Stage 4 — caps-sec slimming, validation, ib client-id
1. `piker/brokers/_daemon.py`: delete `_data_mods` (`:52`); `spawn_brokerd()` `:236` → `enable_modules=tractor_kwargs.pop('enable_modules')`; `broker_init()` `:184` reads `getattr(brokermod, '_brokerd_mods', getattr(brokermod, '__enable_modules__', []))`. Resulting brokerd enable set has no `piker.data.*` at all.
2. Fail-fast ep validation via Stage-0 `validate.get_eps()`: `broker_init` raises a clear error when `get_eps(mod, 'brokerd')` is empty (e.g. "kucoin is datad-only — use paper mode"); `datad_init` warns analogously.
3. ib client-id mitigation in `load_aio_clients()` (`ib/api.py:~1316`): role-based offset when `client_id` is the 6116 default (e.g. `+16` when `'datad' in tractor.current_actor().name`), optionally configurable via `brokers.toml` keys.
4. Docs/comment sweep: resolve `brokers/_daemon.py:62` TODO, `piker/cli/__init__.py:253` TODO, daemon list in `piker/service/__init__.py:21` docstring.
**Gate**: full suite; audit greps clean: `grep -rn maybe_spawn_brokerd piker/` hits only `clearing/_ems.py`, `service/__init__.py`, `brokers/_daemon.py`; no spawn-path `'brokerd'` literals left in `piker/data`, `piker/ui`, `piker/brokers/cli.py`.
---
## Risk register
| Risk | Sev | Mitigation |
|---|---|---|
| ib: `datad.ib` + `brokerd.ib` both connect to TWS/gw; `client_id=6116` collision burns `connect_timeout` retries | High (live ib only) | Stage 4 role-based id offset; both daemons inherit `infect_asyncio` via `_spawn_kwargs` copy in both init fns |
| `piker ledger` ad-hoc actor path silently broken by signature change | Med | `portal:` override kept on `open_brokerd_dialog` (Stage 2); explicit smoke test |
| datad-only backends (kucoin/deribit) → useless brokerd spawn | Low | already covered: `open_brokerd_dialog` forces `exec_mode='paper'` when no trades ep (`_ems.py:413-417`) BEFORE the lazy spawn; Stage 4 fail-fast |
| brokerd in-process symbology/ledger needs (ib `broker.py` imports `.symbols`; kraken uses `stream_messages`) | None | verified pure in-process imports; `enable_modules` gates RPC only |
| paper-mode test asserts brokerd spawns (`test_services.py:237`) | Low | Stage 3 deliberately inverts it |
| doubled per-broker clients (HTTP/WS, symcache loads) | Low | each actor already opens its own WS today; symcache is disk-read-mostly |
## Verification (per stage gates above, plus end-to-end)
- `pytest tests/test_services.py tests/test_feeds.py tests/test_ems.py` at every stage (kraken/binance public endpoints, no creds needed).
- Manual smoke matrix post-Stage-3: `pikerd -l info` + `piker chart btcusdt.spot.binance` → `piker services` shows `datad.binance`, no brokerd; submit paper order → `paperboi.binance` appears, still no brokerd; symbol search; `piker ledger <broker>.paper`.
- If ib creds/gateway available: live ib chart + small live order to exercise dual-actor client-id path (Stage 4).
## Critical files
- **new** `piker/data/_daemon.py` — all datad machinery
- `piker/brokers/_daemon.py` — slimmed brokerd fixture/init/spawn
- `piker/data/feed.py` — spawn cutover + actor-name assert
- `piker/clearing/_ems.py` — emsd lazy brokerd spawn (`open_brokerd_dialog`, `Router`)
- `piker/service/_actor_runtime.py`, `piker/service/__init__.py` — root mods + re-exports
- `piker/data/validate.py` — `get_eps()` helper
- backend `__init__.py`s (kraken/binance/deribit/ib) — `_datad_mods`/`_brokerd_mods`
- `piker/accounting/cli.py` — keyword-form dialog call
- `tests/test_services.py` — inverted + new assertions

View File

@ -0,0 +1,55 @@
# OpenCode Integration
[OpenCode](https://opencode.ai/) discovers provider-neutral skills
directly from `.agents/skills/` and pre-migration piker skills from
`.claude/skills/`. There is no second copy of each skill to keep in
sync.
## Available Skills
| Skill | Activation | Specialization |
|-------|------------|----------------|
| `commit-msg` | `/commit-msg` or explicit request | Piker commit-message style and artifacts |
| `piker-clearing-expert` | automatic | Clearing, order-control, and accounting invariants |
| `piker-conc-expert` | automatic | `tractor` actor topology, RPC, and cancellation |
| `piker-profiling` | automatic | Cross-actor `Profiler` instrumentation |
| `piker-slang` | automatic | Project communication style |
| `py-codestyle` | automatic | Python source conventions |
| `pyqtgraph-optimization` | automatic | Batched Qt graphics rendering |
| `timeseries-optimization` | automatic | NumPy and Polars timeseries performance |
OpenCode's project command lives at
`.opencode/commands/commit-msg.md`. It delegates to the
shared `commit-msg` skill and only supplies command
arguments; workflow logic remains in the skill.
A user-local skill with the same name may override a repo
skill. Treat `opencode debug skill` as authoritative when
diagnosing which source is active.
## Verify Discovery
From the repository root:
```console
opencode debug skill
opencode debug config
```
`opencode debug skill` should list the piker skills from
the project checkout. Restart OpenCode after changing a
skill or command because configuration-time files are not
hot-reloaded into an active session.
## Portability Rules
- Use only portable Agent Skills frontmatter in shared
`SKILL.md` files.
- Keep harness-specific command syntax under
`.opencode/commands/`.
- Detect the active harness and model at runtime; do not
hardcode Claude Code attribution in shared workflows.
- Keep supporting references beside each shared skill so
relative links resolve in every harness.
- Put new provider-neutral bundles under `.agents/skills/`; use
relative provider links only when a harness requires one.

View File

@ -0,0 +1,364 @@
# Chart disconnect / reattach E2E handoff
Recipient: `e2e_qa_mngr` dialog agent.
Date: 2026-10-19. Draft implementation specification, not executed tests.
Source: human-operated IB qualification in `wkt/ib_downstream_pin_eval`.
Authoring harness/model: OpenCode / `openai/gpt-6.1-sol`.
## Request and intended result
Human request: "so while i'm going through all dis, i feel like you
should be drafting up equivalent e2e tests to handoff to the
e2e_qa_mngr dlog agent. i know they're working on them testing branches
i likely referred to prior"
Extend the existing chart qualification work with executable journeys
equivalent to manual trials 1–7 below. Preserve existing task states.
Keep service-isolation and clean-exit assertions independently observable:
an unaffected surviving chart does not make a segfaulting exit successful.
## Existing foundations inspected
Paths below are local worktree snapshots; uncommitted files can change.
Verify HEAD, diff, imports and fixture contracts before implementation.
- `wkt/human_iface_e2e`, HEAD `aadfd1cf`: headless Qt harness foundation.
- `wkt/live_chart_qtractor`, HEAD `1a2d4893`: structured session lifecycle.
Its current dirty work includes paper-mode startup forwarding, runtime
overrides, and live chart tests; HEAD alone does not contain that work.
[test_live_chart.py](/home/goodboy/repos/piker/wkts/live_chart_qtractor/tests/ui/test_live_chart.py:21)
starts a production Kraken chart, checks real SHM/price changes and
records exact actor/SHM ownership. It is explicitly selected through
`--live-provider=kraken` and uses paper EMS without submitting orders.
[test_qtractor.py](/home/goodboy/repos/piker/wkts/live_chart_qtractor/tests/ui/test_qtractor.py:73)
covers sequential structured window closure and runtime restart.
[UI fixtures](/home/goodboy/repos/piker/wkts/live_chart_qtractor/tests/ui/conftest.py:79)
restore QApplication, widgets, ViewBox registries, config and settings.
[open_test_pikerd](/home/goodboy/repos/piker/wkts/live_chart_qtractor/tests/conftest.py:452)
supplies isolated config propagation and the plugin registry fixture.
- `wkt/replay_provider_e2e`, HEAD `06fee700`: real replay feed/service
tests with correlated command acknowledgements and event sequences.
[test_provider.py](/home/goodboy/repos/piker/wkts/replay_provider_e2e/tests/replay/test_provider.py:57)
exercises actual datad, sampler, search, pause/resume and SHM teardown.
[Existing E2E direction](/home/goodboy/repos/piker/wkts/replay_provider_e2e/plans/opencode/live-backend-chart-e2e-review.md:3)
prioritizes real public-provider chart journeys; replay is parked until
an observed failure needs controlled input. These disconnect races now
justify a narrow deterministic reproducer, not a replay-first rewrite.
The live-chart harness reference still describes MainWindow's old SIGINT
behavior, while its branch has the structured lifecycle commit. Reconcile
that stale guidance with current source before selecting commands.
## Stack being qualified
- Evaluation Piker HEAD `eed58566226d5476b548c6dd3bb6a44fcd6325f9`, with
dirty modern-dependency and logger-compatibility changes.
- Tractor directory `/home/goodboy/repos/tractor/wkts/address_logging_levels`,
branch `wkt/ctx_final_return_disconnect`, HEAD `0553fcf8`.
- Tractor final-Return guard and endpoint-error control are uncommitted.
[Offline regression](/home/goodboy/repos/tractor/wkts/address_logging_levels/tests/test_context_final_return_disconnect.py)
directly drives real TCP and `_rpc._invoke()`, proves unrelated sibling
survival, and verifies a genuine endpoint-raised TransportClosed still
propagates. This is runtime regression coverage, not chart E2E coverage.
- Recorded focused checks: regression + RPC suite 10 passed; context
stream semantics 65 passed. These are earlier runs, not handoff execution.
- Evaluation runtime: Python 3.13.14, Trio 0.32.0, Qt 6.11.1, PyQt 6.11.0.
- Structured Qtractor lifecycle has NOT been transplanted to evaluation.
Baseline CLI SIGINT behavior must remain testable separately from the
lifecycle-remediated branch.
## Required topology and observation contract
Use a suite-owned registrar/pikerd A whose lifetime is independent of B
and C. Each chart must be a separate OS process with its own QApplication
and Tractor root. Do not run concurrent Tractor roots in pytest's process.
The existing in-process single-chart test is useful but does not provide
this persistent external-daemon topology.
Give A a test-unique registrar address, propagate isolated config to ALL
children before imports, and bind charts to loopback TCP port zero. Pass
discovery and listener addresses separately. Verify actual chart listener
ports are nonzero and distinct, and all roots contact A. Do not copy the
human's registrar `6116`, fixed chart port `3333`, config or accounts.
The manual bind failure came from `[network].chart` explicitly pinning
3333; changing it to port zero let simultaneous charts boot.
Proposed helper contracts below are implementation requirements, not
existing APIs. Adapt to the manager's current harness instead of adding
another application adapter:
- A fixture owns A, chart subprocess handles, bounded deadlines, captured
output and an external observation/control channel that outlives charts.
- Capture service identity as actor name + UUID + PID + listener addresses.
Snapshot datad/sampler/EMS/paperboi or brokerd, and provider client IDs or
connection-generation counters when that backend exposes them.
- Identify charts and FSPs by full actor UID plus PID/parent ownership;
names alone are ambiguous with two `chart.fsp_0` actors.
- Readiness means feed/history attached, chart views bound, required FSP
streams active, search handlers ready and EMS entered. Race every wait
against process/session completion and preserve the original exception.
- After detach, perform a fresh service RPC and observe a post-detach
provider quote in the survivor. Process existence or sampler timestamps
alone do not establish service/feed health.
- For deterministic input, match a post-action sequence in feed, SHM and
chart/FSP consumption. For a live provider, await fresh market data with
a realistic deadline; do not require every quote to change price.
- Keep exact created SHM names (including child-created unpublished ones)
and child ownership events. Registry polling alone misses transient
replacement actors and allocations that never reach a ready chart.
## Journey specifications
Suggested names describe NEW tests, not existing runnable node IDs.
### 1. `test_chart_attaches_to_external_pikerd`
Start A, then B. Establish ready views, nonempty finite history, actual
fresh market updates, running FSPs and configured clearing mode. Record
baseline identities. Assert B did not create a competing registrar or
replacement data service. This setup is reused within each journey,
not across unrelated tests that could contaminate one another.
### 2–3. `test_last_chart_sigint_then_repeated_reattach`
With only B attached, send terminal-equivalent SIGINT, await its process
exit, then attach B2 and B3 in sequence to the same A. For each cycle:
1. Inspect shutdown outcome, exit code/signal and FSP descendants.
2. Assert shared service identities stay constant and fresh RPCs succeed.
3. Assert reattached chart receives post-attach market data and FSP output.
4. Prove no replacement service/provider-client generation was created.
Use a real process group created for B and its descendants to reproduce
terminal Ctrl-C delivery. Record PID/PGID membership before signaling;
A and the controller must be in different groups. A root-PID-only signal
is a separate variant and must not be called terminal-equivalent.
Run actual MainWindow close/structured shutdown as a separate variant;
calling `request_shutdown()` alone does not cover CLI SIGINT.
### 4. `test_detach_with_search_results_visible_or_dismissed`
Parameterize visible results versus dismissed pane. Drive production
search UI (QtBot in an owned chart worker), await a real query result,
then leave results visible or perform the normal dismiss interaction.
Detach, prove daemon/survivor health, reattach and search again.
Do not substitute merely opening the input field for visible results.
Both states have been manually exercised; no causal connection between
pane visibility and intermittent REPL entry has been established.
### 5. `test_shared_daemon_survives_alternating_chart_departures`
Parameterize same FQME and two distinct FQMEs under one provider.
Bring B and C fully live, then execute this order:
1. Close B; assert the SAME C process consumes a post-close quote/FSP update.
2. Reopen B; establish its readiness and fresh data.
3. Close C; assert the SAME replacement B keeps receiving updates.
4. Close the final B, then reattach one chart and verify fresh updates.
Same-market coverage must prove one departure did not end the shared
market subscription. Different-market coverage must prove per-feed
cleanup did not cancel a sibling feed. Shared feed SHM may legitimately
remain while A owns its cache: assert lifetime according to its owner,
not unconditional SHM disappearance after every chart exit.
### 6. `test_chart_startup_cancel_preserves_running_consumer`
Keep ready B and A. Start C and cancel at each gated phase:
| Phase | Required evidence before cancellation |
| --- | --- |
| Early attachment | Chart actor registered; initial acquisition in flight |
| FSP child bootstrap | Child spawned; imports/registration not completed |
| History publication | History request entered; completion/publication held |
| FSP establishment | Cascade entered; readiness/stream establishment held |
Use explicit entered/release acknowledgements at real boundaries.
Hold only the designated C operation; do not block the shared datad
event loop or stall B. Ensure cancellation can unwind a held operation.
Where needed use a narrow test seam/controlled provider for that exact
race; timestamps, sleeps and matching an `Entering ...` log are not
deterministic gates. A missed gate is not phase coverage.
After each abort, prove B receives fresh data, A identities persist,
C-owned RPCs/children are gone, and a new C can fully start. Finally
exercise startup cancellation with no other chart attached to cover the
last-consumer case. Keep each phase's outcome distinct in the report.
Add a debug-mode bootstrap-abort variant: terminal SIGINT can reach FSP
children during imports, before their IPC registration. The parent must
unwind spawn supervision without attempting an IPC context to itself for
stdio locking. Capture the original bootstrap interruption separately
from any secondary DebugRequestError. Do not classify this mixed failure
as a cancellation-only group or suppress it with cancellation filtering.
### 7. `test_paper_order_state_survives_chart_reattach`
Use real paper EMS with isolated writable ledger/account state and a
qualified feed. Submit an unfilled paper limit order, confirm its EMS
ID/state, detach its chart, reattach and verify the same order/state,
then cancel it and observe the terminal cancellation acknowledgement.
Check absence of duplicate submissions/replay, stable clearing service
identities and consistent position state. A startup order-mode widget
alone does not cover this. Credentialed IB paper-account qualification
is a separately selected backend tier, with unique gateway client IDs;
public-feed paper EMS does not prove IB brokerd reconnect behavior.
Extend IB qualification to a broker-routed limit and an EMS-held dark
order, including a multi-expiry chart. Assert the EMS OID-to-broker reqid
mapping survives reattach, repeated status delivery is idempotent,
market-specific dialogs remain correctly scoped, cancel-all reaches each
applicable order, and terminal cancellation removes the correct dialog.
Repeated status logs alone do not prove duplicate broker submissions;
count canonical order records and broker working orders independently.
### 7b. `test_cancel_during_second_chart_order_bootstrap`
New human-observed sequence: B is already live; start C, then repeatedly
submit and immediately cancel orders in B while C is booting. C can
finish with a zombie order registered that is already cancelled in B
and EMS. The first observation resisted several retries; the human then
narrowed the trigger to repeated submit/cancel during C bootstrap. Exact
causative ordering and whether stale state is graphical or dialog-local
remain unproven. Do not substitute a normal fully-ready two-chart cancel.
Gate C at the actual order snapshot/replay and live-subscription handoff,
keeping B and EMS responsive. Exercise terminal cancellation before,
during and after bootstrap delivery, with multiple distinct OIDs. Release
an older open snapshot after the terminal event in a controlled variant
to test the suspected ordering explicitly, not to assert it was observed.
Require C's canonical order state and graphics to converge to EMS's
terminal state, and remain absent/cancelled after deferred replay and
subsequent live events. Duplicate open delivery must not resurrect a
terminal order. Retain OID, broker reqid, bootstrap phase and event order
in failure output; cover broker-paper and EMS-held dark orders separately.
## Failure assertions and diagnostics
Every journey reports both isolation and exit outcomes even if one fails.
Continue collecting A/survivor evidence after chart failure, then fail
with aggregated original errors; avoid first-assertion cleanup masking.
- Fail clean-exit checks on SIGSEGV/native abort, stuck descendants,
cancel-ack hard-reap, unexpected debugger entry, or cancellation-only
groups relayed as application failures. The native crash is unresolved.
- Capture chart return code, actor IDs, RPC/cid, parent/child relation,
cancellation source and source task, raw stdout/stderr, phase evidence,
SHM ownership, registry/service snapshots and imported code versions.
- Enable fault-handler/native crash evidence in owned subprocesses.
Retain a core identifier/backtrace if available; do not infer the native
fault's root cause solely from the last Python self-SIGINT traceback.
- A `Final context result delivery failed: peer disconnected` warning is
allowed at the guarded send boundary if the actor and sibling work
demonstrably survive. Warning absence does not mean a missed detach.
- Sampler StartAck/CancelAck delivery failure to a departed chart/FSP is
similarly evaluated by target ownership and subsequent sampler health.
- Do not blanket-suppress TransportClosed or BaseExceptionGroup. Mixed
groups and genuine endpoint errors must retain their failure behavior.
- Preserve both failing-baseline and fixed results. Do not label the
current whole journey green because the final-Return guard works.
## Manual evidence available as of this handoff
Human-operated observations, not automated results:
- Last-consumer detach and repeated reattach reused datad UUID
`3d8b3632-2c90-4ad2-bde5-d75d87bf03af`, PID 742944, cached IB client 6132.
- 12:00:27: final-Return guard caught `open_symbol_search` delivery to
departing chart PID 1729338; sampler PID 743912 remained running.
- 12:02:10: B PID 1520477 exited; C PID 1757182 kept receiving live data.
- 12:02:45: replacement B PID 1771731 started with live data.
- 12:03:33: C PID 1757182 exited; human confirmed B remained unaffected.
Datad PID 742944 and sampler PID 743912 retained their identities.
- Same-MNQ alternating-consumer survival is observed. QQQ also booted
alongside MNQ; an equivalent full different-market closure sequence
has not been explicitly reported.
- FSP cancellation-only groups repeatedly became RemoteActorError;
self-SIGINT occurred during Qt close; MNQ exits at 12:00:27 and
12:03:33 segfaulted. Chart exit is not clean.
- 14:41:26–31: chart `5d1517`, PID 3829973, aborted before order-mode
readiness. Two child bootstrap traces were interrupted during Tractor/
Trio imports (`ctypes.util.find_library('pthread')`). Spawn cleanup then
attempted stdio locking through a local-actor IPC context, producing
`RuntimeError: Can not open an IPC ctx with the local actor!` and two
DebugRequestError leaves. The chart entered post-mortem. This is a new
bootstrap/debug-lock failure, not merely cancellation-only FSP noise.
- 14:42:32–37: MNQ chart `432913`, PID 3844804, aborted after BOTH
cascade startup profiler exits. This covers near-startup shutdown,
not the precise incomplete-FSP-establishment phase.
- 14:43:17–25: TSLA chart `6235d8`, PID 3855381, aborted while FSP
display startup was still active. Dolla-volume cascade had started and
resynced growing history; no second cascade startup is shown before
Ctrl-C. This is useful partial-FSP/backfill-overlap evidence, not a
deterministic phase gate. Human reports daemon backfill continued.
- TSLA SHM token names datad prefix `006f9855-1f2e-4d`, unlike the earlier
`3d8b3632-2c90-4a`. Human subsequently explained manual pikerd restarts
between network roams because IB roaming recovery remains incomplete.
Do not attribute cross-batch identity changes to chart detachment.
- 14:45:18: TSLA reattach `41a82c`, PID 3882846, reached both cascade
startup completions. Human reports cached-like loading and daemon
survival. Human subsequently confirmed B kept receiving fresh updates
during C aborts and recovered TSLA quotes/bars/indicators looked good.
Startup still emitted no-size/no-yrange rendering messages.
- 14:45:40: TSLA detach again emitted cancellation-only FSP groups and
MainWindow self-SIGINT. No native segfault is shown in these afternoon
excerpts.
- Order trial on `ib.algopaper`: broker-routed sell limit OID
`70fbf5f4-4d3b-41a4-a16f-af92d68e6aac`, reqid `5042`, size 1 at 29935,
`resp='open'`, `exec_mode='live'`. This is IB's paper account through
the live broker route, not Piker's paperboi execution mode.
- EMS-held sell dark order OID `9efd5800-7a50-4383-995b-f75d51d17853`,
size 1 at 29912.50, `resp='dark_open'`, no broker reqid. Human reports
both reappeared after chart reattach. Logs retain the same IDs/states.
- Reattached chart `b1326a`, PID 253598, at 15:27:37 displayed December
2026 and March 2027 MNQ. Status delivery repeated, with untracked-dialog
and already-set-brokerd_msg warnings. No duplicate broker submission is
established by those repeated logs; replay/dialog correlation needs
focused assertions rather than blanket acceptance or suppression.
- Human used `cc` cancel-all. Initial attempts said no orders cancelled;
later broker details carried reqid `5042`, status `canceled`, filled 0,
remaining 1, but wrapped in `name='error'` / `oid=None`, then emitted
`Received cancel for unsubmitted order None`. Broker cancellation is
observed, clean correlated UI cancellation is not. No terminal dark
cancellation is shown. Human reports positions consistent, no fills.
- 15:29:39: chart PID 253598 segfaulted after Ctrl-C. Native core/GDB
evidence identifies SIP wrapper access during PyQt atexit cleanup.
[Native diagnostics](/home/goodboy/repos/piker/wkts/federated_project_stores/ai/diagnostics/20261019-chart-253598/README.md:1)
are linked from Taken source `piker`, task ID
`piker-qt6-chart-teardown-sigsegv` in the federated store worktree.
- 2026-10-20 repeat: chart `4af4a7`, PID 1080042, replayed broker OID
`6d78315a-0135-44b3-8162-bd7e816d0190`, reqid `4991`, and dark OID
`21f82034-f555-4cbe-a412-ff1a629e5379`. Cancellation again reported
broker status `canceled` wrapped as error / `oid=None`. Human then
restarted the chart and confirmed neither order returned. This is
reported post-cancel absence, not proof of clean status correlation.
- Subsequent second-chart bootstrap trial exposed the zombie-order race
specified in 7b. Preserve this finding alongside successful ordinary
chart restart/cancellation trials; one does not invalidate the other.
Committed `wkt/fsp_backfill_sync` HEAD `749ca1f0` is an ancestor of the
evaluation baseline (verified with `git merge-base --is-ancestor`). That
does not establish inclusion of any dirty work in that other worktree or
coverage of Tractor process supervision/debug-lock failures. Whether an
older Tractor pin masked cancellation-group behavior rather than fixing
it requires historical reproduction; it is not established by these logs.
## Suggested implementation order and delivery evidence
First extend the installed-process topology and same-market alternating
journey; then last-consumer cycles and search-state variants; then gated
startup aborts and paper-order continuity. Reuse the lifecycle fixtures
and live-first direction already owned by the receiving agent.
The manager should return exact implemented node IDs, fixture ownership,
phase-gate semantics, baseline/fixed test results, independent clean-exit
results and remaining backend-specific gaps. Run headless Qt with the
branch's evidenced flags; live-provider and IB selection remain explicit.
Before choosing the target environment, resolve Piker AND Tractor imports
and account for the evaluation's duplicate pytest `--ll` registration
blocker. No commands for nonexistent proposed nodes are supplied here.
This handoff was written from source inspection and supplied live logs.
No live services, chart processes, orders or E2E suite were started by
the authoring agent for this drafting task. Delivery to the named dialog
has not been confirmed; the file is ready for that dialog to consume.

View File

@ -0,0 +1,59 @@
---
model: claude-fable-5[1m]
service: claude
session: 32d15f9a-b2d3-4c26-bdc9-190219141a25
timestamp: 2026-06-10T17:08:59Z
git_ref: datad_service
diff_cmd: git log -1 -p --follow -- ai/prompt-io/claude/20260610T170859Z_75cefe10_prompt_io.md
scope: code
substantive: true
raw_file: 20260610T170859Z_75cefe10_prompt_io.raw.md
---
## Prompt
Session-initiating instruction (driving all 7 commits in
this series):
> ok i want you to become the distributed runtime and
> concurrency expert for this project - namely acquire
> a deep understanding of tractor and how it's used.
> then i want you to attempt to factor our current
> brokerd service daemon into 2 daemons: a brokerd
> which only hosts live/paper trading endpoint tasks
> [..] a new `datad` subdaemon which in a separate
> subactor serves all the data feed service tasks [..]
> give me a mega detailed plan on how to approach this,
> and a staged approach for the implementation.
Proximate driver for THIS commit: during the approved
plan's stage-0 test gate the agent discovered test
subactors were writing into the user's REAL
`~/.config/piker/accounting/` files (a bogus paper fill
landed in `account.kraken.paper.toml`) because the old
test-dir override in `config.get_app_dir()` was
commented out and could never work in spawned
subactors anyway. Fix applied autonomously under the
approved plan; no explicit per-fix user prompt.
## Response summary
Restore `pytest` config-dir isolation by resolving the
per-test tmp dir lazily (at conf-path access time) from
`tractor` runtime-vars, which propagate down the actor
tree; route all conf-path derivation through
`config.get_conf_dir()` so the override is effective in
every (sub)actor.
## Files changed
- `piker/config.py` — add `_maybe_use_test_dir()`;
hook in `get_conf_dir()`; route `get_conf_path()` +
`load()` mkdir through it
- `piker/accounting/_ledger.py` — derive ledger dir
via `config.get_conf_dir()` (not the global)
- `piker/accounting/_pos.py` — same for account files
## Human edits
None — committed as generated.

View File

@ -0,0 +1,46 @@
---
model: claude-fable-5[1m]
service: claude
timestamp: 2026-06-10T17:08:59Z
git_ref: datad_service
diff_cmd: git log -1 -p --follow -- ai/prompt-io/claude/20260610T170859Z_75cefe10_prompt_io.md
---
NOTE: diff-ref mode entry (code committed in the same
commit as this log); backfilled from the live dev
session transcript per the `/prompt-io` skill rules.
> `git log -1 -p --follow -- piker/config.py`
Generated: `config._maybe_use_test_dir()` — lazily
reads `piker_test_dir` from
`tractor.runtime._state._runtime_vars['piker_vars']`
(pre-loaded by `open_piker_runtime()` from the
`tests.conftest._open_test_pikerd()` overrides) and
calls `_override_config_dir()` when set. Hooked at the
top of `get_conf_dir()`; `get_conf_path()` and
`load()`'s dir-creation rerouted through
`get_conf_dir()`.
> `git log -1 -p --follow -- piker/accounting/_ledger.py`
> `git log -1 -p --follow -- piker/accounting/_pos.py`
Generated: ledger/account dir derivation switched from
the `config._config_dir` module global to
`config.get_conf_dir()` + `mkdir(parents=True,
exist_ok=True)` for nested tmp-dir creation.
Key diagnostic reasoning (verbatim from session):
The old (commented-out) `get_app_dir()` override gated
on `'pytest' in sys.modules` which can NEVER work in
spawned subactors (fresh procs, no pytest import); as
a result test `paperboi`/daemon actors were writing
into the user's REAL `~/.config/piker/accounting/`
files. Evidence: `account.kraken.paper.toml` gained a
single 0.001 xbtusdt clear stamped 2026-06-09T18:47Z
(a test fill), and `trades_kraken_paper.toml` (252B)
contained only that fill. The resolution must be
checked lazily at config-path access time (NOT import
time) since sub-actors only receive runtime-vars once
their `tractor` runtime has fully booted.

View File

@ -0,0 +1,53 @@
---
model: claude-fable-5[1m]
service: claude
session: 32d15f9a-b2d3-4c26-bdc9-190219141a25
timestamp: 2026-06-10T17:10:22Z
git_ref: datad_service
diff_cmd: git log -1 -p --follow -- ai/prompt-io/claude/20260610T171022Z_4485f2b9_prompt_io.md
scope: code
substantive: true
raw_file: 20260610T171022Z_4485f2b9_prompt_io.raw.md
---
## Prompt
Same session-initiating `brokerd`-split instruction (see
`20260610T170859Z_75cefe10_prompt_io.md`). Proximate
driver: the approved plan's per-stage test gates could
not run AT ALL — the branch base was broken vs.
`tractor` git `main` (`AttributeError: 'tuple' object
has no attribute 'unwrap'` at `pikerd` boot, stale
`arbiter_sockaddr`/`.type`/`.size` API refs). The agent
fixed forward autonomously to (re)establish the gate
baseline, continuing the `repair_tests` branch lineage
(verified already merged in ancestry via `git cherry`
during a user-requested branch-overlap survey).
## Response summary
Port the service layer + test suites to current
`tractor` APIs: addr-type normalization in
`open_pikerd()`, `query_actor()`/`wait_for_actor()`
kwarg renames, modern self-cancel absorption semantics
in the cancel-method test harness, exc/position attr
renames, a paper-EMS startup-budget bump and a syntax
fix in `.deribit.api`.
## Files changed
- `piker/service/_actor_runtime.py` — `wrap_address()`
normalize before `.unwrap()` in `open_pikerd()`
- `piker/service/_registry.py` — `check_for_service()`
-> `query_actor(regaddr=)` + 2-tuple yield +
`open_registry(addrs=)`
- `piker/brokers/deribit/api.py` — missing comma in
`tractor.trionics` import tuple
- `tests/test_services.py` — `registry_addr=` kwarg,
raddr unwraps, cancel-semantics harness rewrite,
`fail_after` 9 -> 19s
- `tests/test_ems.py` — `.boxed_type`, `pp.cumsize`
## Human edits
None — committed as generated.

View File

@ -0,0 +1,56 @@
---
model: claude-fable-5[1m]
service: claude
timestamp: 2026-06-10T17:10:22Z
git_ref: datad_service
diff_cmd: git log -1 -p --follow -- ai/prompt-io/claude/20260610T171022Z_4485f2b9_prompt_io.md
---
NOTE: diff-ref mode entry (code committed in the same
commit as this log); backfilled from the live dev
session transcript per the `/prompt-io` skill rules.
> `git log -1 -p --follow -- piker/service/_actor_runtime.py`
Generated: normalize each registry addr via
`tractor.discovery._addr.wrap_address()` before
`.unwrap()`-ing for the `accept_addrs` bind check —
entries may be raw `tuple`s when passed in from (test)
client code. Import-path precedent taken from
`piker/cli/__init__.py:336`.
> `git log -1 -p --follow -- piker/service/_registry.py`
Generated: `check_for_service()` ported to
`tractor.query_actor(name, regaddr=...)` (kwarg was
`arbiter_sockaddr=`), unpacking the new
`(sockaddr, portal)` yield, and passing the
now-required `open_registry(addrs=Registry.addrs)`.
> `git log -1 -p --follow -- tests/test_services.py`
> `git log -1 -p --follow -- tests/test_ems.py`
> `git log -1 -p --follow -- piker/brokers/deribit/api.py`
Key diagnostic reasoning (verbatim from session):
- the "DID NOT RAISE ContextCancelled" failure: in this
test the client actor IS pikerd (in-proc), and
current `tractor` main absorbs a `ContextCancelled`
whose canceller is your own actor — self-requested
cancels now exit cleanly instead of raising; the
'sigint' variant propagates a bare collapsed
`KeyboardInterrupt` rather than a
`BaseExceptionGroup`.
- the hard-coded `trio.fail_after(9)` startup budget is
marginal — full stack boot (pikerd -> emsd ->
brokerd.kraken -> paperboi + live kraken symbology
fetch) occasionally exceeds 9s -> bumped to 19s.
- `RemoteActorError.type` -> `.boxed_type`;
`Position.size` -> `.cumsize` (the paper engine
populates `BrokerdPosition.size` from `pp.cumsize`).
- overlap survey (user-requested): all of the
`repair_tests` branch commits are already in this
stack's ancestry; this commit finishes that branch's
port mission (its f4c4f1e2 fixed `conftest.py`'s
`arbiter_sockaddr` usage; this fixes the remaining
`test_services.py` + `check_for_service()` sites).

View File

@ -0,0 +1,54 @@
---
model: claude-fable-5[1m]
service: claude
session: 32d15f9a-b2d3-4c26-bdc9-190219141a25
timestamp: 2026-06-10T17:11:05Z
git_ref: datad_service
diff_cmd: git log -1 -p --follow -- ai/prompt-io/claude/20260610T171105Z_bc6e18d7_prompt_io.md
scope: code
substantive: true
raw_file: 20260610T171105Z_bc6e18d7_prompt_io.raw.md
---
## Prompt
Same session-initiating `brokerd`-split instruction (see
`20260610T170859Z_75cefe10_prompt_io.md`); this is the
approved plan's "stage 0" prep. Plan-shaping user
decisions captured via in-session Q&A:
- `datad.<broker>` topology: sibling of
`brokerd.<broker>` under `pikerd` (vs. child
subactor).
- migration: hard cutover, staged by layer (no
dual-mode runtime flag).
- post-split `brokerd` scope: trading-only +
EMS-lazy-spawned (charts/CLI never touch it).
## Response summary
Declare per-daemon-kind backend submod groups
(`_datad_mods`/`_brokerd_mods`) in the split-style
backends, keyed to the pre-existing
`piker.data.validate._eps` contract; add a
`validate.get_eps()` introspection helper; ws-import
hygiene in `.kraken.broker`. Zero behavior change
(`__enable_modules__` unions unchanged).
## Files changed
- `piker/data/validate.py` — add `get_eps()`
- `piker/brokers/kraken/__init__.py` — mod groups
- `piker/brokers/binance/__init__.py` — mod groups
(note: no `symbols.py`; search eps live in `.feed`)
- `piker/brokers/deribit/__init__.py` — datad-only
groups (no `broker.py` yet)
- `piker/brokers/ib/__init__.py` — add `'api'` to the
pre-existing `_datad_mods`
- `piker/brokers/kraken/broker.py` — import
`NoBsWs`/`open_autorecon_ws` from
`piker.data._web_bs` directly
## Human edits
None — committed as generated.

View File

@ -0,0 +1,46 @@
---
model: claude-fable-5[1m]
service: claude
timestamp: 2026-06-10T17:11:05Z
git_ref: datad_service
diff_cmd: git log -1 -p --follow -- ai/prompt-io/claude/20260610T171105Z_bc6e18d7_prompt_io.md
---
NOTE: diff-ref mode entry (code committed in the same
commit as this log); backfilled from the live dev
session transcript per the `/prompt-io` skill rules.
> `git log -1 -p --follow -- piker/data/validate.py`
Generated: `get_eps(mod, kind) -> dict[str, Callable]`
returning the daemon-kind's ep funcs defined by a
backend mod, keyed by ep name, missing eps excluded;
sourced from the existing `_eps` grouping table
(`'middleware' | 'datad' | 'brokerd'`).
> `git log -1 -p --follow -- piker/brokers/kraken/__init__.py`
> `git log -1 -p --follow -- piker/brokers/binance/__init__.py`
> `git log -1 -p --follow -- piker/brokers/deribit/__init__.py`
> `git log -1 -p --follow -- piker/brokers/ib/__init__.py`
> `git log -1 -p --follow -- piker/brokers/kraken/broker.py`
Key exploration findings driving the design (from the
planning phase, 3 parallel explore agents + 1 architect
agent):
- the codebase already anticipated this split:
`validate._eps` groups eps into 'datad' vs 'brokerd'
kinds and `ib/__init__.py` already declared
`_brokerd_mods`/`_datad_mods`; `brokers/_daemon.py`
carried the literal TODO "rename the daemon to datad
prolly once we split up broker vs. data tasks into
separate actors?".
- the feared kraken feed<->broker "shared ws state"
hazard is mild: `NoBsWs`/`open_autorecon_ws`
originate in `piker.data._web_bs` (re-exported via
`.kraken.feed`); only `stream_messages` is feed-local
and it's a pure parser — in-process imports only,
each actor opens its own ws conn.
- `enable_modules` gates RPC entry, NOT python imports,
so backend trading mods may keep importing feed-side
helpers in-process post-split.

View File

@ -0,0 +1,47 @@
---
model: claude-fable-5[1m]
service: claude
session: 32d15f9a-b2d3-4c26-bdc9-190219141a25
timestamp: 2026-06-10T17:11:42Z
git_ref: datad_service
diff_cmd: git log -1 -p --follow -- ai/prompt-io/claude/20260610T171142Z_119d2c04_prompt_io.md
scope: code
substantive: true
raw_file: 20260610T171142Z_119d2c04_prompt_io.raw.md
---
## Prompt
Same session-initiating `brokerd`-split instruction (see
`20260610T170859Z_75cefe10_prompt_io.md`); this is the
approved plan's "stage 1": introduce the `datad` daemon
machinery additively (nothing routes through it yet).
User-decided constraint applied: `datad.<broker>` is a
SIBLING of `brokerd.<broker>` under `pikerd`, spawned
via the existing `Services` + `maybe_spawn_daemon()`
machinery.
## Response summary
New `piker/data/_daemon.py` hosting the
`datad.<broker>` feed-only daemon-actor: lifetime
fixture owning the actor-global `_FeedsBus`, init/spawn
fns mirroring `.brokers._daemon` conventions and the
`samplerd` sub-daemon precedent, plus root-mod
registration, `piker.service` re-exports and a spawn
test.
## Files changed
- `piker/data/_daemon.py` — NEW:
`_setup_persistent_datad()`, `datad_init()`,
`spawn_datad()`, `maybe_spawn_datad()`,
`_datad_service_mods`
- `piker/service/_actor_runtime.py` — add
`piker.data._daemon` to `_root_modules`
- `piker/service/__init__.py` — re-export spawn eps
- `tests/test_services.py` — add `test_datad_spawn`
## Human edits
None — committed as generated.

View File

@ -0,0 +1,60 @@
---
model: claude-fable-5[1m]
service: claude
timestamp: 2026-06-10T17:11:42Z
git_ref: datad_service
diff_cmd: git log -1 -p --follow -- ai/prompt-io/claude/20260610T171142Z_119d2c04_prompt_io.md
---
NOTE: diff-ref mode entry (code committed in the same
commit as this log); backfilled from the live dev
session transcript per the `/prompt-io` skill rules.
> `git log -1 -p --follow -- piker/data/_daemon.py`
Generated symbols + key design decisions:
- `_datad_service_mods: list[str]` — datad-always
enabled mods, the data-side successor to the old
`piker.brokers._daemon._data_mods` set; kept minimal
per the caps-sec model.
- `_setup_persistent_datad()` — `@tractor.context`
lifetime fixture: console-log setup then allocates
the actor-global feed bus via
`feed.get_feed_bus(brokername, service_nursery)`
exactly as the old brokerd fixture did, pinned open
with `ctx.started()` + `sleep_forever()`.
- `datad_init()` — actor name `f'datad.{brokername}'`;
copies backend `_spawn_kwargs` (CRITICAL for `ib`'s
`infect_asyncio=True`); builds `enable_modules` from
`getattr(brokermod, '_datad_mods',
getattr(brokermod, '__enable_modules__', []))` as
the flat-backend fallback.
- `spawn_datad()` — `Services.actor_n.start_actor()` +
`Services.start_service_task()` exactly mirroring
`spawn_brokerd()`; dedup-composes enable mods via
`list(dict.fromkeys(...))`.
- `maybe_spawn_datad()` — wraps `maybe_spawn_daemon(
service_name=f'datad.{brokername}', ...)`.
> `git log -1 -p --follow -- piker/service/_actor_runtime.py`
> `git log -1 -p --follow -- piker/service/__init__.py`
> `git log -1 -p --follow -- tests/test_services.py`
Design rationale (verbatim from session):
- `_root_modules` must gain `piker.data._daemon` so
`pikerd_portal.run(spawn_datad, ...)` resolves in
the root.
- the `Services`-based impl style deliberately mirrors
`spawn_brokerd()` so the eventual `tractor.hilevel`
`ServiceMngr` port (see the `service_mng_to_tractor`
branch's d8c21d44 prep, surfaced by the
user-requested branch-overlap survey) lands
symmetrically on both spawn fns.
- mod placement (`piker/data/_daemon.py` vs.
generalizing `piker.brokers._daemon`) follows the
per-subsystem daemon-mod convention
(`.clearing._ems`, `.data._sampling`) and resolves
the existing TODO at `brokers/_daemon.py:49` ("move
this def to the `.data` subpkg").

View File

@ -0,0 +1,47 @@
---
model: claude-fable-5[1m]
service: claude
session: 32d15f9a-b2d3-4c26-bdc9-190219141a25
timestamp: 2026-06-10T17:12:26Z
git_ref: datad_service
diff_cmd: git log -1 -p --follow -- ai/prompt-io/claude/20260610T171226Z_64181219_prompt_io.md
scope: code
substantive: true
raw_file: 20260610T171226Z_64181219_prompt_io.raw.md
---
## Prompt
Same session-initiating `brokerd`-split instruction (see
`20260610T170859Z_75cefe10_prompt_io.md`); this is the
approved plan's "stage 2": decouple the clearing layer
from `feed.portals` BEFORE the feed cutover so live
trading works at every stage boundary. User-decided
constraint applied: post-split `brokerd` is
trading-only and spawned LAZILY only by `emsd`'s
`open_brokerd_dialog()` path.
## Response summary
Kill the single coupling forcing feed + trading eps
into one actor (`Router.open_trade_relays()` pulling
its trades portal from `feed.portals[brokermod]`):
`open_brokerd_dialog()` now (maybe) spawns/finds
`brokerd.<broker>` itself and ONLY when a live
trades-ep will actually open; paper mode never touches
it. Pre-cutover this is a pure refactor (registry
lookup finds the same feed-spawned daemon).
## Files changed
- `piker/clearing/_ems.py` — `open_brokerd_dialog()`
re-sig + inner `acquire_live_portal()`;
`Router.maybe_open_brokerd_dialog()` drops `portal`
param; `open_trade_relays()` drops the
`feed.portals` lookup
- `piker/accounting/cli.py` — keyword-form `portal=`
override kept for the `piker ledger` ad-hoc actor
## Human edits
None — committed as generated.

View File

@ -0,0 +1,43 @@
---
model: claude-fable-5[1m]
service: claude
timestamp: 2026-06-10T17:12:26Z
git_ref: datad_service
diff_cmd: git log -1 -p --follow -- ai/prompt-io/claude/20260610T171226Z_64181219_prompt_io.md
---
NOTE: diff-ref mode entry (code committed in the same
commit as this log); backfilled from the live dev
session transcript per the `/prompt-io` skill rules.
> `git log -1 -p --follow -- piker/clearing/_ems.py`
Generated: new `open_brokerd_dialog()` signature
`(brokermod, exec_mode, fqme=None, portal=None,
loglevel=None)` with an inner `@acm
acquire_live_portal()` that yields the caller-provided
`portal` override (the `piker ledger` path) else
`maybe_spawn_brokerd(brokermod.name)` — designated THE
one place a live, credentialed `brokerd.<broker>` gets
booted post-split. The eager
`portal.open_context(trades_endpoint, ...)`
construction moved inside that block so the paper
short-circuit never acquires a live portal.
> `git log -1 -p --follow -- piker/accounting/cli.py`
Key analysis (verbatim from session):
- `feed.portals` had exactly ONE trading consumer:
`piker/clearing/_ems.py:671` (`portal =
feed.portals[brokermod]`) — the single coupling
forcing feed + trading into one actor.
- `piker ledger` (`accounting/cli.py:100-157`) is a
hidden consumer: it calls `broker_init()` directly,
spawns its own ad-hoc actor and passes the portal
positionally — the new signature must keep an
explicit `portal:` override param for this path.
- stage sequencing: clearing decouple lands BEFORE the
feed cutover, otherwise `feed.portals` would hand
emsd a `datad` portal and live `open_trade_dialog`
RPC would fail.

View File

@ -0,0 +1,48 @@
---
model: claude-fable-5[1m]
service: claude
session: 32d15f9a-b2d3-4c26-bdc9-190219141a25
timestamp: 2026-06-10T17:12:59Z
git_ref: datad_service
diff_cmd: git log -1 -p --follow -- ai/prompt-io/claude/20260610T171259Z_59d5d9a6_prompt_io.md
scope: code
substantive: true
raw_file: 20260610T171259Z_59d5d9a6_prompt_io.raw.md
---
## Prompt
Same session-initiating `brokerd`-split instruction (see
`20260610T170859Z_75cefe10_prompt_io.md`); this is the
approved plan's "stage 3": the feed-layer hard cutover
(user-decided: no dual-mode runtime flag, each stage
lands fully working).
## Response summary
The topology flip: all data-feed consumers route to
`datad.<broker>`; the `brokerd` lifetime fixture slims
to trading-only; UI/CLI data-flavoured spawn sites
repointed; test expectations inverted incl. an explicit
negative assert that paper sessions spawn NO
`brokerd.<broker>`.
## Files changed
- `piker/data/feed.py` — `open_feed()` ->
`maybe_spawn_datad()`; `open_feed_bus()` actor-name
assert -> `'datad'`; `install_brokerd_search` ->
`install_datad_search`; comment sweep
- `piker/brokers/_daemon.py` —
`_setup_persistent_brokerd()` slimmed (feed-bus
alloc moved to the datad fixture)
- `piker/ui/_app.py`, `piker/ui/cli.py`,
`piker/brokers/core.py`, `piker/brokers/cli.py`,
`piker/ui/kivy/option_chain.py` — spawn-site
repoints
- `tests/test_services.py` — datad assertions + the
no-`brokerd.kraken` negative check
## Human edits
None — committed as generated.

View File

@ -0,0 +1,54 @@
---
model: claude-fable-5[1m]
service: claude
timestamp: 2026-06-10T17:12:59Z
git_ref: datad_service
diff_cmd: git log -1 -p --follow -- ai/prompt-io/claude/20260610T171259Z_59d5d9a6_prompt_io.md
---
NOTE: diff-ref mode entry (code committed in the same
commit as this log); backfilled from the live dev
session transcript per the `/prompt-io` skill rules.
> `git log -1 -p --follow -- piker/data/feed.py`
Generated: `open_feed()` spawn cutover with the
`maybe_spawn_datad` import done "relative-direct" from
`._daemon` — NOT via `piker.service` — to dodge a
partial-init cycle: when `piker.data.feed` loads as
part of `piker.service.__init__` executing its own
`piker.data._daemon` import, the service pkg is
mid-init and its `maybe_spawn_datad` binding does not
exist yet. The `open_feed_bus()` local-state sanity
assert flips `'brokerd' in servicename` ->
`'datad' in servicename` (the only actor-name assert
in the tree, verified by grep).
> `git log -1 -p --follow -- piker/brokers/_daemon.py`
Generated: `_setup_persistent_brokerd()` slimmed to
console-log setup + pinned-open ctx; drops the bus
alloc, the `assert not feed._bus`, the service nursery
(backend `open_trade_dialog()` ctxs own their task
trees) and the `eg.ExceptionGroup` handler. The
`piker ledger` ad-hoc actor enters this same slimmed
fixture — exactly what it needs.
> `git log -1 -p --follow -- piker/ui/_app.py`
> `git log -1 -p --follow -- piker/ui/cli.py`
> `git log -1 -p --follow -- piker/brokers/core.py`
> `git log -1 -p --follow -- piker/brokers/cli.py`
> `git log -1 -p --follow -- piker/ui/kivy/option_chain.py`
> `git log -1 -p --follow -- tests/test_services.py`
Verification (from session): per-suite gates green at
this commit (services 5-passed incl. the new negative
assert, feeds 3-passed); a headless live smoke
(`maybe_open_pikerd` + `open_feed(['xbtusdt.kraken'])`
on an alt registry port) confirmed quotes flowing via
`datad.kraken` + `samplerd` with `check_for_service(
'brokerd.kraken') is None`. Known pre-existing flake
documented: `test_multi_fill_positions`' second
in-proc runtime boot wedges ~50% (zombie subactor w/
unread parent-IPC bytes); reproduced with the split
fully reverted so NOT a regression of this work.

View File

@ -0,0 +1,43 @@
---
model: claude-fable-5[1m]
service: claude
session: 32d15f9a-b2d3-4c26-bdc9-190219141a25
timestamp: 2026-06-10T17:13:44Z
git_ref: datad_service
diff_cmd: git log -1 -p --follow -- ai/prompt-io/claude/20260610T171344Z_eee19de0_prompt_io.md
scope: code
substantive: true
raw_file: 20260610T171344Z_eee19de0_prompt_io.raw.md
---
## Prompt
Same session-initiating `brokerd`-split instruction (see
`20260610T170859Z_75cefe10_prompt_io.md`); this is the
approved plan's final "stage 4": caps-sec slimming of
the (live, credentialed) trading actor + the `ib`
dual-daemon `client_id` collision mitigation flagged in
the plan's risk register.
## Response summary
`brokerd` loses ALL `piker.data.*` (feed) RPC mods;
spawn fails fast for datad-only backends with a "use
paper-mode" error; `ib`'s default api-gw `client_id`
gets a per-daemon-kind offset so `datad.ib` +
`brokerd.ib` don't collide on connect.
## Files changed
- `piker/brokers/_daemon.py` — `_data_mods` -> minimal
`_brokerd_service_mods`; `broker_init()` reads
`_brokerd_mods` (fallback `__enable_modules__`);
`spawn_brokerd()` fail-fast via `validate.get_eps()`
- `piker/brokers/ib/api.py` — role-based `client_id`
offset in `load_aio_clients()`
- `piker/cli/__init__.py` — resolved "expose datad"
TODO
## Human edits
None — committed as generated.

View File

@ -0,0 +1,41 @@
---
model: claude-fable-5[1m]
service: claude
timestamp: 2026-06-10T17:13:44Z
git_ref: datad_service
diff_cmd: git log -1 -p --follow -- ai/prompt-io/claude/20260610T171344Z_eee19de0_prompt_io.md
---
NOTE: diff-ref mode entry (code committed in the same
commit as this log); backfilled from the live dev
session transcript per the `/prompt-io` skill rules.
> `git log -1 -p --follow -- piker/brokers/_daemon.py`
Generated: the fail-fast originally landed in
`broker_init()` but was relocated to `spawn_brokerd()`
mid-implementation after realizing `piker ledger`
calls `broker_init()` directly even for paper accounts
on datad-only backends (would have crashed the cli);
the service-spawn path is the correct enforcement
seam. Error text:
Backend 'kucoin' offers NO `brokerd` (live
order-control) eps!? It is likely a datad-only
provider, use paper-mode for clearing instead.
(verified live via a `trio.run()` unit check.)
> `git log -1 -p --follow -- piker/brokers/ib/api.py`
Generated: in `load_aio_clients()`, when `client_id`
is the 6116 default: `datad`-named actors offset +16
(disjoint from `brokerd`'s linear `client_id + i`
retry range), other non-`brokerd` (ad-hoc test/cli)
actors +32. Rationale from the plan's risk register:
post-split BOTH per-broker daemons connect to the same
TWS/gw endpoint; a shared default id collides and
burns up to `connect_timeout * retries` (90s) in
retry cycles.
> `git log -1 -p --follow -- piker/cli/__init__.py`

View File

@ -0,0 +1,66 @@
---
model: claude-fable-5[1m]
service: claude
session: 32d15f9a-b2d3-4c26-bdc9-190219141a25
timestamp: 2026-06-10T17:33:09Z
git_ref: datad_service
diff_cmd: git log -1 -p --follow -- ai/prompt-io/claude/20260610T173309Z_f15f8178_prompt_io.md
scope: docs
substantive: true
raw_file: 20260610T173309Z_f15f8178_prompt_io.raw.md
---
## Prompt
The session-initiating instruction, verbatim (this doc
IS its "mega detailed plan" deliverable):
> ok i want you to become the distributed runtime and
> concurrency expert for this project - namely acquire
> a deep understanding of tractor and how it's used.
> then i want you to atttempt to factor our current
> brokerd service daemon into 2 daemons:
>
> - a brokerd which only hosts live/paper trading
> endoint tasks as defined namely within all
> piker/brokers/<backend>/broker.py mods
> - a new `datad` subdaemon which in a separate
> subactor serves all the data feed service tasks
> namely delivered by endpoints in
> piker/brokers/<backend>/feed.py (or similar for
> less mature backends) and as more rigorously
> defined by the validation machinery in
> piker.data.validate.
>
> give me a mega detailed plan on how to approach
> this, and a staged approach for the implementation.
Plan-shaping user decisions (in-session Q&A): sibling
topology under `pikerd`; hard cutover staged by layer;
trading-only EMS-lazy-spawned `brokerd`. The human
staged the doc copy into `ai/claude-code/plans/` and
requested this commit after the 7 implementation
commits landed.
## Response summary
The staged design doc for the `brokerd` -> (`datad` +
`brokerd`) split: context, target supervision topology,
load-bearing verified facts, per-stage file-level
changes with gates, risk register and an end-to-end
verification matrix. Produced via 3 parallel explore
agents + 1 architect agent + human Q&A before any code
was written; all 7 implementation commits in this
branch reference its stages in their provenance
entries.
## Files changed
- `ai/claude-code/plans/datad_service.md` — the design
plan doc (human-staged copy of the AI-authored plan)
## Human edits
None to the doc content — committed as generated. NB:
the implementation deviated from the plan-as-written in
4 places, see the raw file's deviation log.

View File

@ -0,0 +1,51 @@
---
model: claude-fable-5[1m]
service: claude
timestamp: 2026-06-10T17:33:09Z
git_ref: datad_service
diff_cmd: git log -1 -p --follow -- ai/prompt-io/claude/20260610T173309Z_f15f8178_prompt_io.md
---
NOTE: diff-ref mode entry; the AI output here is the
committed doc itself (a design plan, not code) so no
verbatim copy is duplicated:
> `git log -1 -p --follow -- ai/claude-code/plans/datad_service.md`
## Implementation deviation log
Where the landed 7-commit series differs from the plan
as written (recorded for reviewer transparency; the
plan doc is committed unmodified):
1. Commit ordering: the `pytest` config-dir isolation
fix lands BEFORE the `tractor`-API drift port. The
plan's stage gates assumed a runnable+isolated test
baseline; per-commit gating exposed that without
isolation the paper-EMS test reads the user's real
(polluted) `account.kraken.paper.toml` and reddens.
2. The plan's "stage 0: full pytest green" gate
required first repairing pre-existing branch
breakage vs `tractor` git `main` (boot
`AttributeError`, stale discovery/exc/position
APIs) — that repair became its own commit
("Port service+tests to latest `tractor` APIs")
rather than plan-stage work.
3. Stage-4 fail-fast placement: the plan said
`broker_init()` raises on brokerd-ep-less backends;
implementation moved the raise to `spawn_brokerd()`
since `piker ledger` calls `broker_init()` directly
even for paper accounts on datad-only backends.
4. `brokers/_daemon.py` change grouping: the
import-cleanup hunks (`exceptiongroup`, `_FeedsBus`
type-only import) landed with the caps-sec slim
commit instead of the fixture-slim commit, keeping
each intermediate tree import-clean without
sub-hunk surgery.
Also of record: the plan's "Verification" matrix was
executed as written (per-suite gates each stage, the
headless datad-feed smoke, the kucoin fail-fast unit
check); the known pre-existing ~50% second-runtime-boot
test wedge was characterized and excluded as a
regression via revert-testing.

View File

@ -0,0 +1,60 @@
---
model: claude-fable-5[1m]
service: claude
session: 32d15f9a-b2d3-4c26-bdc9-190219141a25
timestamp: 2026-06-10T21:35:49Z
git_ref: datad_service
diff_cmd: git log -1 -p --follow -- ai/prompt-io/claude/20260610T213549Z_f084e899_prompt_io.md
scope: code
substantive: true
raw_file: 20260610T213549Z_f084e899_prompt_io.raw.md
---
## Prompt
User bug report from live `ib` testing of the
(datad|brokerd)-split branch:
> ok doing some testing and noticing live orders do not
> work otb, pikerd show this on live submission,
> [pdb traceback: `RuntimeError("Can not order
> {symbol}, no live feed?")` at `ib/api.py:1152`
> `submit_limit()` with `self._contracts == {}` in the
> `brokerd.ib` actor + a `TrioTaskExited` teardown
> cascade]
Follow-up user direction fixing the design intent:
> i think the main thing here is that [brokerd] should
> be able to always submit orders without a live feed
> being up and registered in the same subactor right?
## Response summary
Root cause: pre-split the feed code populated the
actor-local `Client._contracts` cache (via
`get_mkt_info()` writing `mkt.bs_fqme` keys) in the
SAME process as order submission; post-split the
trading actor's client is never warmed. Fix: lazily
qualify + cache the contract per order request in
`handle_order_requests()` by running the same
`get_mkt_info(fqme, proxy=...)` ep the feed side uses,
plus per-order error relay (`BrokerdError`) so one bad
submission can't crash the whole trades dialog (the
`TrioTaskExited` storm was teardown cascade from the
original raise).
## Files changed
- `piker/brokers/ib/broker.py` — thread `proxies` into
`handle_order_requests()`; lazy contract qualify on
cache-miss; guard `submit_limit()` w/ `BrokerdError`
relay; uncomment the (anticipatory) `get_mkt_info`
import
- `piker/brokers/ib/api.py` — fix the non-f-string
raise msg + document the new qualification contract
## Human edits
None — committed as generated. Live `ib` order retest
performed by the human post-commit.

View File

@ -0,0 +1,57 @@
---
model: claude-fable-5[1m]
service: claude
timestamp: 2026-06-10T21:35:49Z
git_ref: datad_service
diff_cmd: git log -1 -p --follow -- ai/prompt-io/claude/20260610T213549Z_f084e899_prompt_io.md
---
NOTE: diff-ref mode entry (code committed in the same
commit as this log); recorded from the live debug
session per the `/prompt-io` skill rules.
> `git log -1 -p --follow -- piker/brokers/ib/broker.py`
> `git log -1 -p --follow -- piker/brokers/ib/api.py`
Key diagnostic chain (from session):
- pdb showed `Client._contracts == {}` inside
`brokerd.ib`'s `submit_limit()`; the cache has
exactly TWO write sites: `Client.find_contracts()`
(api.py, keys `f'{sym}.{exch}.ib'`) and
`symbols.get_mkt_info()` (key `mkt.bs_fqme`, eg.
'nvda.nasdaq' — NO `.ib` suffix).
- `BrokerdOrder.symbol` arrives as the bs_fqme form
('nvda.nasdaq') so ONLY the `get_mkt_info()` write
site produces the key `submit_limit()` reads —
ie. pre-split it was the feed's in-proc
`get_mkt_info(sym, proxy=proxy)` call keeping orders
working, NOT `find_contracts()`.
- the existing TODO at `symbols.py:642-644` literally
predicted this: "this is going to be problematic
if/when we split out the datad vs. brokerd actors
since the mktmap lookup table will now be
inaccessible.."
- instance identity verified: `proxy._aio_ns` IS the
same `Client` obj as `_accounts2clients[account]`
(both sourced from the `load_aio_clients()` cache via
`open_client_proxies()`), so a brokerd-side
`get_mkt_info(fqme, proxy=proxies[account])` warms
exactly the dict `submit_limit()` reads. It also
populates `client._cons2mkts` which the
position-audit path (`broker.py` backup-table code)
needs in this actor anyway.
- the `TrioTaskExited` storm in the user's log
(`recv_trade_updates`, `open_aio_client_method_relay`
aio tasks) is teardown cascade: the raise crashed
`handle_order_requests` -> nursery teardown ripped
the trio sides of still-running aio relay tasks.
Hence the added per-order try/except ->
`BrokerdError` relay hardening so a single bad
submission degrades to an EMS error msg instead of
killing the backend's entire order-ctl dialog.
Verification: `tests/test_services.py` (5 passed) +
`tests/test_ems.py` (6 passed) regression-green; live
`ib` submission retest delegated to the human (needs a
running TWS/gw).

View File

@ -0,0 +1,34 @@
# AI Prompt I/O Log — claude
This directory tracks prompt inputs and model
outputs for AI-assisted development using
`claude` (claude-code CLI).
## Policy
Prompt logging follows the
[NLNet generative AI policy][nlnet-ai].
All substantive AI contributions are logged
with:
- Model name and version
- Timestamps
- The prompts that produced the output
- Unedited model output (`.raw.md` files)
[nlnet-ai]: https://nlnet.nl/foundation/policies/generativeAI/
## Usage
Entries are created by the `/prompt-io` skill
or automatically via `/commit-msg` integration.
Each commit carrying AI-generated changes links
to its provenance entry via a `Prompt-IO:`
commit-msg trailer; entries use "diff-ref mode"
(pointers into `git log -p` instead of verbatim
code copies) to avoid duplicating committed
code.
Human contributors remain accountable for all
code decisions. AI-generated content is never
presented as human-authored work.

View File

@ -0,0 +1,36 @@
---
model: gpt-6 (exact variant not exposed)
service: codex
session: fsp-session-recovery-takeover-20260915
timestamp: 2026-09-15T15:52:29Z
git_ref: fadab3d2
scope: code
substantive: true
raw_file: 20260915T155229Z_fadab3d2_prompt_io.raw.md
---
## Prompt
Recover the interrupted OpenCode FSP session and take over its ongoing
work. The recovered child instruction records human authorization for
the minimal EMA omitted-default fix followed by a complete commit plan.
## Response summary
Removed the eager Numba signature that rejected Python's omitted
default arguments. Preserved nopython/nogil compilation and numerical
behavior. Added nine numerical regressions and verified all 38 focused
FSP cases. Rebuilt planning artifacts separately from production code.
## Files changed
- `piker/fsp/_momo.py`: drop the eager signature and unused type imports.
- `tests/test_fsp_momo.py`: omitted/explicit argument and seed coverage.
## Human edits
The human identified the omitted-default limitation in the earlier
session, authorized the minimal lazy-specialization approach, and
requested this takeover after OpenCode failed. Existing source changes
and older OpenCode provenance were retained. The new fix remains
unstaged and awaits human review; no acceptance is inferred.

View File

@ -0,0 +1,50 @@
---
model: gpt-6 (exact variant not exposed)
service: codex
timestamp: 2026-09-15T15:52:29Z
git_ref: fadab3d2
diff_cmd: git diff HEAD~1..HEAD
---
## Prompt
> i'm thinking maybe you can just take over that work and we can
> abandon that oc dialog for now?
Recovered child-session instructions describe the earlier human
authorization to remove EMA's eager Numba signature while retaining
`jit(nopython=True, nogil=True)`, verify defaults, and finish the commit
plan without staging or committing. This is recovered context, not a
verbatim quote of the earlier human authorization.
## Response
> `git diff HEAD~1..HEAD -- piker/fsp/_momo.py`
Removed EMA's eager Numba signature and now-unused Numba type imports.
Retained nopython/nogil compilation, the numerical kernel, historical
comments, and pre-existing staged and unstaged edits.
> `git diff HEAD~1..HEAD -- tests/test_fsp_momo.py`
Added production-dispatcher regressions for omitted positional and
keyword arguments, explicit None and numeric parameters, and the
previously corrected one-sample continuation. Expected outputs are
hand-computed recurrences, rather than the Python implementation.
Before the fix, the first case failed with:
```text
TypeError: No matching definition for argument type(s)
array(float64, 1d, C), omitted(default=None), omitted(default=None)
```
After the fix, the focused run outside the socket-restricted sandbox
reported:
```text
38 passed in 0.66s
```
Ruff passed for the modified momentum module and new regression file.
Combined-worktree results do not attest to isolated commit boundaries.

View File

@ -0,0 +1,64 @@
---
model: gpt-6 (exact variant not exposed)
service: codex
timestamp: 2026-09-23T16:46:56.830897+00:00
git_ref: efa72980
session: 01a0a5b9-f872-7ca3-ab8a-d1cffc14e895
scope: code
substantive: true
raw_file: 20260923T164656Z_efa72980_prompt_io.raw.md
---
## Prompt
Investigate existing branch fixes before repairing IB day-skipping.
Create an isolated worktree on ib_connectivity_harden and add a test
that checks actual persisted data, leaving live gap repair for later.
## Response summary
Preserve IB request boundaries on HMDS no-data responses. Retain the
existing earliest-history classification and bounded reset handling.
Separate requested and published boundaries during backfill so the
first response after an empty interval does not lose its end bar.
Prove supplied bars survive both SHM publication and a fresh parquet
load while a simulated venue closure remains empty.
## Files changed
- `piker/brokers/ib/feed.py`: complete HMDS no-data requests unchanged.
- `piker/tsp/_history.py`: filter overlap against published history.
- `tests/test_ib_history.py`: six empty-response boundary cases.
- `tests/test_history_backfill.py`: two SHM/parquet integration cases.
## Human edits
The human identified the live gap, required prior-branch discovery,
selected ib_connectivity_harden as the base, and requested stronger
end-to-end evidence. They corrected overstated reproduction claims,
insisted on plain explanations, and pointed out that valid venue
closures must remain gaps. Existing data repair was explicitly deferred.
No direct human source edits have been observed; changes await review.
## Review follow-up
The human's tuicr review asked whether None was part of the backfill
protocol and requested plain explanations of the two dates and overlap
filter. Clarified the IB-internal empty-response sentinel and its
translation into NoData/DataUnavailable before reaching the backfiller.
Renamed last_start_dt to request_end_dt and published_start_dt to
oldest_shm_dt. Added a concrete empty-interval example at the filter and
explained that only successful SHM writes move oldest_shm_dt.
These changes clarify naming and documentation without changing the
previously tested request or publication behavior.
## History-filter review follow-up
The human flagged the unused append_until_dt argument and confusing
append/prepend terminology in diff_history(). The helper had one
caller, using only prepend_until_dt. Removed the helper and inlined
its inclusive lower-time cutoff beside the SHM duplicate filter.
The human's nearby TODO comment is resolved by this removal; the
replacement comment describes the retained timestamps directly.
The storage comparison remains >= backfill_until_dt; the SHM
comparison remains < oldest_shm_dt.

View File

@ -0,0 +1,87 @@
---
model: gpt-6 (exact variant not exposed)
service: codex
timestamp: 2026-09-23T16:46:56.830897+00:00
git_ref: efa72980
diff_cmd: git diff efa72980 -- piker/brokers/ib/feed.py piker/tsp/_history.py tests/test_ib_history.py tests/test_history_backfill.py
---
## Prompt
The human requested the IB request-boundary repair in a new worktree
based on ib_connectivity_harden and a test exercising real persistence.
The existing MNQ parquet gap is outside this repair scope.
## Code output
> `git diff efa72980 -- piker/brokers/ib/feed.py`
> `git diff efa72980 -- piker/tsp/_history.py`
> `git diff efa72980 -- tests/test_ib_history.py`
> `git diff efa72980 -- tests/test_history_backfill.py`
get_bars() now completes HMDS no-data responses without changing the
requested date. open_history_client() retains its NoData and earliest
history classification. start_backfill() tracks the published boundary
separately from the request cursor after empty intervals.
## Unedited progress output
The new test failed on the original code as intended: the
temporary parquet contained only 4 bars instead of 2,004.
The day-skip caused 2,000 available bars to be omitted.
The test also includes a simulated venue closure that must remain
a gap. It checks that we fetch available bars without inventing
data for an empty interval.
The IB fix now preserves all 2,004 bars in parquet. The SHM
assertion exposed a second bug: after stepping over an empty
interval, the backfiller drops the last bar of the next response
as though it were already published.
I’ll separate the request date from the published-data boundary
so the same test can verify that both memory and disk retain
every available bar.
## Validation
Using the existing fsp_backfill_sync/py313 interpreter, with cwd in
ib_history_request_bounds and the Piker import checked against that cwd:
- tests/test_history_backfill.py: 11 passed in 1.92s.
- tests/test_ib_history.py: 17 passed in 0.36s.
- Ruff on all four changed Python files: all checks passed.
- git diff --check: passed.
The integration test uses simulated IB bars responses, real IB history
adapter and backfill logic, real SHM, and temporary NativeDB parquet.
It checks a fresh disk load. It does not test a live gateway, actor RPC,
or Qt rendering, and does not prove the origin of the user's live gap.
## Unedited review response
`None` is internal to the IB adapter. The backfiller receives
`NoData`, or `DataUnavailable` when the requested date has reached
the known beginning of history. I’ll make that distinction explicit.
The two dates answer different questions: “where should the next
history query end?” and “what is the oldest bar already in SHM?”
An empty reply changes only the first. I’ll rename them to
`request_end_dt` and `oldest_shm_dt` and explain the filtering with
a concrete example.
> `git diff efa72980 -- piker/brokers/ib/feed.py piker/tsp/_history.py`
## History-filter review follow-up
The human flagged the unused append_until_dt argument and confusing
append/prepend terminology in diff_history(). The helper had one
caller, using only prepend_until_dt. Removed the helper and inlined
its inclusive lower-time cutoff beside the SHM duplicate filter.
The human's nearby TODO comment is resolved by this removal; the
replacement comment describes the retained timestamps directly.
The storage comparison remains >= backfill_until_dt; the SHM
comparison remains < oldest_shm_dt.

View File

@ -0,0 +1,51 @@
---
model: gpt-6 (exact variant not exposed)
service: codex
timestamp: 2026-09-24T16:57:57.342784+00:00
git_ref: 1da9b760
scope: code
substantive: true
raw_file: 20260924T165757Z_1da9b760_prompt_io.raw.md
---
## Prompt
Document and test the existing null-segment repair, including its
flat-bar fallback, while distinguishing packed timestamp gaps.
## Response summary
Documented SHM-only repair, provider placement assumptions, fallback
fields, notifications, and concurrency limits. Added boundary and
no-history coverage and fixed epoch-dependent comparison tolerance
which could synthesize a duplicate timestamp at the right margin.
## Files changed
- piker/tsp/_history.py: helper contract and absolute tolerance.
- tests/test_history_backfill.py: four additional parameter/case runs.
## Human edits
The human identified cancellation as a source of stored gaps,
correctly described packed indexes and insertion costs, pointed to
maybe_fill_null_segments(), and requested immediate documentation
and tests. No direct source edits observed during this change;
the patch remains uncommitted for review.
## Close-copy factoring follow-up
The human requested stronger internal types, a reusable close-copy
helper, searchable comments for copying close and synthesizing
stamps, and research into historical venue closure queries.
Extracted _copy_close_to_null_rows(), preserving non-OHLC fields and
rejecting occupied slots before mutation. Added explicit callback,
array, segment-index and date annotations. Preserved the existing
provenance TODO and suggested avoiding volume sentinels.
Four helper cases cover successful fill and unchanged rejected
inputs. Backfill suite: 19 passed in 2.01s; Ruff and diff check pass.
IB documents historical SCHEDULE requests separately from contract
hours. The installed ib_async exposes reqHistoricalScheduleAsync.
No live schedule request or venue behavior change was performed.
Source: https://interactivebrokers.github.io/tws-api/historical_bars.html

View File

@ -0,0 +1,48 @@
---
model: gpt-6 (exact variant not exposed)
service: codex
timestamp: 2026-09-24T16:57:57.342784+00:00
git_ref: 1da9b760
diff_cmd: git diff 1da9b760 -- piker/tsp/_history.py tests/test_history_backfill.py
---
## Prompt
The human requested documentation and tests for
maybe_fill_null_segments(), identifying its flat OHLC fallback
and asking how packed SHM could accept later recovered bars.
## Code output
> `git diff 1da9b760 -- piker/tsp/_history.py`
> `git diff 1da9b760 -- tests/test_history_backfill.py`
Document the existing slot-based repair and its limitations.
Extend fallback coverage to NoData and DataUnavailable; verify
the observed packed MNQ gap remains unchanged. Reproduce the
relative-tolerance failure at a modern epoch, then disable relative
tolerance in the synthetic timestamp boundary check.
## Validation
Before the fix: modern-epoch boundary case failed, zero-epoch
case passed. The overfull synthetic run duplicated the right bar.
After the fix: 15 backfill tests passed in 1.99s.
## Close-copy factoring follow-up
The human requested stronger internal types, a reusable close-copy
helper, searchable comments for copying close and synthesizing
stamps, and research into historical venue closure queries.
Extracted _copy_close_to_null_rows(), preserving non-OHLC fields and
rejecting occupied slots before mutation. Added explicit callback,
array, segment-index and date annotations. Preserved the existing
provenance TODO and suggested avoiding volume sentinels.
Four helper cases cover successful fill and unchanged rejected
inputs. Backfill suite: 19 passed in 2.01s; Ruff and diff check pass.
IB documents historical SCHEDULE requests separately from contract
hours. The installed ib_async exposes reqHistoricalScheduleAsync.
No live schedule request or venue behavior change was performed.
Source: https://interactivebrokers.github.io/tws-api/historical_bars.html

View File

@ -0,0 +1,91 @@
---
model: gpt-6 (exact variant not exposed)
service: codex
timestamp: 2026-09-24T18:54:56.893490+00:00
git_ref: 1da9b760
scope: code
substantive: true
raw_file: 20260924T185456Z_1da9b760_schedule_prompt_io.raw.md
---
## Prompt
The human requested: first freeze a commit-plan for the existing
close-copy/null-repair changes, then implement ib_async historical
schedule support with tests, use it for datad.ib history-buffer gap
spacing, and plan a common on-demand segment interface for CLI and
chart selection. Avoid reindexing in this first pass and record
fallback insertion only as a TODO.
## Response summary
Implemented historical schedule normalization, bounded startup
reservations, explicit actor-local synthetic ranges, and two read
RPC functions. Added deterministic adapter tests and real SHM/parquet
startup coverage. Wrote the segment-backfill interface plan.
This entry covers the new schedule work only. The earlier frozen
null-repair plan has its own provenance. The shared _history.py file
contains changes from both scopes until the earlier commit is made.
## Human edits
The human identified cancellation during backfill as the reproducible
trigger, explained the packed-index insertion tradeoff, requested
reuse of venue closure knowledge, and chose placeholders to avoid
expensive rewrites. They required explicit placeholder annotation,
proposed later graphics compression, and asked to defer insertion
fallbacks. They also requested the existing null-repair commit plan
before the new schedule work. These are human-directed architecture
and scope choices; no acceptance or task completion state is inferred.
## Validation and limits
55 targeted tests passed; Ruff and diff checks passed. No live IB
qualification was performed. Reservations are synthetic, not recovered
observations. Repair execution, persistence of unfinished requests,
chart compression, FSP handling, export filtering, and request-level
IB cancellation remain follow-up work documented in the plan.
## Files changed
- `piker/brokers/ib/venues.py`
- `piker/brokers/ib/api.py`
- `piker/brokers/ib/feed.py`
- `piker/tsp/_reserve.py`
- `piker/tsp/_history.py`
- `tests/providers/test_ib_schedule.py`
- `tests/providers/test_history_reservations.py`
- `docs/history_segment_backfill.rst`
## Pre-commit module review
The human requested placing historical schedule normalization in the
existing venues.py module, naming the allocation module _reserve.py,
and adding the missing project license headers. Applied those moves,
updated imports and test patch targets, and added the standard AGPL
headers to the new allocation module and both new test modules.
## Local tuicr review follow-up
The human supplied eleven comments requesting a configurable newest
gap count, caller and metadata references, explanations of guards
and provider-specific limits, multiline boolean style, capacity
fallback rationale, an explanation of numpy split boundaries, and
clearer test cases, fixture types, and module docstrings.
Added newest_gaps=32, with zero disabling queries and negative values
rejected. Documented tsdb_backfill ownership and the local/RPC range
accessors. Explained recoverable overflow and half-open synthetic
range grouping. Typed tmp_path as pathlib.Path and monkeypatch as
pytest.MonkeyPatch. Added four request-budget cases.
Verification: reservation tests 11 passed; schedule tests 7 passed;
Ruff and git diff --check passed. Changes remain unstaged for review.
No forge review or CI state was queried; this was local feedback.
## Provider-test layout review
The human requested grouping both new history/schedule test modules
under tests/providers. Moved both files without changing test logic;
they retain the shared tests/conftest.py fixtures.

View File

@ -0,0 +1,100 @@
---
model: gpt-6 (exact variant not exposed)
service: codex
timestamp: 2026-09-24T18:54:56.893490+00:00
git_ref: 1da9b760
diff_cmd: git diff HEAD~1..HEAD
---
> `git diff HEAD~1..HEAD -- piker/brokers/ib/_schedule.py`
> `git diff HEAD~1..HEAD -- piker/brokers/ib/api.py`
> `git diff HEAD~1..HEAD -- piker/brokers/ib/feed.py`
> `git diff HEAD~1..HEAD -- piker/tsp/_reservations.py`
> `git diff HEAD~1..HEAD -- piker/tsp/_history.py`
> `git diff HEAD~1..HEAD -- tests/test_ib_schedule.py`
> `git diff HEAD~1..HEAD -- tests/test_history_reservations.py`
> `git diff HEAD~1..HEAD -- docs/history_segment_backfill.rst`
## Generated output
Added Client.history_schedule() over the installed ib_async API,
normalizing dated sessions to UTC with explicit response coverage.
Reject ambiguous DST times, reversed sessions, and overlapping
sessions. Match the existing extended-hours bars policy.
Added startup-only schedule reservations to tsdb_backfill before
prepending older stored history. Reserve open-session sample slots
only where the reply covers the full candidate gap. Return synthetic
UTC ranges separately from OHLCV; copy preceding close into flat
placeholders without writing those placeholders to parquet.
Unknown coverage and capacity failures retain packed time gaps.
Added datad.ib schedule and reservation metadata RPC functions and
a proposed shared CLI/chart segment request protocol. The repair
queue, durable unresolved intervals, chart compression, FSP handling,
and coordinated insertion remain deferred. No live data was repaired.
Verification is complete: 55 targeted tests pass, including the
new schedule and real SHM/parquet tests. I also added checks for
reversed session bounds and the 25-hour day at the autumn DST
change.
The segment API plan uses UTC ranges for both CLI and chart
requests, a provider-owned queue, durable unfinished intervals,
and replacement of reserved slots without reindexing. Live IB
schedule coverage still needs qualification.
## Limits
Only stored-history gaps are reserved before prepend; published rows
are never shifted. Newest 32 gaps, at most 31 days each, are queried
serially with a 10-second schedule timeout. This can delay history
publication. Underlying IB request cancellation is still a TODO.
Manual whole-SHM export requires synthetic-range exclusion before
it can preserve provider-only storage. The chart currently displays
flat placeholders, and synthetic-aware FSP treatment is deferred.
Tests: history backfill 19, IB history 17, method proxy 5, schedule 7,
reservations 7. Sandbox local-socket failures were rerun with approval.
Ruff and git diff --check passed. No live broker requests were made.
## Module-layout review response
Schedule normalization now lives in piker.brokers.ib.venues beside
the existing session helpers. The allocation module is tsp._reserve.
Added the project AGPL headers to the new module and test files.
Earlier diff pointers record the original generated paths; use
the following paths for the reviewed layout:
> `git diff HEAD~1..HEAD -- piker/brokers/ib/venues.py`
> `git diff HEAD~1..HEAD -- piker/tsp/_reserve.py`
## Local review response
The fixes pass all 18 tests in the two affected files.
The new limit tests cover zero, negative, one-gap, and two-gap
budgets, including newest-first selection.
Capacity overflow remains a warning: no partial reservation is
published for that gap, and the original observations remain
available. The comments now explain that fallback explicitly.
Diff pointers above include the review edits to tsp._reserve, both
test modules, and the interface document. This review did not query
a forge or publish replies.
## Provider-test paths
Moved both new test modules under tests/providers as requested.
> `git diff HEAD~1..HEAD -- tests/providers/test_ib_schedule.py`
> `git diff HEAD~1..HEAD -- tests/providers/test_history_reservations.py`

View File

@ -0,0 +1,45 @@
---
model: gpt-6
service: codex
timestamp: 2026-09-24T23:24:05.525363+00:00
git_ref: 441358e2
scope: code
substantive: true
raw_file: 20260924T232405Z_441358e2_schedule_coverage_prompt_io.raw.md
---
## Prompt
User: "do it", approving the proposed bounded wider-window retry
for incomplete schedule coverage and its MNQ regression test.
Previous live qualification showed a one-day request began after
the gap's left edge; a wider query covered it and excluded the
one-hour venue closure.
## Response summary
Added bounded backwards widening under one timeout, preserving actual
coverage and fixed end bounds. Added captured-response allocation,
unknown-coverage, and retry-timeout tests, and updated API documentation.
39 selected tests and Ruff passed. Live data was not changed.
## Files changed
- piker/brokers/ib/api.py — bounded schedule coverage retries.
- tests/providers/test_ib_schedule.py — captured MNQ and retry regressions.
- docs/history_segment_backfill.rst — retry contract and limits.
## Human edits
The user established schedule-driven allocation, explicit unknown
coverage, and avoiding reindexing as the design constraints, supplied
the MNQ gap, and authorized this focused coverage fix. No direct human
source edits occurred during this patch; review is pending.
## Follow-up review
The user requested factoring and documenting the repeated 86400
conversion. Added piker.calc.SECONDS_PER_DAY, documented elapsed
versus calendar/session days once, and reused it in the IB query
and reservation span check. Named the client retry limit locally.
Schedule tests: 11 passed; Ruff passed after this adjustment.

View File

@ -0,0 +1,66 @@
---
model: gpt-6
service: codex
timestamp: 2026-09-24T23:24:05.525363+00:00
git_ref: 441358e2
diff_cmd: git diff HEAD~1..HEAD
---
> `git diff HEAD~1..HEAD -- piker/brokers/ib/api.py`
> `git diff HEAD~1..HEAD -- tests/providers/test_ib_schedule.py`
> `git diff HEAD~1..HEAD -- docs/history_segment_backfill.rst`
Client.history_schedule() widens incomplete left coverage by doubling
numDays up to 31 while preserving endDateTime and actual returned
coverage. All attempts share an asyncio timeout. Missing right coverage
returns unchanged because extending the lookback cannot repair it.
Tests replay the captured MNQ schedules through the real client and
reservation allocator, check bounded incomplete coverage and retry
cancellation under one timeout. Documentation describes these limits.
## User-facing output
The captured MNQ case now passes through the real client method
and allocator: the request widens from one day to two, then
reserves 51,824 slots while leaving the venue closure compressed.
The tests also cover permanently incomplete replies and a timeout
during the retry. I’m running the related regression suites now.
All 39 selected tests passed, and Ruff passed. The reservation
suite needed a retry outside the sandbox because Trio’s local
wakeup socket was blocked.
The fix and regression tests are ready for review. I’m recording
the prompt provenance; live SHM and parquet remain unchanged.
## Validation commands and results
Worktree-local py313 in the existing Nix environment; import piker
resolved inside this worktree.
- py313/bin/python -m pytest -p no:xonsh -q -x --tb=short --no-header tests/providers/test_ib_schedule.py tests/providers/test_history_reservations.py
Sandbox: 11 passed then PermissionError creating Trio wakeup socket.
Escalated rerun: 22 passed in 1.02s; fake IB, disposable test SHM.
- py313/bin/python -m pytest -p no:xonsh -q -x --tb=short --no-header tests/test_ib_history.py
17 passed in 0.69s.
- ruff check piker/brokers/ib/api.py tests/providers/test_ib_schedule.py
All checks passed (existing deprecated configuration warning).
- git diff --check: passed.
No live service restart, live history repair, staging, or commit.
## Follow-up user review and generated changes
User: "feels like this same magic # from the prior commit should be
factored and commented once? `num_days: int = max(1, ceil(seconds /
86400))` other then that all looks decent"
> `git diff HEAD~1..HEAD -- piker/calc.py piker/tsp/_reserve.py`
Added SECONDS_PER_DAY for both consumers, with one comment explaining
fixed elapsed days versus calendar days and trading sessions. Named
the client's 31-day retry limit locally. After this change the
schedule suite passed 11 tests and Ruff passed all four Python files.

View File

@ -0,0 +1,10 @@
# AI Prompt I/O Log — codex
This directory tracks prompts and outputs for AI-assisted development
using Codex. Substantive contributions include model identification,
prompt context, output records, and human contribution accounting.
Code output uses Git diff references; non-code output is retained in
the paired `.raw.md` file. Human contributors remain responsible for
review and acceptance. Entries follow the
[NLNet generative AI policy](https://nlnet.nl/foundation/policies/generativeAI/).

View File

@ -0,0 +1,64 @@
---
model: gpt-5.6-sol
provider: openai
service: opencode
session: ses_0799212ebffe42arY96czXn89F
timestamp: 2026-07-21T20:52:28Z
git_ref: 58ffe487
scope: config
substantive: true
raw_file: 20260721T205228Z_58ffe487_prompt_io.raw.md
---
## Prompt
The user asked to scan Claude's repo-local skills and
documentation, summarize their specializations, and then
rework repo-specific skills for use across coding
harnesses, particularly OpenCode. The user intends to
choose one of Claude's unfinished tasks afterward.
## Response summary
Standardized the tracked piker skills on portable Agent
Skills frontmatter, made `commit-msg` harness-aware,
documented OpenCode discovery and command delegation, and
fixed an unsafe `np.searchsorted()` example found during
the audit. Preserved the existing `.claude` artifact path
as a shared legacy location to avoid breaking established
commit workflows.
## Files changed
- `.claude/skills/commit-msg/SKILL.md` - portable,
harness-aware commit-message workflow
- `.claude/skills/commit-msg/style-guide-reference.md` -
generic coding-harness attribution
- `.claude/skills/piker-conc-expert/SKILL.md` - portable
frontmatter
- `.claude/skills/piker-profiling/SKILL.md` - portable
frontmatter
- `.claude/skills/piker-slang/SKILL.md` - portable
frontmatter
- `.claude/skills/pyqtgraph-optimization/SKILL.md` -
portable frontmatter
- `.claude/skills/timeseries-optimization/SKILL.md` -
portable frontmatter and safe search bounds handling
- `ai/README.md` - shared-skill integration model
- `ai/claude-code/README.md` - cross-harness Claude Code
documentation
- `ai/opencode/README.md` - OpenCode usage and discovery
documentation
- `.opencode/commands/commit-msg.md` - thin OpenCode
command delegating to the shared skill
- `ai/prompt-io/opencode/README.md` - OpenCode provenance
policy
- `ai/prompt-io/opencode/20260721T205228Z_58ffe487_prompt_io.raw.md`
- unedited response record
- `ai/prompt-io/opencode/20260721T205228Z_58ffe487_prompt_io.md`
- provenance metadata and summary
## Human edits
None - generated changes have not yet been reviewed or
edited by the human.

View File

@ -0,0 +1,33 @@
---
model: gpt-5.6-sol
provider: openai
service: opencode
timestamp: 2026-07-21T20:52:28Z
git_ref: 58ffe487
diff_cmd: git diff HEAD~1..HEAD
---
The repo-local skill inventory contains seven domains:
commit-message generation, actor-runtime concurrency,
distributed profiling, project communication style,
Python source style, PyQtGraph rendering, and NumPy/Polars
timeseries optimization.
I converted the six tracked repo-specific skills to the
portable Agent Skills frontmatter subset so Claude Code
and OpenCode can load the same source files. The
commit-message workflow now gathers live git context at
execution time, uses runtime harness/model attribution,
preserves human-owned task state, and keeps the existing
message artifact paths for compatibility.
I added OpenCode integration documentation and updated
the shared and Claude Code docs to describe a single
cross-harness skill source with thin harness commands.
During validation I also corrected an out-of-bounds
`np.searchsorted()` example in the timeseries skill.
The unfinished artifacts found during the scan are a
shared-memory migration plan and a gap-annotation
performance handoff. The earlier datad split plan appears
implemented and is now captured by the concurrency skill.

View File

@ -0,0 +1,41 @@
---
model: gpt-5.6-sol
provider: openai
service: opencode
session: ses_0799212ebffe42arY96czXn89F
timestamp: 2026-07-21T21:50:55Z
git_ref: c6ec3d41
scope: docs
substantive: true
raw_file: 20260721T215055Z_c6ec3d41_prompt_io.raw.md
---
## Prompt
The user asked to boot a plan to continue work on the
time-series gap checker and bug fixing of the history
backfiller. The user authorized prompt-I/O capture for the
plan documentation after the repository audit.
## Response summary
Produced a bounded repair plan based on current source,
historical issue caches, existing profiling notes, and a
test-coverage audit. The plan prioritizes reproducible
correctness failures and storage durability before venue
classification, annotation performance, or manual repair
UX.
## Files changed
- `plans/opencode/gap-checker-backfiller-repair.md` -
phased implementation and verification plan
- `ai/prompt-io/opencode/20260721T215055Z_c6ec3d41_prompt_io.raw.md`
- unedited response record
- `ai/prompt-io/opencode/20260721T215055Z_c6ec3d41_prompt_io.md`
- provenance metadata and response summary
## Human edits
None - generated plan has not yet been edited by the
human.

View File

@ -0,0 +1,26 @@
---
model: gpt-5.6-sol
provider: openai
service: opencode
timestamp: 2026-07-21T21:50:55Z
git_ref: c6ec3d41
diff_cmd: git diff HEAD~1..HEAD
---
> `git diff -- plans/opencode/gap-checker-backfiller-repair.md`
Generated a phased implementation plan for continuing
piker's gap checker and history backfiller repairs. The
plan separates data correctness from annotation
performance and orders work by dependency: deterministic
tests, canonical frame/gap normalization, structured
backfill completion, non-destructive atomic NativeDB
writes, exact null repair, provider/venue classification,
annotation vectorization, and crash qualification.
The plan is grounded in current code paths, local issue
caches `#62`, `#71`, and `#75`, and the existing 1,285-gap
profiling handoff. It records confirmed present defects,
design invariants, per-phase acceptance gates, test
commands, commit boundaries, and explicitly deferred UX
and provider work.

View File

@ -0,0 +1,46 @@
---
model: gpt-5.6-sol
provider: openai
service: opencode
session: ses_0799212ebffe42arY96czXn89F
timestamp: 2026-07-22T00:13:57Z
git_ref: 16f3cd6f
scope: code
substantive: true
raw_file: 20260722T001357Z_16f3cd6f_prompt_io.raw.md
---
## Prompt
Continue the gap-checker and history-backfiller repair plan. Start
with deterministic analysis characterization, then fix canonical
sorting, dedupe-derived columns, positive gap detection, and null
grouping.
During review, the user challenged the replacement null-grouping
algorithm because its initial tests did not assert exact endpoints.
The user explicitly required retaining detailed, human-oriented
algorithm documentation such as the existing ASCII segment map.
## Response summary
Added deterministic analysis regressions and corrected normalization
so sorting and duplicate resolution precede derived-column
calculation. Unified null grouping for NumPy and Polars inputs,
documented its exact inclusive-boundary contract with an adapted
visual map, and verified all small null layouts against an independent
state-machine oracle. Retained sorted smart-dedupe output.
## Files changed
- `piker/tsp/_anal.py` - canonical gap and documented null grouping
- `piker/tsp/_dedupe_smart.py` - sorted duplicate resolution
- `tests/test_tsp_analysis.py` - exact and exhaustive regressions
- `ai/prompt-io/opencode/20260722T001357Z_16f3cd6f_prompt_io.raw.md`
- unedited response record
- `ai/prompt-io/opencode/20260722T001357Z_16f3cd6f_prompt_io.md`
- provenance metadata and response summary
## Human edits
None - generated changes have not yet been edited by the human.

View File

@ -0,0 +1,40 @@
---
model: gpt-5.6-sol
provider: openai
service: opencode
timestamp: 2026-07-22T00:13:57Z
git_ref: 16f3cd6f
diff_cmd: git diff HEAD~1..HEAD
---
> `git diff HEAD~1..HEAD -- piker/tsp/_anal.py`
Generated canonical gap and null-segment normalization. Derived
datetime and delta columns are recomputed after final sorting and
duplicate resolution, and negative deltas no longer become ordinary
gaps.
NumPy and Polars null grouping share one implementation while
retaining the human-oriented visual map of contiguous zero runs. The
documented contract defines absolute inclusive endpoints, margin
expansion, frame boundary clamping, contiguous-index validation, and
requested-column selection.
> `git diff HEAD~1..HEAD -- piker/tsp/_dedupe_smart.py`
Adjusted smart dedupe to honor sorted output and resolve duplicate
timestamps without retaining stale order.
> `git diff HEAD~1..HEAD -- tests/test_tsp_analysis.py`
Added deterministic synthetic OHLCV coverage for sorted dedupe, fresh
derived columns, and positive gap detection. Null grouping is checked
against readable endpoint examples and an independent state-machine
oracle over every layout through six rows, three margin widths, and
both NumPy and Polars inputs.
Verification generated with the patch:
`timeout -k 5 30 python -m pytest -q tests/test_tsp_analysis.py`
Result: 11 passed.

View File

@ -0,0 +1,36 @@
---
model: gpt-5.6-sol
provider: openai
service: opencode
session: ses_0799212ebffe42arY96czXn89F
timestamp: 2026-07-22T00:13:58Z
git_ref: 16f3cd6f
scope: code
substantive: true
raw_file: 20260722T001358Z_16f3cd6f_prompt_io.raw.md
---
## Prompt
Continue the structured backfill phase after the analysis regressions.
Characterize provider exhaustion and genuinely empty frames, then remove
completion paths that can hang after child-task exit.
## Response summary
Removed orphan-prone completion events from the structured nursery flow
and checked empty provider frames before indexing timestamp endpoints.
Added deterministic timeout-bounded regressions for both exit paths.
## Files changed
- `piker/tsp/_history.py` - structured completion and empty-frame fixes
- `tests/test_history_backfill.py` - deterministic exit regressions
- `ai/prompt-io/opencode/20260722T001358Z_16f3cd6f_prompt_io.raw.md`
- unedited response record
- `ai/prompt-io/opencode/20260722T001358Z_16f3cd6f_prompt_io.md`
- provenance metadata and response summary
## Human edits
None - generated changes have not yet been edited by the human.

View File

@ -0,0 +1,25 @@
---
model: gpt-5.6-sol
provider: openai
service: opencode
timestamp: 2026-07-22T00:13:58Z
git_ref: 16f3cd6f
diff_cmd: git diff HEAD~1..HEAD
---
> `git diff HEAD~1..HEAD -- piker/tsp/_history.py`
Removed redundant completion events whose consumers waited only after
their owning nurseries had joined. Added empty provider-frame handling
before timestamp endpoint indexing so provider exhaustion exits cleanly.
> `git diff HEAD~1..HEAD -- tests/test_history_backfill.py`
Added bounded, actor-free regressions for `DataUnavailable` and empty
provider-frame completion.
Verification generated with the patch:
`timeout -k 5 60 python -m pytest -q tests/test_history_backfill.py`
Result: 2 passed.

View File

@ -0,0 +1,39 @@
---
model: gpt-5.6-sol
provider: openai
service: opencode
session: ses_0799212ebffe42arY96czXn89F
timestamp: 2026-07-22T02:41:33Z
git_ref: 3a4f6737
scope: code
substantive: true
raw_file: 20260722T024133Z_3a4f6737_prompt_io.raw.md
---
## Prompt
The user challenged the analysis commit message's claim that only
positive sample-step gaps are detected and asked whether out-of-order
timestamps were simply ignored. After confirming the missing separate
reporting path, the user instructed the agent to implement the fix.
## Response summary
Separated missing-history gaps from timestamp-ordering errors without
changing the existing `dedupe()` return shape. Added a structured
detector and made normalization warn before sorting removes evidence of
duplicates or reversed timestamps.
## Files changed
- `piker/tsp/_anal.py` - ordering detector and dedupe reporting
- `piker/tsp/__init__.py` - public detector export
- `tests/test_tsp_analysis.py` - exact ordering-error regression
- `ai/prompt-io/opencode/20260722T024133Z_3a4f6737_prompt_io.raw.md`
- unedited response record
- `ai/prompt-io/opencode/20260722T024133Z_3a4f6737_prompt_io.md`
- provenance metadata and response summary
## Human edits
None - generated changes have not yet been edited by the human.

View File

@ -0,0 +1,30 @@
---
model: gpt-5.6-sol
provider: openai
service: opencode
timestamp: 2026-07-22T02:41:33Z
git_ref: 3a4f6737
diff_cmd: git diff HEAD~1..HEAD
---
> `git diff HEAD~1..HEAD -- piker/tsp/_anal.py`
Added `detect_time_ordering_errors()` to return non-positive timestamp
steps as structured rows. `dedupe()` now warns with those rows before
sorting and duplicate removal erase the original ordering evidence.
> `git diff HEAD~1..HEAD -- piker/tsp/__init__.py`
Exported the ordering detector through the public TSP package.
> `git diff HEAD~1..HEAD -- tests/test_tsp_analysis.py`
Changed the negative-delta regression to verify exact predecessor,
timestamp, and delta values, confirm exclusion from missing-history
gaps, and require explicit normalization reporting.
Verification generated with the patch:
- `tests/test_tsp_analysis.py`: 11 passed
- Ruff: passed
- `git diff --check`: passed

View File

@ -0,0 +1,43 @@
---
model: gpt-5.6-sol
provider: openai
service: opencode
session: ses_0799212ebffe42arY96czXn89F
timestamp: 2026-07-22T02:41:34Z
git_ref: 3a4f6737
scope: code
substantive: true
raw_file: 20260722T024134Z_3a4f6737_prompt_io.raw.md
---
## Prompt
The user asked why the in-progress `piker/storage` changes were omitted
from the earlier commit plan. After receiving an inventory of unfinished
Phase 3 requirements, the user instructed the agent to complete the work.
## Response summary
Finished non-destructive, atomic NativeDB updates and integrated them
with history startup and reverse backfill. Expanded tests through two
adversarial review rounds that caught and corrected cross-client lock
blocking, stale temp indexing, discontinuous persisted indexes, startup
snapshot races, missing latest-frame persistence, notification wedges,
and mutable/truncated SHM persistence.
## Files changed
- `piker/storage/__init__.py` - incremental storage contract
- `piker/storage/nativedb.py` - validated atomic merge persistence
- `piker/storage/marketstore/__init__.py` - non-duplicate update mode
- `piker/tsp/_history.py` - provider-delta and startup persistence
- `tests/test_storage_nativedb.py` - NativeDB durability regressions
- `tests/test_history_backfill.py` - persistence lifecycle regressions
- `ai/prompt-io/opencode/20260722T024134Z_3a4f6737_prompt_io.raw.md`
- unedited response record
- `ai/prompt-io/opencode/20260722T024134Z_3a4f6737_prompt_io.md`
- provenance metadata and response summary
## Human edits
None - generated changes have not yet been edited by the human.

View File

@ -0,0 +1,43 @@
---
model: gpt-5.6-sol
provider: openai
service: opencode
timestamp: 2026-07-22T02:41:34Z
git_ref: 3a4f6737
diff_cmd: git diff HEAD~1..HEAD
---
> `git diff HEAD~1..HEAD -- piker/storage/__init__.py piker/storage/nativedb.py piker/storage/marketstore/__init__.py`
Added an incremental storage update contract. NativeDB now validates
canonical numeric OHLCV data, serializes writers both actor-locally and
through cancellable filesystem locks, merges with incoming-wins seam
resolution, regenerates contiguous indexes, reopens and validates a
same-directory temporary parquet, fsyncs it, atomically replaces the
target, fsyncs the directory, and publishes cache only after replacement.
NativeDB indexing ignores lock and crash-left temporary files.
MarketStore uses its non-duplicate write mode for incremental updates.
> `git diff HEAD~1..HEAD -- piker/tsp/_history.py`
Separated full provider deltas from capacity-truncated SHM pushes.
Backfill persists before SHM mutation, publishes bounded notifications,
and delays latest-frame publication until the concurrent pre-update
storage snapshot completes.
> `git diff HEAD~1..HEAD -- tests/test_storage_nativedb.py tests/test_history_backfill.py`
Added deterministic coverage for merge preservation, conflict policy,
atomic failures, reopened-candidate validation, cache consistency,
schema and timestamp invariants, contiguous index regeneration,
separate series, stale temp indexing, cancellable file-lock contention,
full provider-delta persistence, latest-frame ordering, and bounded
notification teardown.
Verification generated with the patch:
- deterministic analysis/backfill/storage set: 31 passed
- Ruff: passed
- `git diff --check`: passed
- final adversarial review: no findings

View File

@ -0,0 +1,32 @@
---
model: openai/gpt-5.6-sol
service: opencode
session: ses_092a25886ffeT0nrIrdkMEcgJK
timestamp: 2026-07-24T22:01:56Z
git_ref: flake_update
scope: config
substantive: true
raw_file: 20260724T220156Z_43d7a3ed_prompt_io.raw.md
---
## Prompt
After committing Piker's provider-neutral deployment, the user asked to
extract Piker-specialized `/run-tests` content from the historical
`wkt/run_tests_skill` implementation into the new deployment.
## Response summary
Create a repository-owned `test-harness-reference.md` for the canonical Run
base. Revalidate historical commands and assumptions against current pytest,
uv/Nix environments, tests, fixtures, and Tractor runtime behavior, retaining
only current Piker-specific guidance.
## Files changed
- `.claude/skills/run-tests/test-harness-reference.md` - define Piker's safe
commands, opt-in boundaries, mappings, known outcomes, and runtime notes.
## Human edits
None at generation time; pending human review.

View File

@ -0,0 +1,32 @@
---
model: openai/gpt-5.6-sol
service: opencode
timestamp: 2026-07-24T22:01:56Z
git_ref: flake_update
diff_cmd: git diff HEAD~1..HEAD
---
The user asked to extract useful Piker-specific `/run-tests` behavior from
`wkt/run_tests_skill` after committing the provider-neutral deployment.
> `git diff HEAD~1..HEAD -- .claude/skills/run-tests/test-harness-reference.md`
Generate a repository-owned harness reference rather than restoring the old
standalone `SKILL.md`. Preserve verified Piker-specific environment, command,
scope, fixture, test-layout, change-mapping, known-outcome, and Tractor-runtime
details while leaving shared execution and cleanup policy in the canonical
`ai.skillz` base.
Repository inspection found that the historical skill needs correction:
- the comments-only `pytest.ini` shadows `pyproject.toml` pytest options, so
`-p no:xonsh` must be explicit;
- no marker-based offline suite exists, and several live/GUI/container tests
are unsafe defaults;
- skipped Docker and Questrade modules can fail during import before marks;
- actor timeout retry remains relevant only for the exact timed-out command;
- `py313` and frozen no-sync `uv run` are the current local environment paths.
Validate with checkout import resolution, safe collection, and deterministic
watchlist/accounting tests. Do not run live broker, GUI, container,
credentialed, or user-config-dependent cases during harness verification.

View File

@ -0,0 +1,40 @@
---
model: gpt-5.6-sol
provider: openai
service: opencode
session: unavailable
timestamp: 2026-07-27T20:47:11Z
git_ref: 84a6d47b
scope: code
substantive: true
raw_file: 20260727T204711Z_84a6d47b_prompt_io.raw.md
---
## Prompt
The user identified that `backfiller_deep_fixes` introduced persistent
`.parquet.lock` files, questioned whether Parquet or Arrow already handled
writer coordination, and clarified that piker's `tractor` structured
concurrency design already provides single-writer ownership through the
persistent `datad` feed task. The user then asked to open the backfiller
worktree and resolve the redundant locking.
## Response summary
Removed redundant actor-local and filesystem locking while preserving
atomic crash-safe Parquet replacement. Replaced the synthetic contention
test with regressions for the actual cross-branch failure: writes create
no lock sidecars, and legacy lock artifacts are ignored during indexing.
## Files changed
- `piker/storage/nativedb.py` - rely on `datad` writer ownership
- `tests/test_storage_nativedb.py` - lock-sidecar regressions
- `ai/prompt-io/opencode/20260727T204711Z_84a6d47b_prompt_io.raw.md`
- unedited response record
- `ai/prompt-io/opencode/20260727T204711Z_84a6d47b_prompt_io.md`
- provenance metadata and response summary
## Human edits
None - generated changes have not been edited by the human.

View File

@ -0,0 +1,30 @@
---
model: gpt-5.6-sol
provider: openai
service: opencode
timestamp: 2026-07-27T20:47:11Z
git_ref: 84a6d47b
diff_cmd: git diff HEAD~1..HEAD
---
> `git diff HEAD~1..HEAD -- piker/storage/nativedb.py`
Removed actor-local and filesystem writer locks from NativeDB. The
persistent `datad` feed task already owns writes for each series, while
its timeframe children write distinct files. NativeDB retains validated
temporary writes, file and directory synchronization, and atomic target
replacement for crash durability. The exact `.parquet` suffix filter is
retained so legacy lock sidecars are harmless.
> `git diff HEAD~1..HEAD -- tests/test_storage_nativedb.py`
Removed the synthetic cross-client lock-contention test. Added regression
coverage proving replacement and incremental writes create no lock
sidecars. Extended indexing coverage with the legacy
`.parquet.lock` artifact that caused upstream `flake_update` to crash.
Verification generated with the patch:
- NativeDB and history regression set: 20 passed
- Ruff: passed
- `git diff --check`: passed

View File

@ -0,0 +1,42 @@
---
model: gpt-5.6-sol
provider: openai
service: opencode
session: ses_0799212ebffe42arY96czXn89F
timestamp: 2026-07-27T21:24:54Z
git_ref: 689df816
scope: code
substantive: true
raw_file: 20260727T212454Z_689df816_prompt_io.raw.md
---
## Prompt
The user reran the known IB FQME on `backfiller_deep_fixes` and reported
that `publish_latest_frame()` still failed because NativeDB required an
`index` absent from IB's provider frame. The user called out the missed
tabular schema boundary and requested an actual end-to-end test suite
rather than storage fakes.
## Response summary
Repaired the IB-provider-to-NativeDB boundary by canonicalizing durable
fields and mapping reloads into provider-specific SHM buffers. Added a
local actor/SHM/Parquet integration regression covering first startup and
restart, plus focused conversion and timestamp-coercion tests. Two review
rounds found and resolved restart hydration and fractional-time hazards.
## Files changed
- `piker/storage/nativedb.py` - canonical storage and SHM field mapping
- `piker/tsp/_anal.py` - name-based Polars-to-NumPy conversion
- `tests/test_history_backfill.py` - actor/SHM/Parquet integration test
- `tests/test_storage_nativedb.py` - schema conversion regressions
- `ai/prompt-io/opencode/20260727T212454Z_689df816_prompt_io.raw.md`
- unedited response record
- `ai/prompt-io/opencode/20260727T212454Z_689df816_prompt_io.md`
- provenance metadata and response summary
## Human edits
None - generated changes have not been edited by the human.

View File

@ -0,0 +1,34 @@
---
model: gpt-5.6-sol
provider: openai
service: opencode
timestamp: 2026-07-27T21:24:54Z
git_ref: 689df816
diff_cmd: git diff HEAD~1..HEAD
---
> `git diff HEAD~1..HEAD -- piker/storage/nativedb.py piker/tsp/_anal.py`
Normalized index-less provider frames to NativeDB's canonical names,
order, and dtypes before merge and publication. Provider-only columns
are excluded from durable files, derived indexes are regenerated, and
fractional timestamps are rejected before integer conversion. Added a
canonical field map for hydrating provider-specific SHM buffers while
leaving provider-only fields at their defaults. Changed Polars-to-NumPy
conversion to select columns by field name rather than position.
> `git diff HEAD~1..HEAD -- tests/test_history_backfill.py tests/test_storage_nativedb.py`
Added deterministic coverage using IB's actual provider dtype and a real
local `tractor` root actor, IB-typed shared-memory buffers, NativeDB,
Parquet persistence, first-start publication, and restart hydration.
Added focused regressions for reordered/extra Polars columns and
fractional timestamp rejection.
Verification generated with the patch:
- NativeDB and history integration set: 23 passed
- Ruff: passed
- `git diff --check`: passed
- second adversarial review: no findings
- no live gateway or network tests run

View File

@ -0,0 +1,41 @@
---
model: gpt-5.6-sol
provider: openai
service: opencode
session: ses_0799212ebffe42arY96czXn89F
timestamp: 2026-07-27T21:59:06Z
git_ref: 689df816
scope: code
substantive: true
raw_file: 20260727T215906Z_689df816_prompt_io.raw.md
---
## Prompt
After the actor/SHM/Parquet integration test exposed a stale SHM triplet,
the user requested leak-cleaner machinery in the pytest harness using
Tractor's existing ownership and lifetime patterns.
## Response summary
Added creator-scoped SHM leak tracking to pytest without scanning
`/dev/shm` or treating attachments as owned. The fixture cooperates with
normal Tractor teardown, verifies POSIX object identity before fallback
cleanup, restores process-local token state, and fails after cleaning a
leak. Regressions cover the exact pre-actor-registration allocation window,
positional creators, and external attachment safety.
## Files changed
- `tests/conftest.py` - creator-scoped SHM leak tracking fixture
- `tests/test_shm_cleanup.py` - ownership and failure-window regressions
- `.claude/skills/run-tests/test-harness-reference.md`
- document current-process SHM cleanup behavior
- `ai/prompt-io/opencode/20260727T215906Z_689df816_prompt_io.raw.md`
- unedited response record
- `ai/prompt-io/opencode/20260727T215906Z_689df816_prompt_io.md`
- provenance metadata and response summary
## Human edits
None - generated changes have not been edited by the human.

View File

@ -0,0 +1,25 @@
---
model: gpt-5.6-sol
provider: openai
service: opencode
timestamp: 2026-07-27T21:59:06Z
git_ref: 689df816
diff_cmd: git diff HEAD~1..HEAD
---
> `git diff HEAD~1..HEAD -- tests/conftest.py tests/test_shm_cleanup.py .claude/skills/run-tests/test-harness-reference.md`
Added function-scoped SHM ownership tracking around Tractor's test-process
factory. The fixture records only successful creators, removes ownership
records during normal Tractor teardown, verifies POSIX object identity
before fallback unlink, restores the token-cache baseline, and fails after
cleaning any leak. Regressions reproduce allocation failure before actor
lifetime registration and prove external attachments remain untouched.
Verification generated with the patch:
- SHM cleanup regressions: 2 passed
- combined SHM, NativeDB, and history set: 25 passed
- Ruff: passed
- `git diff --check`: passed
- final adversarial review: no findings

View File

@ -0,0 +1,42 @@
---
model: gpt-5.6-sol
provider: openai
service: opencode
session: 24fb9765-a550-4570-8350-f0fc9b7e17db
timestamp: 2026-07-28T00:32:47Z
git_ref: ed85721c
scope: code
substantive: true
raw_file: 20260728T003247Z_ed85721c_prompt_io.raw.md
---
## Prompt
Build a safe xonsh workflow for qualifying NativeDB and reverse-backfill
correctness against a known gappy IB chart, beginning with
`mnq.cme.20260918`. Cover baseline, fresh, restart, append, and expansion
testing through implementation, adversarial review, and verification.
## Response summary
Implemented a private disposable xonsh qualification workflow. Evidence is
tied to a fixed case, report checksum, snapshot checksum, worktree import,
and phase metadata. Added explicit path and symlink safety, source
replacement handling, xonsh child environments, and lifecycle preservation
comparisons.
## Files changed
- `snippets/nativedb_backfill_audit.xsh` - disposable qualification helpers
- `docs/manual_backfill_qualification.rst` - operator lifecycle runbook
- `tests/test_backfill_audit_snippet.py` - xonsh and comparison regressions
- `.claude/skills/run-tests/test-harness-reference.md`
- qualification test-harness guidance
- `ai/prompt-io/opencode/20260728T003247Z_ed85721c_prompt_io.raw.md`
- unedited response record
- `ai/prompt-io/opencode/20260728T003247Z_ed85721c_prompt_io.md`
- provenance metadata and response summary
## Human edits
None - generated changes have not been edited by the human.

View File

@ -0,0 +1,32 @@
---
model: gpt-5.6-sol
provider: openai
service: opencode
timestamp: 2026-07-28T00:32:47Z
git_ref: ed85721c
diff_cmd: git diff HEAD~1..HEAD
---
> `git diff HEAD~1..HEAD -- snippets/nativedb_backfill_audit.xsh docs/manual_backfill_qualification.rst tests/test_backfill_audit_snippet.py .claude/skills/run-tests/test-harness-reference.md`
Added sourceable xonsh helpers that create a private disposable root, bind
child processes to the reviewed worktree, capture checksum-bound phase
evidence, safely seed and clear test history, and compare lifecycle
preservation.
The workflow rejects path traversal, pre-existing symlinks, output
collisions, mismatched case/report/snapshot evidence, and same-phase
comparison. The accompanying runbook covers known-bad replay, fresh
backfill, restart, append, gap triage, and expansion cases.
Verification generated with the patch:
- xonsh qualification helper regressions: passed
- xonsh sourceability: passed
- Ruff: passed
- `git diff --check`: passed
Adversarial review drove additional evidence binding, worktree executable,
environment propagation, source replacement, and descriptor-level safety
fixes. Residual concurrent mutation risk is stated explicitly in the private
disposable-root runbook.

View File

@ -0,0 +1,40 @@
---
model: gpt-5.6-sol
provider: openai
service: opencode
session: 24fb9765-a550-4570-8350-f0fc9b7e17db
timestamp: 2026-07-28T00:50:34Z
git_ref: ed85721c
scope: code
substantive: true
raw_file: 20260728T005034Z_ed85721c_prompt_io.raw.md
---
## Prompt
Add reusable read-only tooling to qualify raw NativeDB Parquet against a
known gappy IB chart, beginning with `mnq.cme.20260918`. Preserve malformed
evidence and distinguish structural validity from unclassified positive
gaps without opening or mutating the storage runtime.
## Response summary
Implemented raw Parquet audit reporting with human and JSON output, bounded
cadence details, exact integer arithmetic, strict status, collision-safe
snapshot capture, and source path protections. Regressions cover malformed
schema and values, timestamp precision, gap arithmetic, corrupt bytes,
copy failures, symlinks, traversal, and read-only CLI behavior.
## Files changed
- `piker/storage/_audit.py` - raw Parquet audit and snapshot implementation
- `piker/storage/cli.py` - `piker store audit` command
- `tests/test_storage_audit.py` - audit and CLI regressions
- `ai/prompt-io/opencode/20260728T005034Z_ed85721c_prompt_io.raw.md`
- unedited response record
- `ai/prompt-io/opencode/20260728T005034Z_ed85721c_prompt_io.md`
- provenance metadata and response summary
## Human edits
None - generated changes have not been edited by the human.

View File

@ -0,0 +1,27 @@
---
model: gpt-5.6-sol
provider: openai
service: opencode
timestamp: 2026-07-28T00:50:34Z
git_ref: ed85721c
diff_cmd: git diff HEAD~1..HEAD
---
> `git diff HEAD~1..HEAD -- piker/storage/_audit.py piker/storage/cli.py tests/test_storage_audit.py`
Added a raw, read-only NativeDB Parquet audit with human and JSON output,
bounded cadence evidence, exact integer arithmetic, pre-parse snapshot
capture, and strict qualification status.
The command rejects unsafe FQME paths, symlinked sources and outputs,
destination collisions, non-regular source nodes, malformed canonical
schema, non-finite values, invalid timestamps, noncanonical indexes, and
sub-period cadence. Copy-failure cleanup preserves source bytes while
completed corrupt snapshots remain available for diagnosis.
Verification generated with the patch:
- storage audit and CLI regressions: passed
- Ruff: passed
- `git diff --check`: passed
- final adversarial review: no findings

View File

@ -0,0 +1,39 @@
---
model: gpt-5.6-sol
provider: openai
service: opencode
session: 24fb9765-a550-4570-8350-f0fc9b7e17db
timestamp: 2026-07-28T23:27:18Z
git_ref: ed85721c
scope: code
substantive: true
raw_file: 20260728T232718Z_ed85721c_prompt_io.raw.md
---
## Prompt
Diagnose the live `pikerd` failure where backfill publication raised
`ValueError: OHLCV timestamps must be finite and positive` while merging the
known gappy MNQ NativeDB baseline with fresh IB history. Determine whether to
commit the pending audit stack or iterate first.
## Response summary
Traced the primary failure through `publish_latest_frame()` to
`NativeStorageClient.update_ohlcv()`. The incoming frame was valid, but the
canonicalized persisted baseline retained its epoch-zero row and caused the
merged frame to fail validation. Added persisted-row repair before merge and
a regression using the observed legacy schema characteristics.
## Files changed
- `piker/storage/nativedb.py` - drop non-positive persisted rows during merge
- `tests/test_storage_nativedb.py` - live MNQ failure-shape regression
- `ai/prompt-io/opencode/20260728T232718Z_ed85721c_prompt_io.raw.md`
- unedited response record
- `ai/prompt-io/opencode/20260728T232718Z_ed85721c_prompt_io.md`
- provenance metadata and response summary
## Human edits
None - generated changes have not been edited by the human.

View File

@ -0,0 +1,27 @@
---
model: gpt-5.6-sol
provider: openai
service: opencode
timestamp: 2026-07-28T23:27:18Z
git_ref: ed85721c
diff_cmd: git diff HEAD~1..HEAD
---
> `git diff HEAD~1..HEAD -- piker/storage/nativedb.py tests/test_storage_nativedb.py`
Fixed incremental NativeDB updates against legacy persisted history with
non-positive timestamps. Incoming provider frames remain strictly validated,
while merge repair discards invalid persisted rows before concat, dedupe,
ordering, canonical-index rewrite, and atomic publication.
The regression reproduces the observed MNQ baseline shape: one epoch-zero
row, noncanonical absolute indexes, extra derived columns, and a valid fresh
IB frame. It proves the invalid row is removed while all valid old and new
bars survive with canonical indexes.
Verification generated with the patch:
- NativeDB regressions: 18 passed
- history backfill regressions: 6 passed
- focused Ruff: passed
- `git diff --check`: passed

View File

@ -0,0 +1,40 @@
---
model: gpt-5.6-sol
provider: openai
service: opencode
session: 24fb9765-a550-4570-8350-f0fc9b7e17db
timestamp: 2026-07-29T03:41:06Z
git_ref: 922a5df8
scope: code
substantive: true
raw_file: 20260729T034106Z_922a5df8_prompt_io.raw.md
---
## Prompt
Diagnose the live IB reverse-fill failure where
`reqHistoricalDataAsync()` raised API error 10314 because its end date,
time, or timezone was invalid.
## Response summary
Traced the failure to `Client.bars()`, where an unfinished EST conversion
left the original datetime for `ib_async` to serialize as
`YYYYMMDD HH:MM:SS UTC`. Converted aware boundaries explicitly to UTC and
passed IB's accepted `YYYYMMDD-HH:MM:SS` string while retaining a blank
latest-request boundary. Added exact request-argument regressions.
## Files changed
- `piker/brokers/ib/api.py` - explicit UTC-dash history boundary format
- `tests/test_ib_history.py` - IB request-format regressions
- `.claude/skills/run-tests/test-harness-reference.md`
- deterministic IB history test mapping
- `ai/prompt-io/opencode/20260729T034106Z_922a5df8_prompt_io.raw.md`
- unedited response record
- `ai/prompt-io/opencode/20260729T034106Z_922a5df8_prompt_io.md`
- provenance metadata and response summary
## Human edits
None - generated changes have not been edited by the human.

View File

@ -0,0 +1,25 @@
---
model: gpt-5.6-sol
provider: openai
service: opencode
timestamp: 2026-07-29T03:41:06Z
git_ref: 922a5df8
diff_cmd: git diff HEAD~1..HEAD
---
> `git diff HEAD~1..HEAD -- piker/brokers/ib/api.py tests/test_ib_history.py .claude/skills/run-tests/test-harness-reference.md`
Fixed IB reverse-history requests to serialize aware end boundaries with
IB's explicit UTC-dash syntax, `YYYYMMDD-HH:MM:SS`. This bypasses
`ib_async`'s trailing-`UTC` representation, which the live Gateway rejected
with API error 10314, and removes the unfinished EST comparison breakpoint.
The regression captures the exact `reqHistoricalDataAsync()` argument for
latest, stdlib-aware, Pendulum UTC, and America/New_York inputs.
Verification generated with the patch:
- IB history request regressions: 4 passed
- focused Ruff: passed
- `git diff --check`: passed
- adversarial review: no findings

View File

@ -0,0 +1,40 @@
---
model: gpt-5.6-sol
provider: openai
service: opencode
session: 24fb9765-a550-4570-8350-f0fc9b7e17db
timestamp: 2026-07-29T04:17:23Z
git_ref: ce33deb6
scope: code
substantive: true
raw_file: 20260729T041723Z_ce33deb6_prompt_io.raw.md
---
## Prompt
Diagnose why the chart still showed a zero first OHLCV row after the
epoch-zero timestamp repair, causing auto-y-ranging to include zero.
## Response summary
Audited live 60s and 1s NativeDB files and found one exact 60s first-slot
sentinel at epoch 60 with a completely zero OHLCV payload. Added narrowly
scoped read and merge repair, matching incoming validation, sentinel-only
startup handling, canonical reindexing, and explicit audit classification.
Preserved modern all-zero bars as unclassified evidence rather than assuming
all zero-priced instruments are corrupt.
## Files changed
- `piker/storage/nativedb.py` - first-slot sentinel repair and validation
- `piker/storage/_audit.py` - sentinel classification and zero-row evidence
- `tests/test_storage_nativedb.py` - hydration, merge, and boundary regressions
- `tests/test_storage_audit.py` - sentinel and modern-zero audit regressions
- `ai/prompt-io/opencode/20260729T041723Z_ce33deb6_prompt_io.raw.md`
- unedited response record
- `ai/prompt-io/opencode/20260729T041723Z_ce33deb6_prompt_io.md`
- provenance metadata and response summary
## Human edits
None - generated changes have not been edited by the human.

View File

@ -0,0 +1,31 @@
---
model: gpt-5.6-sol
provider: openai
service: opencode
timestamp: 2026-07-29T04:17:23Z
git_ref: ce33deb6
diff_cmd: git diff HEAD~1..HEAD
---
> `git diff HEAD~1..HEAD -- piker/storage/nativedb.py piker/storage/_audit.py tests/test_storage_nativedb.py tests/test_storage_audit.py`
Fixed the remaining legacy first-slot sentinel observed in live MNQ 60s
history: `time=60` with zero open, high, low, close, and volume. NativeDB now
repairs the exact sentinel before read hydration and merge, reindexes the
filtered view, and persists the repaired frame on the next valid update.
The repair remains narrowly scoped. Modern all-zero OHLC bars stay valid for
spreads or synthetic instruments, null-containing malformed rows still fail
validation, new writes can not recreate the first-slot sentinel, and a file
containing only sentinels loads as no history so fresh backfill can proceed.
The raw audit reports total all-zero price rows, classifies first-slot
sentinels as structural violations, and leaves later all-zero rows visible as
unclassified warnings.
Verification generated with the patch:
- NativeDB, audit, history, xonsh, and IB regressions: 61 passed
- focused Ruff: passed
- `git diff --check`: passed
- final adversarial review: no findings

View File

@ -0,0 +1,34 @@
---
model: openai/gpt-5.6-sol
service: opencode
session: 6409e3bb-1a9a-4ada-b6c4-c554115143e9
timestamp: 2026-07-29T18:54:06Z
git_ref: d359b1cb
scope: code
substantive: true
raw_file: 20260729T185406Z_d359b1cb_prompt_io.raw.md
---
## Prompt
The user reported that QQQ 60-second zeros appeared as a flat line after
reload, clarified that the outstanding concern was the prior `ldshm`
failure, and instructed the agent to implement the more focused fix.
## Response summary
Changed `ldshm` to classify invalid SHM snapshots before downstream
analysis. Cadence is inferred only from observed finite positive steps;
buffers containing non-finite or non-positive timestamps, or lacking a
positive cadence, are skipped rather than partially replacement-written
or reloaded. Added deterministic helper and command-level regressions.
## Files changed
- `piker/storage/cli.py` - inspect cadence and reject invalid SHM snapshots before mutation.
- `tests/test_ldshm.py` - cover QQQ-like zeros, corrupt timestamps, cadence selection, degenerate buffers, and mutation prevention.
- `.claude/skills/run-tests/test-harness-reference.md` - register the deterministic regression target.
## Human edits
None - generated output remains uncommitted.

View File

@ -0,0 +1,58 @@
---
model: openai/gpt-5.6-sol
service: opencode
timestamp: 2026-07-29T18:54:06Z
git_ref: d359b1cb
diff_cmd: git diff HEAD~1..HEAD
---
The user clarified that the QQQ 60-second chart now displayed a flat
forward-filled segment, then asked whether the previously reported
`piker store ldshm` failure had been fixed. After being told it had not,
the user instructed: "ok but i read above you have a more focussed fix
todo now? so have at it!"
The implementation focused on the exact failure boundary. It does not
filter invalid rows into a partial replacement and does not mutate live
SHM. Instead, `ldshm` inspects one captured Polars snapshot, infers its
cadence from observed finite positive timestamp steps, and skips the
entire buffer before analysis, persistence, or reload when any timestamp
is non-finite or non-positive.
> `git diff HEAD~1..HEAD -- piker/storage/cli.py`
Generated `_shm_period_and_invalid_count()` and integrated its result in
`ldshm`. The helper selects the most frequent observed positive step,
using the smallest observed step as the deterministic tie-break. Short
or cadence-degenerate buffers are skipped. Invalid snapshots are logged
and cannot reach deduplication, NativeDB replacement, or SHM reload.
> `git diff HEAD~1..HEAD -- tests/test_ldshm.py`
Generated deterministic regressions for the QQQ-shaped interior zero
run, observed-step cadence selection, all invalid timestamp classes,
short and degenerate buffers, and command-level prevention of dedupe,
storage writes, and reload. The command-level test uses an immutable
Polars snapshot and failing spies, without live SHM mutation or timing.
> `git diff HEAD~1..HEAD -- .claude/skills/run-tests/test-harness-reference.md`
Added the deterministic `ldshm` regression target and its change-to-test
mapping to the repository test harness reference.
Verification output:
```text
.......................................................... [100%]
58 passed in 0.70s
```
`git diff --check` passed. Ruff was unavailable in the worktree's
existing environment. Three adversarial review passes found and drove
fixes for partial-replacement data loss, invented median timeframes,
mixed SHM observations, and pre-guard null-analysis failures. The final
review reported no remaining findings.
The patch intentionally does not claim that `ldshm` is safe to run as a
general concurrent repair writer. It handles invalid live snapshots by
refusing mutation rather than weakening NativeDB validation.

Some files were not shown because too many files have changed in this diff Show More