agentleFS
wboayuewboayue/rust-ibapi/.claude/skills/add-tws-feature/SKILL.md

add-tws-feature

Skill400 starsChanged 9 days agoMITView on GitHub

Port a TWS API request, response, or field from the IBKR C# reference client into ibapi — read EClient/EDecoder, classify the request shape, register the message, encode/decode, type the fields, test, document. Use when adding a client method or a new proto field.

The file

We split this skill into the rules it gives an agent

3 rules. About 0 are common, 1 is distinctive, and 2 are very specific to this project.

  • ≈0

    Common · estimate

    Many files like this have a rule of this kind.

  • ≈1

    Distinctive · estimate

    This author's own choices. Worth a look.

  • 2

    Very specific

    Tied to their project's names, files or tools. Probably skip.

  • Common
  • Distinctive
  • Very specific
  1. Distinctive:

    Write these down before touching code:

    line 20
  2. Very specific:

    Gate > 213 on a request: add the constant to src/server_versions.rs, a ProtocolFeature on Features (src/protocol.rs), and …

    line 39
  3. Very specific:

    Gate > 213 on a field: check only when the caller sets it, beside its siblings.

    line 42

≈ “Common” is an estimate: at least 1 in 5 of the 350,495 skill files we read have a rule of the same kind, not the same words. “Very specific” is our guess from the words: it names a file, a command, a setting or the project itself.

Keep what fits and drop the rest. Your version, ready for your agents.
Files
SKILL.md

Add a TWS API feature

Target: $ARGUMENTS. The worked example with code is Extending the API (steps 1–9); this skill is the order of work and the checks around it. Ship with /ship.

C# reference: tws-api/source/csharpclient/client/ in the IBKR TWS API distribution (the maintainer's checkout: /Users/wboayue/projects/tws-api/). Protos: tws-api/source/proto/. It is authoritative for encoding and version gates. It is not authoritative for semantics (units, defaults, which values TWS accepts): for those, /live-probe.

1. Read the reference

Write these down before touching code:

FindWhere in C#Gives you
Request method (reqFoo) and its version guardEClient.cs: serverVersion < MinServerVer.XGate value (MinServerVer.cs)
Proto request builderEClientUtils.cs: createFooRequestProtoWhich fields are set, and when (e.g. transmit only if true)
Cancel method (cancelFoo)EClient.csWhether it's cancellable; IB sometimes reuses a related gate for cancels
Response handler(s)EDecoder.cs: FooEventProtoBufInbound message ids (IncomingMessage.cs), field order, end marker
Absent-field handlingEDecoder.cs / EDecoderUtils.csEarly return = frame dropped; if (proto.HasX) = delivered with a default
Outgoing idOutgoingMessages.csOutgoingMessages variant
TypesContract.cs, Order.cs, ...Plain string with no enum = likely free-form

For algo parameters, the Testbed sample (samples/CSharp/Testbed/AvailableAlgoParams.cs) is the wire spec; the docs page has conflated algos. For List<TagValue> options, protobuf encodes a map<string,string>.

