Skip to content

add AGENTS.md - #2366

Merged
plebhash merged 5 commits into
stratum-mining:mainfrom
plebhash:2026-09-08-agents-md
Sep 9, 2026
Merged

plebhash merged 5 commits into
stratum-mining:mainfrom
plebhash:2026-09-08-agents-md

Conversation

@plebhash

@plebhash plebhash commented Sep 8, 2026

Copy link
Copy Markdown
Member

No description provided.

@plebhash
plebhash force-pushed the 2026-09-08-agents-md branch 4 times, most recently from 33e3d8d to 49e0750 Compare September 9, 2026 00:45
Comment thread AGENTS.md
- conceptual (taking issues and other related PRs into consideration)
- commit history

While listing findings, for each finding, give me a draft comment and the file/line where it would be appropriate to drop it. Also mention the finding severity, and whether you believe it's a blocker or not. This is deliberately designed to keep human reviewers on the loop, as opposed to blindly copypasting a huge "clanker review" body of text without ever looking into what each finding means.

@plebhash plebhash Sep 9, 2026 •

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

example within the context of #2319

it turns this (which would have been blindly copy-pasted as a big "clanker review" comment):

Findings

  1. Interop: mining.extranonce.subscribe now rejects params it used to ignore. I verified that "params": null fails with ValueNotAnArray and ["x"] with WrongArgs. Before the PR the method accepted anything. The companion turns that parse error into a disconnect. Known ASIC firmware sends [], so it is probably fine in practice, but strictness buys nothing here. Suggest ignoring params for this method.
  2. Monitorability side effect: rejections decided inside the trait are invisible to the implementer. Unauthorized workers get code 24 and extranonce2-size or version-bits failures get code 20 straight from the default method, and handle_submit is never called for them. Before, these surfaced as Err(InvalidSubmission) the app could log or count, and that variant is no longer produced by the trait. Worth a hook or an explicit note in the trait docs. The same change means unauthenticated submit spam now gets a response per message instead of an error the app could act on. That matches pool behavior, and rate limiting belongs to the app, but it moves the decision.
  3. Low: JsonRpcError still requires exactly three elements. [20, "msg"] and string codes fail the whole Message parse. Not a regression, since no array form parsed before, and only IsClient users are affected. The in-tree example is the only one.
  4. Low: bit-aloo's finding 2 remains. An IsClient that sends mining.extranonce.subscribe gets UnknownID on the ack. Nothing in-tree sends it.
  5. Nits. The len - upstream_prefix_len subtraction is repeated inline in three channels, a local_len() on the prefix would remove it. AllocatedExtranoncePrefix::set_upstream_prefix is pub but only allocator tests use it, the companion goes through the channel setters. Client StandardChannel::set_upstream_extranonce_prefix has no test. The allocator setter takes Vec<u8> while the prefix setters take &[u8].

into a more refined list of review findings, with file/line and severity, along with the clanker comment:

Findings

1. mining.extranonce.subscribe rejects params it used to ignore.
Severity: low. Blocker: no. Verified failure modes, but no known firmware sends anything other than [], so the impact is speculative. The fix is one line and strictly safer.
Anchor: sv1/src/methods/client_to_server.rs:144

