I wanted to contribute where I did not control the architecture
I spend most of my engineering time building systems where I understand the history because I helped create it. Open source removes that advantage. You arrive after decisions have already been made. The interfaces have users. The maintainers have expectations. There is repository history you have not lived through, and the most useful contribution is rarely the first change that comes to mind.
That is one reason I have been deliberately contributing upstream. I wanted more practice entering unfamiliar production code, reading until I could explain the contract, finding a narrow place where I could add value, and then proving that my change respected everything around it.
Two recent contributions gave me exactly that experience in very different domains. NautilusTrader is a Rust-native trading engine designed for research, simulation and live execution. SignrrGPT is an AI-powered sign-language project created by Anthony Okeh (@Okeha1810 on X) with the goal of making AI more useful and accessible to Native Signers.
The domains are different, but the work reinforced the same engineering habits for me: understand the invariant before changing the implementation, make performance work measurable, preserve semantics while optimizing, treat failure paths as part of the feature, and write tests around the exact boundary that motivated the contribution.
NautilusTrader: contributing inside a production-grade trading engine
NautilusTrader describes itself as an open-source, production-grade, Rust-native engine for multi-asset, multi-venue trading systems. Its architecture is built to support research, deterministic simulation and live trading from the same system, with a high-performance Rust core exposed to Python through PyO3.
That context matters. Trading infrastructure is a place where correctness has a very literal meaning. A public configuration that claims to be valid has to remain valid all the way down to the internal data structures. An indicator that produces a different value at an accepted boundary is not merely an implementation curiosity; it can change what a strategy sees.
I was reading through the Rust implementation of the Aroon oscillator when I noticed an inconsistency between the public maximum period and the amount of history the internal buffers could actually retain. The constructor accepted MAX_PERIOD = 1024, but Aroon requires period + 1 observations to initialize and calculate the window correctly.
public maximum period = 1024
required Aroon window = 1024 + 1 = 1025
internal deque capacity = 1024
That meant the maximum public value required 1,025 highs and 1,025 lows while the wrapping deques could keep only 1,024.
I reproduced the boundary before I proposed the fix
I did not want to open an issue based only on reading the types. The important question was whether the capacity mismatch could affect the actual indicator value.
I reproduced the behavior against the published nautilus_trader==2.0.0rc5 wheel. I used an input where the oldest observation contained the unique highest high. At the moment a 1,024-period Aroon oscillator first has enough history, that original high should still be exactly 1,024 periods back.
expected at initialization
aroon_up = 0.0
aroon_down = 100.0
value = -100.0
observed before the fix
aroon_up = 100.0
aroon_down = 100.0
value = 0.0
The wrapping deque had already displaced the oldest high. The remaining equal highs caused the newest high to become the effective maximum, which changed Aroon Up from 0 to 100 and the oscillator from -100 to 0.
I documented the full reproduction, the internal invariant, the published-wheel behavior and the possible fix directions in the issue below.
The implementation was small. The evidence around it was the contribution.
The fix itself was intentionally narrow: introduce an internal MAX_CAPACITY = MAX_PERIOD + 1 and give the high and low deques enough storage to honor the already-supported public maximum.
pub const MAX_PERIOD: usize = 1_024;
const MAX_CAPACITY: usize = MAX_PERIOD + 1;
high_inputs: ArrayDeque,
low_inputs: ArrayDeque,
The larger part of the work was proving the change. I added Rust and Python regression coverage for both sides of the extrema boundary: one case where the oldest observation is the unique highest high and another where it is the unique lowest low. The tests verify exact initialization after MAX_PERIOD + 1 observations, correct Aroon Up and Down values, the final oscillator value, and rollover on the next observation.
I also searched the repository history before opening the PR. An earlier contribution, PR #5022, had already explored the same capacity direction but had been closed without merge. Referencing it mattered. Upstream work is not just about producing the right diff; it is also about understanding what has already been tried and carrying the context forward.
The final PR passed formatting and pre-commit checks, the affected Rust indicator suite and the full Python test suite. It was reviewed, approved and merged upstream on 21 September 2026. That was a satisfying result, but the more useful takeaway for me was the process: trace a public promise into an internal invariant, reproduce the user-visible consequence, and make the smallest change that restores the promise.
The merged correctness fix also led me to a separate performance RFC
While tracing the Aroon implementation, I noticed a second question that was not part of the correctness fix. Once AroonOscillator is initialized, every update scans the full high window to find the current maximum and then scans the full low window to find the current minimum.
For a period p, that makes each initialized update O(p). Across N observations, the streaming work trends toward O(Np), while storage remains O(p).
current implementation
single initialized update: O(p)
N observations: O(Np)
storage: O(p)
proposed rolling extrema
single initialized update: O(1) amortized
N observations: O(N)
storage: O(p)
In RFC #4996, I proposed maintaining one monotonic deque for highs and another for lows. Each new observation would expire candidates that have left the period + 1 window, remove dominated candidates from the back, insert the new value with its sequence position, and read the current extreme from the front. Because each observation enters and leaves a deque at most once, the extrema maintenance becomes amortized constant time.
The interesting part is not the Big O by itself. The current Aroon implementation scans from newest to oldest and updates its selected extreme only on a strict comparison. That means equal highs or equal lows resolve to the most recent occurrence. Any monotonic-deque implementation has to preserve that exact tie behavior, as well as initialization at exactly period + 1, eviction at the window boundary, reset behavior and rollover.
I also made the RFC deliberately benchmark-driven. The current implementation is simple and easy to audit, while monotonic deques add more state and more edge cases. My proposal is to benchmark representative period sizes first and only implement the optimization if the measured improvement justifies that added complexity.
I have not implemented the RFC yet. It remains open, and I explicitly asked maintainer @cjdsellers to confirm whether the monotonic-deque direction is something they would be interested in reviewing before I start benchmarking or implementation. As of 27 September 2026, I am still waiting for maintainer feedback. That pause is intentional: for a substantial optimization in someone else’s system, alignment should come before code.
SignrrGPT: contributing to a project whose accessibility goal is bigger than the code
My SignrrGPT contribution started differently. Anthony Okeh, the project’s original creator, posted publicly asking technical people to contribute to an open-source project intended to make AI more accessible to Native Signers. That caught my attention because the problem is immediately human: communication systems are often designed around people who can speak, hear or type comfortably, while sign language remains underserved by mainstream interfaces.
You can read Anthony’s original contributor call on X and visit his GitHub profile. I also referenced this earlier SignrrGPT post from his X account while learning the context around the project.
SignrrGPT is working toward bi-directional sign-language interaction: translating signing into text and translating text back into signing through a 3D avatar. The public project spans a React/TypeScript/Three.js frontend and a Python/FastAPI backend using VideoMAE for video understanding.
What interested me as a backend engineer was the infrastructure around the model. Real-time AI products are shaped by much more than model accuracy. Upload memory, JPEG decode work, session continuity, cleanup, failure behavior and temporal sampling all affect whether the product can remain responsive and dependable as usage grows.
I reviewed the backend and opened three focused issues with corresponding pull requests. I think of them as contributions to make an already meaningful project more efficient and more consistent, not as a list of things that were “wrong” with it.
The three SignrrGPT backend contributions
1. Bounding memory during uploaded-video inference
The video-upload path originally materialized the whole compressed upload and every decoded RGB frame before inference. VideoMAE ultimately needs only a small uniformly sampled sequence, so retaining the whole video made memory scale with video size and decoded frame count instead of the model’s fixed sample requirement.
before
peak memory ≈ O(B + F × P)
after
peak memory ≈ O(upload_chunk_size + K × P)
B = compressed upload bytes
F = decoded frame count
P = RGB bytes per frame
K = model sample count
My PR streams the upload to temporary storage in bounded chunks, counts decodable frames without retaining every RGB array, then keeps only the exact uniform positions required for inference. I chose a sequential two-pass approach instead of depending on container frame-count metadata or random seeking because those can vary across codecs and files.
The contribution also preserves short-video repeat semantics, releases every VideoCapture in finally paths, keeps temporary-file cleanup, and preserves intended HTTP 400 responses instead of allowing them to be wrapped as generic 500s.
2. Restoring session continuity for cloud non-streaming chat
The cloud chat implementation already read prior history for a session_id, but the successful non-streaming path did not append the new user message and assistant response back into session memory. I contributed the missing write-back so the next request receives the completed previous turn as context.
response = client.chat.completions.create(...)
assistant_response = response.choices[0].message.content
if session_id:
chat_memory.add_message(session_id, "user", user_message)
chat_memory.add_message(session_id, "assistant", assistant_response)
return assistant_response
The ordering is deliberate: persistence happens only after the provider returns successfully. If the provider fails, the session does not end up with a user message that looks like a completed turn without its assistant response. Requests without a session ID remain stateless.
I added mocked regression coverage for successful persistence, second-turn history ordering, stateless requests and provider failure. That lets the behavior be tested without making network calls to the cloud model provider.
3. Sampling VideoMAE frames before JPEG decoding
The real-time frame-batch path can receive up to 120 encoded JPEG frames while the model samples 16 by default. The previous flow decoded the full batch first and sampled afterward. My contribution moves the existing uniform sampling decision in front of the expensive base64/JPEG/RGB decode stage.
maximum request batch = 120 frames
default model sample = 16 frames
before: decode 120 → sample 16
after: sample indexes → decode 16
maximum-case decode work: 120 / 16 = 7.5× less
The important part was preserving behavior. The new helper uses the same np.linspace(...).astype(int) index policy as the existing implementation. When an input contains fewer than 16 frames and the sample sequence repeats positions, each unique source JPEG is decoded once and the sampled sequence reuses the decoded frame reference.
Regression tests cover 1, 8, 16, 60 and 120-frame inputs, prove that unselected images are not decoded, preserve the processor tensor shape (1, 16, 3, 224, 224), and use a deterministic fixture to check prediction parity. At the 120-frame limit, the expensive image-decode stage falls from 120 images to 16 while the model still receives the same temporal positions.
Across the three SignrrGPT contributions I added 15 focused regression tests around the changed behavior. As I publish this, all three pull requests are open for upstream review.
What working across both systems reinforced for me
Public limits are engineering promises. The NautilusTrader contribution reminded me that input validation and storage capacity cannot be reasoned about separately. If a constructor accepts a boundary, every layer beneath it has to support the state that boundary requires.
Asymptotic improvement is not enough on its own. RFC #4996 is a good reminder that O(Np) → O(N) looks attractive on paper, but the simpler implementation may still be the better engineering choice if representative benchmarks do not justify the additional state and edge cases.
Performance work needs a behavioral contract. “Decode fewer images” sounds straightforward until an optimization changes the temporal sequence presented to a video model. The safe version starts by identifying the semantics that must not change, then proves that the faster path produces the same sequence.
Memory complexity becomes a concurrency problem very quickly. Keeping every decoded video frame can look harmless during a single local request. With larger videos and concurrent users, the cost multiplies. Bounding memory around the actual inference requirement gives the service a much more predictable operating shape.
Failure paths are product behavior. A failed provider call should not leave a half-complete chat turn. A client error should remain a client error. File handles, video captures and temporary files have to be released even when processing fails. Those paths are not cleanup trivia; they decide how a backend behaves under pressure.
Repository history and maintainer alignment are part of the code. The earlier NautilusTrader PR taught me to search before claiming novelty, and the open RFC reinforced that a substantial design change should wait for maintainer direction rather than becoming an unsolicited implementation. SignrrGPT taught the same lesson in another form: the umbrella repository was not where these changes belonged, so I moved the issues and PRs to the backend repository that actually owns the implementation.
A good contribution is larger than its diff. Sometimes the final code change is small. The engineering contribution includes the reproduction, explanation, scope control, regression tests, validation, links to prior work and enough context for a maintainer to review it without reverse-engineering my reasoning.
What I am taking forward from these contributions
I like that these two contributions sit so far apart. One lives in quantitative trading infrastructure where a single missing observation at an accepted boundary changed an indicator value. The other lives in an accessibility-oriented AI project where efficient video handling and reliable conversational state help support a much larger goal: making AI interfaces more useful to people whose primary language is visual.
Both forced me to do the thing I wanted from open-source work in the first place: enter someone else’s system without the comfort of authorship, understand it well enough to make a narrow change, and leave behind evidence that the change belongs there.
The NautilusTrader correctness contribution is merged. The separate Aroon performance RFC is open and waiting for maintainer feedback before I benchmark or implement it. The SignrrGPT contributions are in review. I am proud of the code, but what I value more is the engineering muscle behind it: reading deeply, respecting existing architecture, reasoning about boundaries, measuring costs, preserving semantics and making the reviewer's job easier.
That is the kind of open-source contribution I want to keep doing.
Sources and further reading
- NautilusTrader documentation
- NautilusTrader issue #4995: AroonOscillator MAX_PERIOD window
- NautilusTrader PR #5037: merged AroonOscillator capacity contribution
- NautilusTrader RFC #4996: amortized O(1) rolling extrema proposal
- NautilusTrader PR #5022: earlier related contribution
- SignrrGPT umbrella repository
- Anthony Okeh on GitHub
- Anthony Okeh (@Okeha1810) on X
- SignrrGPT post by Anthony Okeh on X
- Original SignrrGPT contributor call on X
- SignrrGPT backend issue #1: bounded upload inference memory
- SignrrGPT backend PR #4: bounded-memory video upload inference
- SignrrGPT backend issue #2: cloud chat session persistence
- SignrrGPT backend PR #5: restore cloud chat memory
- SignrrGPT backend issue #3: sample before JPEG decode
- SignrrGPT backend PR #6: VideoMAE sample-before-decode optimization
- TensorFlow: How AI is revolutionizing sign language recognition