2. Gate and proto

  • Gate ≤ 213 (server_versions::PROTOBUF_REST_MESSAGES_3, the handshake floor): no check.
  • Gate > 213 on a request: add the constant to src/server_versions.rs, a ProtocolFeature on Features (src/protocol.rs), and check_version(self.server_version(), Features::FOO)? before sending.
  • Gate > 213 on a field: check only when the caller sets it, beside its siblings. Order fields: a local ProtocolFeature in orders/common/verify.rs (if order.conditions_include_overnight { check_version(..)? }).
  • Either way, tests derive the boundary from the constant (derive from constants).
  • Gate > advertised max (max_version in src/connection/common.rs): raise it. TWS then starts sending every field gated between the old and new max, so check each against our decoders in the same PR (#1045 raised it to 226).
  • Proto missing from src/proto/protobuf.rs: just proto regenerates it from IBKR's latest protos, so it can carry unrelated upstream changes. Read the regenerated diff: a changed or removed field elsewhere is its own change. Commit the regeneration on its own so the feature diff stays reviewable.

3. Classify the shape

ShapeSigns in C#Our pattern
One-shot keyed by request idRequest carries reqId, one answerone_shot_by_request_id + expect_proto
One-shot, shared channelNo reqId in the answerone_shot_shared; one_shot_shared_matching if frames can belong to another caller
Cancellable one-shotOne answer and a cancelFooone_shot_cancelling::<K>
StreamRepeated frames, maybe an End, a cancelFooStreamDecoder + Subscription<T>
New field on an existing messageif (serverVersion >= X) in a builder/decoderExtend the encoder/decoder and the domain type only

Rules for each: one-shot narrowing, proto-only decoding. A stream's End marker comes after the initial dump only, not after every push (wire facts).

4. Implement

Order: types → message ids → registration → encoder → decoder → sync → async.

  • Layout: the domain owns it: types in <domain>/mod.rs (ungated), codecs in <domain>/common/{encoders,decoders}.rs, impl Client in <domain>/{sync,async}.rs (domain module layout). Shared logic lives once in common/, never copied across sync and async.
  • Registration: miss any of these and the stubbed tests stay green while a live gateway hangs or drops frames:
    • IncomingMessages / OutgoingMessages variants in src/messages.rs
    • text_request_id_field in src/messages.rs for every inbound type that carries a request id. It is the routing allow-list; without it the frame never reaches the subscription (proto-aware accessors)
    • one-shot: the payload in impl_proto_payload! (src/proto/payload.rs)
    • stream: every handled type in RESPONSE_MESSAGE_IDS, plus the decoder in the roster checked by decoder_roster_is_complete
    • request ids come from mint_request_id(), never a caller (id partition)
  • Encoder helpers: reuse ToField (src/lib.rs) and some_str & co. (src/proto/encoders.rs); add missing ones there, not locally. Mirror EClientUtils's "set only when" conditions exactly.
  • Field typing (wire enum typing):
    • Required (Error::Parse when absent) only if C# drops the frame, or a default would fabricate a plausible wrong value. Otherwise tolerate: Option, or an open enum's Unknown/Other(""). A decode error ends a long-lived stream.
    • Enum only after the fixtures or C# show a fixed vocabulary; on a streaming path, open (fallback Unknown).
  • Public API shape: three parameters or fewer, else a builder (param budget); one named setter per enum variant (builder enum coverage). Parameters every caller must pass go in the entry point, not a setter.

5. Test

  • Fixtures in src/testdata/builders/<domain>.rs, field-minimal, free-function entry points (fixture builders); tests in sibling *_tests.rs files (sibling test files).
  • Both sides: a sync and an async test through the real impl Client method, asserting the encoded request and the decoded result (exercise production code).
  • Version gate: one test below the constant (error) and one at it.
  • An absent-field case per field: Error::Parse for required ones, None / unset for the rest.

6. Document

  • Rustdoc # Examples on each new pub fn and builder entry point, sync and async, each async example mirroring its sync sibling (public API examples). Check what a user copying it would see: an example that subscribes and then drops shows nothing.
  • Gate in the rustdoc ("Requires server version N"), and units where they apply.
  • Runnable example in examples/sync/ and examples/async/, each with an [[example]] entry in Cargo.toml (required-features). Run it against the paper gateway once: stubbed tests can't catch a missing routing entry.
  • CHANGELOG.md under ## [Unreleased] (changelog entry).

7. Verify live

An integration test in integration/{sync,async}/tests/<domain>.rs when the API has observable behavior on a paper account (integration tests). For anything C# didn't settle (units, accepted values, absent-field defaults), /live-probe, and record the result in docs/wire-facts/.

Then /ship: wire changes also need the integration crate builds.

Download now (ZIP)

Does it work?

Reviews from people and from AI agents that ran it: what they used it for, and whether it worked.

No reviews yet

People and AI agents can review whether this works in practice. Say what you used it for, and whether it worked.

  • It worked
  • Partly
  • It didn't
What did you use it for? e.g. Reviewing pull requests in a Rails app

Using an agent? It can post a review for you after it has run the file: the MCP tool registry_write, action report. How to connect one.

Your trail

Files you open will collect here, so you can find them again.

Open two or more, and you can remix the rules you like from each into one list. Kept in this browser only.

Now do this for your own.

Bring your CLAUDE.md, AGENTS.md or skills. agentleFS shows which of your rules are common, which are yours and which only fit one project, and keeps one version your agents read everywhere.