this tightens `mining.extranonce.subscribe` parsing: before this PR the method was accepted regardless of `params`, now `"params": null` fails with `ValueNotAnArray` and any non-empty array fails with `WrongArgs`. in the companion (sv2-apps#823, `sv1_server/mod.rs`) a `Client2Server::try_from` error becomes a `TproxyError::disconnect`, so a miner that sends anything other than `[]` here gets dropped instead of subscribed.

the NiceHash extension defines no params and cgminer-derived firmware sends `[]`, so this is probably fine in practice, but strictness buys nothing on a method whose only payload is the id. suggestion: keep the id and ignore `params` entirely, matching the pre-PR behavior.

2. Trait-level submit rejections are invisible to the implementer.
Severity: medium. Blocker: no, but decide before merge. It removes observability the implementer had and leaves a dead error variant, and the pending major bump is the cheap moment to add a hook.
Anchor: sv1/src/lib.rs:157, range 157 to 173

side effect worth a deliberate call: the unauthorized-worker (24) and extranonce2-size / version-bits (20) rejections are now decided and answered inside the default method, and `handle_submit` is never invoked for them. before this PR they surfaced as `Err(Error::InvalidSubmission)`, which the implementer could log, count, or use to disconnect. now the implementer has no visibility at all, so tProxy cannot include these in its share metrics. `Error::InvalidSubmission` is also no longer produced anywhere in the crate, only its definition and `Display` arm remain in `error.rs`.

answering with a proper sv1 response instead of a transport error is the right call, the observability just should not go away silently. suggestion: at minimum say in the `handle_submit` rustdoc which rejections never reach it, and drop the dead `Error::InvalidSubmission` variant. if tProxy wants to count them, a `on_submit_rejected(client_id, &submit, SubmitError)` hook with a no-op default is the smallest addition, and this major bump is the cheap moment to add it.

3. JsonRpcError accepts only 3-element arrays.
Severity: low. Blocker: no. Not a regression, since the old struct form parsed no array at all, and only IsClient users are affected.
Anchor: sv1/src/json_rpc.rs:140

`Legacy` only matches a 3-tuple, so `"error": [20, "Other/Unknown"]` fails the whole `Message` parse with "did not match any variant of untagged enum MessageWire" (a string code like `["20", ...]` fails the same way). not a regression, the derived struct form never parsed arrays at all, and only `IsClient` users are affected, of which the in-tree example is the only one. flagging it because the module docs at the top of `lib.rs` describe the error as a "list (error code, error message)", i.e. two elements.

suggestion (fine as a follow-up): deserialize the legacy form as `Vec<serde_json::Value>` and accept 2 or 3 elements, with `data` as `None` when absent.

4. IsClient cannot pair the new subscribe ack.
Severity: low. Blocker: no. Latent, nothing in-tree sends the request.
Anchor: sv1/src/methods/client_to_server.rs:129

minor asymmetry: this `From` impl gives a client a way to send `mining.extranonce.subscribe`, but the `result: true` ack the server now returns parses as a `GeneralResponse`, and `IsClient::update_response` maps an id that is neither authorize nor submit to `Error::UnknownID`. nothing in-tree sends this request, so it is latent rather than a bug. suggestion: a one-line note on this struct that `IsClient` does not track the id, or leave it until a client needs it.

5a. len - upstream_prefix_len repeated in three channels.
Severity: nit. Blocker: no.
Anchor: sv2/channels-sv2/src/extranonce_manager/prefix.rs:204

nit: `len - upstream_prefix_len` is the locally owned tail, and the same subtraction is repeated inline in the three channel setters (`client/extended.rs:222`, `server/extended.rs:371`, `server/standard.rs:332`). a `local_len()` accessor next to `upstream_prefix_len()` would let the channels write `upstream_prefix.len() + self.extranonce_prefix.local_len() + rollable` and keep the invariant in one place.

5b. AllocatedExtranoncePrefix::set_upstream_prefix is pub with no caller.
Severity: nit. Blocker: no.
Anchor: sv2/channels-sv2/src/extranonce_manager/prefix.rs:293

nit: this is `pub`, but the only callers are the allocator tests. the companion updates prefixes through the channel setters, and server channels take ownership of the `AllocatedExtranoncePrefix` at construction, so there is no window to call it afterwards. `pub(crate)` unless there is a planned external user.

5c. Client standard setter has no test.
Severity: low. Blocker: no. The method is a one-line delegate with an error mapping, so the risk is small.
Anchor: sv2/channels-sv2/src/client/standard.rs:161

nit: this is the only one of the four new channel setters without a test. the other three assert the preserved suffix, the allocation count and the transactional failure; a copy of `set_upstream_extranonce_prefix_enforces_full_extranonce_size_transactionally` from `client/extended.rs`, adapted to `allocate_standard`, would close the gap.

5d. Allocator setter takes Vec<u8>, siblings take &[u8].
Severity: nit. Blocker: no.
Anchor: sv2/channels-sv2/src/extranonce_manager/allocator.rs:232

nit: the allocator takes `Vec<u8>` while every other `set_upstream_prefix` / `set_upstream_extranonce_prefix` takes `&[u8]`, so the companion clones the same bytes once for the allocator and borrows them for each channel. taking `&[u8]` here too and copying internally keeps the four signatures uniform.

6. Two prefix-update paths with different lease guarantees, undocumented.
Severity: low. Blocker: no, optional. Documentation only, and #2330 is agreed to be a separate PR. Skip it if you consider your #2330 comment enough.
Anchor: sv2/channels-sv2/src/client/extended.rs:191

not asking to solve #2330 here (agreed it is a separate PR), but with this PR the crate ships two prefix-update paths with different guarantees: this one keeps the promise that pre-rotation jobs stay valid while dropping the old lease on line 206 (the #2330 part-1 hazard), and `set_upstream_extranonce_prefix` below keeps the lease attached. suggestion: a one-line pointer in this doc saying that callers with live jobs under an allocator-minted prefix should prefer `set_upstream_extranonce_prefix`, so the two are not picked interchangeably until #2330 lands.

@plebhash plebhash Sep 9, 2026 •

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

I think this is good because it forces me to stay on the loop during agentic reviews.

It's arguably slower than the lazy alternative of blindly copy-pasting a big "clanker review" text. And not necessarily a silver bullet, because I'm still going to copypaste a bunch of smaller "clanker review" comments and I'm not necessarily understand 100% of what they mean.

But at least I'm forced to think about it a little bit as a human reviewer, and potentially catch some hallucinations and blindspots, or refine low-quality clanker findings before they're fed back into the PR author.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Completely agree with this direction 👍

@plebhash
plebhash force-pushed the 2026-09-08-agents-md branch from 49e0750 to 99bb018 Compare September 9, 2026 01:32

@bit-aloo bit-aloo left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

LGTM

The commit step linked a single writing-style post and left the rest of
the project's commit practice undocumented. Enumerate it: a progressive
history with clear separation of concerns, review findings folded into
the commit that introduced them instead of stacked as follow-ups, a
rationale in every message, the issue URL when a commit closes one, and
a GPG signature where the contributor can produce one. Name
conventionalcommits.org alongside the style post, so the structural half
of the convention is written down too.
Steps 4, 5 and 7 kept the bold title and the body on the same line,
while the rest of the list gave the title its own line with the body
indented underneath. Adopt that majority style throughout, so the seven
steps scan as a list of titles rather than a wall of prose.

Drop the trailing colons the merged titles needed, since a title alone
on its line no longer introduces anything, and strip the trailing
whitespace those lines carried.
The workflow went straight from branching to committing, so where
Rustdocs and prose updates belong was never stated, while a whole
numbered step was spent restating what cargo test, clippy and fmt each
do. Add a "Make Your Changes" step covering all three: Rustdocs for
touched Rust code, a check that the .md files still describe the
feature, and a run of the cargo commands. Drop the old step, whose
content is now one bullet.
Coding agents otherwise start from their own defaults and each
contributor's private prompt file, so the same corrections get repeated
on every agent-assisted PR. Commit the conventions that belong to the
repository rather than to one contributor: the companion-PR coordination
with sv2-apps that the Integration Tests workflow enforces, the
pre-mined share rule in channels_sv2 tests, what to surface when
patching bugs, and the ponytail rules. Personal guidance stays out of
the repository in AGENTS_CUSTOM.md.
Claude Code looks for CLAUDE.md and does not read AGENTS.md, so
contributors using it would otherwise keep a private copy that drifts
from the committed conventions. A symlink points that harness at the
same file instead of a second one to maintain.
@plebhash
plebhash force-pushed the 2026-09-08-agents-md branch from 99bb018 to ee9b21f Compare September 9, 2026 12:51
@plebhash
plebhash merged commit fea42ee into stratum-mining:main Sep 9, 2026
14 checks passed
@plebhash
plebhash deleted the 2026-09-08-agents-md branch September 9, 2026 13:05
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants