<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Sourav Nandy</title>
    <description>The latest articles on DEV Community by Sourav Nandy (@sourav-nandy).</description>
    <link>https://dev.to/sourav-nandy</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F4029140%2F440a3da9-390e-4960-aa2d-01975e24ea2a.png</url>
      <title>DEV Community: Sourav Nandy</title>
      <link>https://dev.to/sourav-nandy</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/sourav-nandy"/>
    <language>en</language>
    <item>
      <title>Your Compute-Usage Receipt Can Be Cryptographically Signed and Still Be Forged. Here's the One Field That Actually Prevents It, From the Command Line.</title>
      <dc:creator>Sourav Nandy</dc:creator>
      <pubDate>Sat, 29 Aug 2026 17:36:17 +0000</pubDate>
      <link>https://dev.to/sourav-nandy/your-compute-usage-receipt-can-be-cryptographically-signed-and-still-be-forged-heres-the-one-3b1o</link>
      <guid>https://dev.to/sourav-nandy/your-compute-usage-receipt-can-be-cryptographically-signed-and-still-be-forged-heres-the-one-3b1o</guid>
      <description>&lt;p&gt;&lt;em&gt;Co-authored by &lt;a href="https://github.com/RudrenduPaul" rel="noopener noreferrer"&gt;Rudrendu Paul&lt;/a&gt; and &lt;a href="https://github.com/Sourav-nandy-ai" rel="noopener noreferrer"&gt;Sourav Nandy&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;Repo: &lt;a href="https://github.com/RudrenduPaul/ComputeLedger" rel="noopener noreferrer"&gt;github.com/RudrenduPaul/ComputeLedger&lt;/a&gt;, Apache-2.0. &lt;code&gt;npm install -g computeledger-cli&lt;/code&gt; (Node.js 18+) or &lt;code&gt;pip install computeledger-cli&lt;/code&gt; (Python 3.10+), two independently maintained implementations of the same receipt format, both live at &lt;code&gt;0.1.2&lt;/code&gt; (npm) and &lt;code&gt;0.1.4&lt;/code&gt; (PyPI) on their respective registries as of this writing; the two implementations now version independently rather than staying in lockstep.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Quick summary:&lt;/strong&gt; A cryptographic signature on a usage receipt doesn't automatically make that receipt unforgeable. If the signer's public key is passed alongside the signed data instead of inside it, an attacker can swap in a different key, re-sign, and the check still passes. We built ComputeLedger, a provider-agnostic CLI for signing and independently verifying compute-usage receipts, and an internal security audit made us confirm, with a dedicated test, that the one field preventing exactly that gap was actually where it needed to be before we shipped it.&lt;/p&gt;

&lt;p&gt;Multi-cloud and multi-provider GPU usage has no standard, portable, independently verifiable record. A provider's billing dashboard tells you what that provider says you used. It doesn't let a third party confirm the number wasn't altered after the fact, and it only covers that one provider's stack. Two actively maintained open-source projects already prove real demand for pieces of this problem: a multi-cloud job orchestrator has 10,463 GitHub stars and shipped a commit the same week this article was written, and a Kubernetes/cloud cost-monitoring project under the CNCF has 6,670 stars and is also under active development (both checked live via the GitHub API, 2026-08-08). Neither one signs a usage claim in a way a stranger, with no account and no trust in either party, can independently check.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Real output: key generation, a recorded receipt, and a ledger-integrity check, run end to end against the actual &lt;code&gt;computeledger&lt;/code&gt; binary.&lt;/em&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  &lt;strong&gt;What "cryptographically signed" quietly leaves out&lt;/strong&gt;
&lt;/h2&gt;

&lt;p&gt;Say a compute job's usage record, GPU-hours, hardware, duration, gets signed with Ed25519, the same signature scheme used in SSH, TLS 1.3, and the Signal protocol (Bernstein et al., &lt;em&gt;High-speed high-security signatures&lt;/em&gt;) [1]. A verifier checks the signature against a public key and it passes. Case closed, receipt is real. Except: passes against which public key, and where did that key come from?&lt;/p&gt;

&lt;p&gt;If the public key travels next to the signed payload rather than as a hashed part of it, nothing stops someone from taking a receipt, generating their own fresh Ed25519 keypair, re-signing the same claim under their own key, and handing you a receipt that verifies perfectly. The signature math is completely correct. What broke is the binding between the claim and a specific, trusted identity, exactly the failure OWASP's own Top 10 groups under cryptographic failures rather than injection or access control, because the crypto isn't broken; it's applied to the wrong scope (OWASP Top 10:2021, A02:2021-Cryptographic Failures) [2]. A signature proves "this exact byte string was signed by whoever holds this exact private key." It proves nothing about whether that key is the one you meant to trust, unless the thing being signed pins that down itself.&lt;/p&gt;

&lt;p&gt;ComputeLedger's fix is one design decision, not a library or a protocol: the public key is a field inside the payload that gets hashed and signed, not metadata bolted on afterward. The source code says this directly, in a comment above the function that builds what gets hashed:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="err"&gt;/**&lt;/span&gt;&lt;span class="w"&gt;
 &lt;/span&gt;&lt;span class="err"&gt;*&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;The&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;hash&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;covers&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;every&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;field&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;an&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;attacker&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;could&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;tamper&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;with,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;including&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;publicKey&lt;/span&gt;&lt;span class="w"&gt;
 &lt;/span&gt;&lt;span class="err"&gt;*/&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="err"&gt;export&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;function&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;hashPayload(payload:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;ReceiptPayload):&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;string&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="err"&gt;return&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;sha&lt;/span&gt;&lt;span class="mi"&gt;256&lt;/span&gt;&lt;span class="err"&gt;Hex(canonicalizeToBytes(payload));&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Swap the public key and the hash changes. A different hash means the original signature no longer matches anything, so ledger verify rejects it before signature verification even runs. This isn't a novel cryptographic technique. It's the same "sign what you mean to bind" discipline that shows up wherever a protocol has been burned by leaving an identity field outside the signed scope. What's easy to get wrong is skipping it, because a naive implementation that checks "does this signature verify against this embedded public key" looks completely correct in a code review and in every happy-path test. It only fails the one test that specifically tries to swap the key and re-sign, which is exactly the test our own security audit required before we called this feature done.&lt;/p&gt;

&lt;h2&gt;
  
  
  &lt;strong&gt;The two bugs an internal audit caught before a single user could&lt;/strong&gt;
&lt;/h2&gt;

&lt;p&gt;The forgery gap above was caught at design time, before code existed to catch. Two smaller, real bugs got caught after code existed, during a security-focused audit of the finished implementation, and both are worth naming because neither is a cryptography mistake. They're the ordinary kind of gap that ships in working software constantly.&lt;/p&gt;

&lt;p&gt;First: the ledger reads its history back from a local &lt;code&gt;.jsonl&lt;/code&gt; file, one JSON object per line. The original version parsed each line with &lt;code&gt;JSON.parse&lt;/code&gt; and no error handling around it. A correctly-formatted-but-tampered line was already caught (a modified entry with a still-valid JSON shape fails the hash-chain check, by design). A line that isn't valid JSON at all, from a half-written disk flush, a manual edit gone wrong, anything, would throw an uncaught exception and crash the whole &lt;code&gt;ledger list&lt;/code&gt; or &lt;code&gt;ledger verify&lt;/code&gt; command instead of reporting a clean error about which line was bad.&lt;/p&gt;

&lt;p&gt;Second: the &lt;code&gt;record&lt;/code&gt; command validated &lt;code&gt;--duration-seconds&lt;/code&gt; as a finite, non-negative number before doing anything else with it. It didn't apply the same check to &lt;code&gt;--gpu-hours&lt;/code&gt; or &lt;code&gt;--flops&lt;/code&gt;. Passing a non-numeric value to either one didn't fail cleanly, it threw a raw JavaScript stack trace from deep inside the canonicalization step that hashes the payload, three layers away from where the actual mistake was made.&lt;/p&gt;

&lt;p&gt;Neither bug is exotic. Both are the unglamorous kind that a manual code review can miss because the happy path works, and both were live in the codebase at the time of the audit. We checked the current source directly while writing this piece: both are fixed now, &lt;code&gt;readAll()&lt;/code&gt; wraps the per-line parse in a try/catch and skips or reports the bad line instead of crashing, and &lt;code&gt;--gpu-hours&lt;/code&gt;/&lt;code&gt;--flops&lt;/code&gt; get the identical &lt;code&gt;Number.isFinite&lt;/code&gt; check &lt;code&gt;--duration-seconds&lt;/code&gt; always had, with a clean CLI error message instead of a stack trace. Neither fix is interesting on its own. What's worth taking away is the audit step that found them: reading the actual implementation against a specific checklist of failure classes instead of only running the existing test suite and calling it verified, because the existing tests were all written against inputs that already worked.&lt;/p&gt;

&lt;h2&gt;
  
  
  &lt;strong&gt;What this doesn't prove, and why that distinction matters&lt;/strong&gt;
&lt;/h2&gt;

&lt;p&gt;A signed, hash-chained receipt proves two specific things: that the receipt hasn't been altered since it was signed, and that whoever holds the private key behind the embedded public key produced it. It proves nothing about whether the numbers inside that receipt were true at the moment of signing. If you run &lt;code&gt;computeledger run --provider on-prem --hardware nvidia-a100 -- python train.py&lt;/code&gt; on a machine you fully control, you control every input the tool captures before it signs anything. Tamper-evidence protects the record after signing. It is not a truth oracle for what happened before signing, and a tool that implied otherwise would be overselling what cryptography can actually do here. That's a real limitation, not a caveat to bury in fine print, and it's the same limitation every self-attested signed-record system has, from a Git commit signature to a notarized affidavit: the notarization proves who signed and that it wasn't altered afterward, not that the signer told the truth.&lt;/p&gt;

&lt;p&gt;The private signing key itself carries a second, related tradeoff. It's stored as an unencrypted PEM file, protected only by filesystem permissions (mode 600, owner read/write only), no passphrase. That's the same tradeoff an unprotected local SSH key makes: fine for a local tool where the threat model is "don't leak the file," not fine if the threat model includes "an attacker with read access to this machine." A hosted or fleet-wide version of this idea would need a real key-management story; a single-machine CLI signing your own local job history reasonably doesn't, but that's a judgment call worth stating plainly rather than leaving implicit.&lt;/p&gt;

&lt;h2&gt;
  
  
  &lt;strong&gt;What this means if you're accountable for compute spend across providers&lt;/strong&gt;
&lt;/h2&gt;

&lt;p&gt;If your job is tracking GPU spend, capacity, or usage claims across more than one provider, "cryptographically signed" in a vendor's marketing copy is not, by itself, a claim worth taking at face value. The question worth asking is narrower and checkable: is the identity that a signature is supposed to bind to actually part of what got signed, or is it sitting next to the signature as unauthenticated metadata? The difference between those two designs is invisible in a demo and in a green test suite. It only shows up when someone deliberately tries to swap the key, which is exactly the kind of test a working system has no built-in reason to run against itself.&lt;/p&gt;

&lt;h2&gt;
  
  
  &lt;strong&gt;The Evolving Landscape of Verifiable Compute Usage&lt;/strong&gt;
&lt;/h2&gt;

&lt;p&gt;A handful of distinct approaches already touch some part of "prove what compute was actually used," and they solve genuinely different problems, not competing versions of the same one.&lt;/p&gt;

&lt;p&gt;Multi-cloud job orchestration and cost-visibility platforms focus on running workloads across providers and showing you what they cost, often with real scale and real adoption behind them. Their value is operational: fewer manual steps to launch a job anywhere, one dashboard instead of five billing consoles. None of them, by design, produces a receipt a third party with no account and no API access can independently verify offline; the record lives inside the platform's own system of record, which is a different trust model than a portable, self-contained proof.&lt;/p&gt;

&lt;p&gt;A newer category of cryptoeconomic compute networks verifies contributed compute as part of a decentralized training or inference marketplace, often anchored to a blockchain or token mechanism for settlement and reputation. This solves a real problem, verifying work done by untrusted third parties who get paid for it, but it's a heavier commitment than most teams tracking their own already-trusted infrastructure need: adopting a specific chain, a specific settlement mechanism, and a specific network's governance just to get a receipt for a job that ran on hardware you already own or already pay a known cloud provider for directly.&lt;/p&gt;

&lt;p&gt;A third category, enterprise attestation platforms increasingly backed by hardware vendors, ties verification to trusted execution environments and hardware-rooted attestation, a genuinely stronger guarantee in the specific sense that it can attest to what actually ran inside a protected enclave, rather than only what a process later claims happened. The tradeoff is dependency: it generally requires specific hardware support and, often, an enterprise relationship with the platform providing the attestation service, which is a real barrier for a team that wants a receipt format they can adopt today on whatever hardware they already have.&lt;/p&gt;

&lt;p&gt;A fourth category is the direct precedent this project has to be honest about: a prior open-source attempt at training-compute provenance attestation, binding model weights to a hardware-rooted proof, shipped, got real attention, and then stopped. Its GitHub repository sits at 20 stars with no commits since June 2024 (verified live via the GitHub API, 2026-08-08) and, worth stating precisely rather than loosely, is not formally archived by GitHub itself, it's dormant, not read-only. That's a real data point about how hard this specific narrow niche is to sustain as a standalone open-source project, not a detail to gloss over.&lt;/p&gt;

&lt;p&gt;None of these four fully occupies the specific, narrow space of a lightweight, provider-agnostic, offline-verifiable receipt format that doesn't require adopting a chain, a hardware dependency, or a hosted platform, just a CLI command or an MCP tool call and a signature anyone can check with no account. A persistent gap remains between "trust the platform's dashboard" and "commit to a full attestation ecosystem." An emerging, narrower category of small, self-contained receipt formats is starting to fill that specific gap: a signed, hash-chained, cross-language-verifiable claim that travels with the job itself rather than living inside any one provider's or network's system of record.&lt;/p&gt;

&lt;h2&gt;
  
  
  &lt;strong&gt;Honest limitations&lt;/strong&gt;
&lt;/h2&gt;

&lt;p&gt;ComputeLedger doesn't attest to ground truth, only to what was signed and whether it was altered afterward, a distinction the section above covers in full because it's the single most important thing to understand correctly before using this for anything that matters. It doesn't do multi-cloud orchestration or cost dashboards; SkyPilot and OpenCost solve those problems, actively and at real scale, and ComputeLedger is explicitly not attempting to replace either. Windows CI coverage doesn't exist yet, only Linux is continuously tested upstream, even though both packages install cross-platform. And the honest one with no benchmark number attached to fix it: whether teams actually want a separate, portable receipt format instead of trusting whatever their existing cloud or orchestration platform already reports is a real, unsettled question. An earlier internal validation pass on this exact niche found it already crowded by funded players working adjacent angles, and we built this anyway as a narrower, cheaper bet specifically on the provider-agnostic, no-lock-in framing those players don't occupy. Whether that bet is right isn't proven by shipping the tool.&lt;/p&gt;

&lt;h2&gt;
  
  
  &lt;strong&gt;Try it yourself&lt;/strong&gt;
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="err"&gt;computeledger&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;keys&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;generate&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;--local&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="err"&gt;computeledger&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;record&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;--local&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;--provider&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;aws&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;--hardware&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;nvidia-h&lt;/span&gt;&lt;span class="mi"&gt;100&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;\&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="err"&gt;--duration-seconds&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;3600&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;--gpu-hours&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;--workload-type&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;training&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="err"&gt;computeledger&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;ledger&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;verify&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;--local&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Or skip the manual record call and wrap a real job directly:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="err"&gt;computeledger&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;run&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;--local&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;--provider&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;on-prem&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;--hardware&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;nvidia-a&lt;/span&gt;&lt;span class="mi"&gt;100&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;--&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;python&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;train.py&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Anyone with the resulting &lt;code&gt;receipt.json&lt;/code&gt; can run &lt;code&gt;computeledger verify receipt.json&lt;/code&gt; on a completely different machine, with a completely different install (npm or PyPI, either direction) and get the same answer.&lt;/p&gt;

&lt;p&gt;We have one open, genuinely undecided question: whether the next release should add a signed, optional witness field (a third party attesting they observed the job run, in addition to the issuer's own signature) or should instead focus on hardening the single-machine key-storage story before adding any new receipt fields at all. Both are real, scoped pieces of work. Which one would actually make you consider wiring this into a compute-tracking pipeline you already run?&lt;/p&gt;

&lt;p&gt;If you're building anything that signs a claim and hands it to someone who has to trust it later, a compute receipt, an audit log, an API response, check whether the field that names who's trusted is actually part of what gets hashed, or just sitting next to it. If you maintain a FinOps, MLOps, or compute-governance pipeline and want a portable, independently verifiable usage format as a dependency instead of building one from scratch, a star on the repo helps other people building the same kind of tool find it.&lt;/p&gt;

&lt;p&gt;GitHub: &lt;a href="https://github.com/RudrenduPaul/ComputeLedger" rel="noopener noreferrer"&gt;github.com/RudrenduPaul/ComputeLedger&lt;/a&gt; &lt;br&gt;
MCP Servers:&lt;br&gt;
&lt;a href="https://mcpservers.org/servers/rudrendupaul/computeledger" rel="noopener noreferrer"&gt;mcpservers.org/servers/rudrendupaul/computeledger&lt;/a&gt;&lt;br&gt;
&lt;a href="https://glama.ai/mcp/servers/RudrenduPaul/ComputeLedger" rel="noopener noreferrer"&gt;glama.ai/mcp/servers/RudrenduPaul/ComputeLedger&lt;/a&gt;&lt;br&gt;
npm: &lt;a href="https://www.npmjs.com/package/computeledger-cli" rel="noopener noreferrer"&gt;npmjs.com/package/computeledger-cli&lt;/a&gt; &lt;br&gt;
PyPI: &lt;a href="https://pypi.org/project/computeledger-cli" rel="noopener noreferrer"&gt;pypi.org/project/computeledger-cli&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  &lt;strong&gt;References&lt;/strong&gt;
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;Bernstein, D.J., Duif, N., Lange, T., Schwabe, P., Yang, B.Y., &lt;a href="https://ed25519.cr.yp.to/ed25519-20110926.pdf" rel="noopener noreferrer"&gt;&lt;em&gt;High-speed high-security signatures&lt;/em&gt;&lt;/a&gt;, Journal of Cryptographic Engineering, 2, 77-89, 2012 (the Ed25519 signature scheme ComputeLedger uses)&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://owasp.org/Top10/A02_2021-Cryptographic_Failures" rel="noopener noreferrer"&gt;OWASP Top 10:2021 - A02:2021-Cryptographic Failures&lt;/a&gt;, OWASP Foundation, 2021&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://www.rfc-editor.org/rfc/rfc8032" rel="noopener noreferrer"&gt;RFC 8032: Edwards-Curve Digital Signature Algorithm (EdDSA)&lt;/a&gt;, IETF, 2017&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Co-authored by &lt;a href="https://github.com/RudrenduPaul" rel="noopener noreferrer"&gt;Rudrendu Paul&lt;/a&gt; and &lt;a href="https://github.com/Sourav-nandy-ai" rel="noopener noreferrer"&gt;Sourav Nandy&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;&lt;a href="https://github.com/RudrenduPaul" rel="noopener noreferrer"&gt;Rudrendu Paul&lt;/a&gt; and &lt;a href="https://github.com/Sourav-nandy-ai" rel="noopener noreferrer"&gt;Sourav Nandy&lt;/a&gt; build open-source developer tools for the AI agent ecosystem. They are the co-authors of &lt;a href="https://github.com/RudrenduPaul/ComputeLedger" rel="noopener noreferrer"&gt;ComputeLedger&lt;/a&gt;, a dual-distribution (npm + PyPI) CLI, library, and MCP server for signing and independently verifying compute-usage receipts across any cloud or on-prem provider, and other AI agent infrastructure tools. Find the code at &lt;a href="https://github.com/RudrenduPaul" rel="noopener noreferrer"&gt;github.com/RudrenduPaul&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>ai</category>
      <category>opensource</category>
      <category>showdev</category>
      <category>security</category>
    </item>
    <item>
      <title>How to Trace Which Neurons Actually Caused Your LLM's Output, From the Command Line</title>
      <dc:creator>Sourav Nandy</dc:creator>
      <pubDate>Tue, 25 Aug 2026 20:54:11 +0000</pubDate>
      <link>https://dev.to/sourav-nandy/how-to-trace-which-neurons-actually-caused-your-llms-output-from-the-command-line-1ljp</link>
      <guid>https://dev.to/sourav-nandy/how-to-trace-which-neurons-actually-caused-your-llms-output-from-the-command-line-1ljp</guid>
      <description>&lt;p&gt;&lt;em&gt;Co-authored by &lt;a href="https://github.com/Sourav-nandy-ai" rel="noopener noreferrer"&gt;Sourav Nandy&lt;/a&gt; and &lt;a href="https://github.com/RudrenduPaul" rel="noopener noreferrer"&gt;Rudrendu Paul&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;Repo: &lt;a href="https://github.com/RudrenduPaul/neuronscope" rel="noopener noreferrer"&gt;https://github.com/RudrenduPaul/neuronscope&lt;/a&gt;, An open-source CLI and MCP server for mechanistic interpretability.&lt;/p&gt;

&lt;p&gt;You ask a language model why it gave you a particular answer, and it hands back a confident, plausible-sounding explanation. That explanation is generated by the same model that produced the original answer, so it's not a report on what actually happened inside it. It's a second guess dressed up as a first-hand account.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;NeuronScope&lt;/code&gt; is our attempt at the alternative: a CLI and MCP server that traces which attention heads and MLP neurons actually drove a model's output, instead of asking the model to narrate itself. It's built on top of an existing open-source library for hooking into model internals, ships a versioned JSON schema on every command, and exposes the same four operations to an AI agent over MCP. MIT-licensed, &lt;code&gt;pip install neuronscope-cli&lt;/code&gt;, runs small models like GPT-2 on CPU.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F50liejfidf9ilyvxvz0g.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F50liejfidf9ilyvxvz0g.png" alt="NeuronScope" width="800" height="479"&gt;&lt;/a&gt;&lt;em&gt;Real output: &lt;code&gt;neuronscope trace gpt2 "The capital of France is Paris. The capital of Japan is"&lt;/code&gt; ranking the attention heads and neurons that produced the prediction &lt;code&gt;Tokyo&lt;/code&gt;.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;Gartner expects explainable-AI tooling to account for half of all LLM observability investment by 2028, up from 15% today (&lt;a href="https://www.gartner.com/en/newsroom/press-releases/2026-03-30-gartner-predicts-by-2028-explainable-ai-will-drive-llm-observability-investments-to-50-percent-for-secure-genai-deployment" rel="noopener noreferrer"&gt;Gartner: Explainable AI Will Drive 50% of LLM Observability Investment by 2028&lt;/a&gt;). Almost all of the funding chasing that shift is going into hosted platforms: interpretability research lab Goodfire raised a $150M Series B at a $1.25B valuation in February (&lt;a href="https://www.goodfire.ai/blog/our-series-b" rel="noopener noreferrer"&gt;Goodfire: Our Series B&lt;/a&gt;), and Apollo Research converted from a philanthropy-funded model to a VC-backed public benefit corporation the same month (&lt;a href="https://www.apolloresearch.ai/blog/apollo-research-is-becoming-a-pbc" rel="noopener noreferrer"&gt;Apollo Research Is Becoming a PBC&lt;/a&gt;). Almost none of that money is landing on a command you can run against an open-weight model on your own laptop. That's the gap we're trying to close. It's also three weeks old, and we already broke its most-marketed feature once. That's most of this article.&lt;/p&gt;

&lt;h2&gt;
  
  
  &lt;strong&gt;What mechanistic interpretability actually measures&lt;/strong&gt;
&lt;/h2&gt;

&lt;p&gt;Ask a language model why it produced a given output and it will happily generate a plausible explanation. Because that explanation is a second generation from the same model, it's disconnected from what actually happened inside the first one. Mechanistic interpretability is the alternative: you instrument the forward pass and measure which components moved the prediction.&lt;/p&gt;

&lt;p&gt;Three terms carry most of the weight here. &lt;strong&gt;Attention heads&lt;/strong&gt; move information between token positions; ranking them by direct logit attribution tells you which head's output pushed the final prediction the hardest. &lt;strong&gt;MLP neurons&lt;/strong&gt; fire on specific features inside a layer; ranking them by activation magnitude tells you which ones were most active at the position that mattered. &lt;strong&gt;Activation patching&lt;/strong&gt; (or ablation) is the causal test: zero out one component and watch how much the prediction actually changes, which is a stronger claim than "this component was active" on its own.&lt;/p&gt;

&lt;p&gt;The tooling gets honest about its own limits right at that distinction. High logit attribution only tells you a component was correlated with the output: two components can be redundant, so ablating either one individually barely moves the prediction even though both ranked high in isolation. NeuronScope's own &lt;code&gt;circuit&lt;/code&gt; command, which chains ranking and single-component ablation into an automated sketch, states this explicitly in its JSON output's &lt;code&gt;method&lt;/code&gt; field, so a caller can't assume more rigor than the method provides. A tool that can't name where its own technique breaks down is a tool you should be skeptical of, and that applies as much to NeuronScope as to anything else in this space.&lt;/p&gt;

&lt;h2&gt;
  
  
  &lt;strong&gt;The CLI-shaped hole&lt;/strong&gt;
&lt;/h2&gt;

&lt;p&gt;The underlying interpretability library most of this space is built on gives you a real, capable Python API: load a model, register hooks, run a forward pass, read the activations back as tensors. That's the right interface for a research notebook where you're iterating interactively. It's the wrong interface for two things that are increasingly common in 2026: a CI check that needs a JSON exit code, and an agent that needs to call a tool over MCP and get back an already-serialized, structured result.&lt;/p&gt;

&lt;p&gt;NeuronScope exists to be that second interface. Here's the actual command and its real output:&lt;/p&gt;

&lt;p&gt;&lt;code&gt;neuronscope trace gpt2 "The capital of France is Paris. The capital of Japan is" --top-k 3 --json&lt;/code&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"schema_version"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"operation"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"trace"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"model"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"requested_name"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"gpt2"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"resolved_name"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"gpt2"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"backend"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"transformer_lens"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"device"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"cpu"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"n_layers"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;12&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"n_heads"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;12&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"d_model"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;768&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"d_mlp"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;3072&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"prompt"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"The capital of France is Paris. The capital of Japan is"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"predicted_token"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;" Tokyo"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"predicted_token_id"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;11790&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"top_neurons"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"layer"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;10&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"neuron_index"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;97&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"activation"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mf"&gt;7.839381217956543&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"layer"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;11&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"neuron_index"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;611&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"activation"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mf"&gt;4.695372581481934&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"layer"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;11&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"neuron_index"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;2997&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"activation"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mf"&gt;4.646785736083984&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"top_heads"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"layer"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;9&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"head_index"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;8&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"logit_attribution"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mf"&gt;4.067923545837402&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"layer"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;8&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"head_index"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;11&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"logit_attribution"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mf"&gt;2.9028172492980957&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"layer"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;10&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"head_index"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;7&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"logit_attribution"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mf"&gt;-1.4781968593597412&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;gpt2&lt;/code&gt; predicts &lt;code&gt;Tokyo&lt;/code&gt;, and head &lt;code&gt;L9H8&lt;/code&gt; is the single largest contributor. That's the same document, byte-for-byte, that an MCP client gets back when it calls the equivalent tool, no CLI shell-out required. One schema, two callers.&lt;/p&gt;

&lt;p&gt;For a team deciding where to put interpretability tooling budget, the split is straightforward: computing the activations, the hard part, is a solved research problem with mature open libraries behind it. Integration was the unsolved part: getting that computation into a shape a script or an agent can consume without hand-written glue code per model architecture. That's a tooling investment. It's a much smaller check to write than a research one.&lt;/p&gt;

&lt;h2&gt;
  
  
  &lt;strong&gt;The mistake that took down the flagship feature&lt;/strong&gt;
&lt;/h2&gt;

&lt;p&gt;Three weeks after the first release, the MCP server, the single most heavily marketed capability in the README, stopped working on every fresh install. It broke immediately, for anyone who ran &lt;code&gt;pip install neuronscope-cli&lt;/code&gt; after a specific date.&lt;/p&gt;

&lt;p&gt;The cause was a dependency line in &lt;code&gt;pyproject.toml: mcp&amp;gt;=1.0&lt;/code&gt;, with no upper bound. That's a reasonable-looking constraint right up until the &lt;code&gt;mcp&lt;/code&gt; package ships a 2.0.0 release that removes the exact module (&lt;code&gt;mcp.server.fastmcp&lt;/code&gt;) NeuronScope's server code imports. It did. A fresh install resolved &lt;code&gt;mcp==2.0.0&lt;/code&gt;, and &lt;code&gt;neuronscope mcp-server&lt;/code&gt; crashed on startup with &lt;code&gt;ModuleNotFoundError: No module named&lt;/code&gt; &lt;code&gt;'mcp.server.fastmcp'&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;We found this the way you'd want to, and the way a lot of teams don't: an independent repo audit ran the documented quickstart command in a clean virtual environment before anything shipped further. It didn't lean on a CI run from a week earlier. That run was already stale, since CI had last gone green before the breaking &lt;code&gt;mcp&lt;/code&gt; release even existed. The fix was one line, &lt;code&gt;mcp&amp;gt;=1.0,&amp;lt;2.0&lt;/code&gt;, plus a version bump and a changelog entry. The lesson generalizes past this one dependency: an unbounded floor on any library your integration touches directly, especially one still moving fast enough to ship breaking majors, is a live outage waiting for someone else's release cadence to trigger it. We now pin every dependency NeuronScope imports directly, by name, with both a floor and an upper bound.&lt;/p&gt;

&lt;p&gt;For an engineering lead, the actual takeaway is narrower than "pin your dependencies," which everyone already knows: the dependencies worth bounding aggressively are the ones backing your most-marketed, least-tested code path. That's exactly where a silent break does the most damage to a first impression, and it's the break that gets caught last.&lt;/p&gt;

&lt;h2&gt;
  
  
  &lt;strong&gt;The feature we decided not to build&lt;/strong&gt;
&lt;/h2&gt;

&lt;p&gt;Not every engineering decision in this project was a bug fix. One was a deliberate no. The obvious move for reaching the Node/TypeScript agent-tooling crowd would've been a thin npm package that shells out to the PyPI one, so &lt;code&gt;npx neuronscope-cli&lt;/code&gt; works without anyone needing Python on their PATH. We scoped it, then skipped it for v1.&lt;/p&gt;

&lt;p&gt;The reasoning: the tool's actual work always requires a Python runtime with a multi-hundred- megabyte machine learning dependency installed. A Node wrapper doesn't remove that cost, it just adds a second package to keep in sync, a second place for bugs to hide (subprocess invocation, PATH resolution, version drift between the shim and the thing it wraps), and a convenience that mostly matters to someone who wasn't going to get value from the tool anyway, since they'd still need Python and PyTorch installed to run anything past the wrapper itself. We'll build it if real users ask for it post-launch. We won't build it speculatively because it looked easy.&lt;/p&gt;

&lt;p&gt;The failure mode worth naming explicitly for anyone maintaining a CLI with a heavy native dependency: a second distribution channel isn't free just because the wrapper code is short. The maintenance cost comes from the surface area it adds, no matter how few lines the wrapper itself takes to write.&lt;/p&gt;

&lt;h2&gt;
  
  
  &lt;strong&gt;Why the backend is an interface&lt;/strong&gt;
&lt;/h2&gt;

&lt;p&gt;The one piece of architecture built ahead of an immediate need: &lt;code&gt;Backend&lt;/code&gt; is an abstract interface (&lt;code&gt;load_model, get_activations, patch_activations, list_supported_architectures&lt;/code&gt;), and the interpretability library NeuronScope launched with is the only concrete implementation of it in v1. That's a small amount of upfront indirection to pay for a project that, on day one, has exactly one backend to support.&lt;/p&gt;

&lt;p&gt;The reason it's worth that cost here specifically: the domain has more than one credible backend already in view, each with a different trade-off (fixed-model-family coverage versus arbitrary PyTorch model support versus deeper feature-level analysis). Adding a second one later means writing a new class against an existing interface: the CLI, the MCP layer, and the JSON schema stay untouched. Hardcoding the first backend's specific calls directly into the command layer would have turned that into a rewrite. The failure mode to watch for on the other side of this decision: building the interface before you have a second real implementation to validate it against is a bet. It's only a good one when you're confident enough in the domain's shape to lose a little now for a lot less later.&lt;/p&gt;

&lt;h2&gt;
  
  
  &lt;strong&gt;Where NeuronScope fits, and where it doesn't&lt;/strong&gt;
&lt;/h2&gt;

&lt;p&gt;Four categories of existing approaches are relevant here, and it's worth being precise about where each one actually sits: treating them as interchangeable hides real differences.&lt;/p&gt;

&lt;p&gt;Research-grade Python libraries for hooking into a model's internals are the most mature part of this space: mainstream, actively maintained, and capable of real depth. They're built for a notebook-and-script workflow, though, which means a non-Python process can't shell out to them and an agent can't call them directly.&lt;/p&gt;

&lt;p&gt;A newer wave of hosted, enterprise-funded interpretability platforms is where most of the capital described above is actually going. These offer browsable feature databases and managed infrastructure, real value for teams that want a dashboard. They're a different product category from a lightweight, scriptable CLI, and they often carry deployment requirements (a database, a container orchestrator) that a solo developer running a one-off trace doesn't want.&lt;/p&gt;

&lt;p&gt;A third category offers exactly the CLI-plus-structured-output shape this article is arguing for. Its trade-off: model support is locked to a fixed, curated list of families, and generic coverage of whatever an underlying library happens to support isn't part of the deal. That's a legitimate trade-off, depth of analysis against breadth of model coverage. Because of it, the "CLI with JSON output" problem already has real competition claiming that niche. What's still comparatively open is pairing model-agnostic coverage with a native, first-class MCP surface: elsewhere, that surface shows up only as JSON export bolted on afterward.&lt;/p&gt;

&lt;p&gt;A fourth category focuses specifically on the training and analysis of sparse autoencoders, a deeper and more specialized technique for decomposing model internals into interpretable features. That's complementary to component-level tracing, and NeuronScope doesn't attempt to replace it.&lt;/p&gt;

&lt;p&gt;NeuronScope's own position in that space is narrow on purpose: a CLI and MCP layer that works across whatever model families the library underneath it supports, skipping the fixed allowlist other tools rely on. That trades some analytical depth for breadth, and for being callable by a script or an agent without hand-written integration code. It's trying to make the research those four categories already do reachable from a terminal or a tool call, without claiming to out-research any of them.&lt;/p&gt;

&lt;h2&gt;
  
  
  &lt;strong&gt;What this means if you're deciding where to spend on interpretability&lt;/strong&gt;
&lt;/h2&gt;

&lt;p&gt;If you're an engineering leader weighing interpretability tooling spend, the practical split is between two different problems that get talked about as one. Interpretability &lt;em&gt;research capacity&lt;/em&gt;, training new methods, discovering new circuit-analysis techniques, is a genuinely hard, ongoing problem, and it's where the funding above is correctly flowing. Interpretability &lt;em&gt;tooling integration&lt;/em&gt;, getting an existing, working method into a shape your CI pipeline or your agent harness can actually call, is a much smaller, mostly-solved software engineering problem that happens to be sitting mostly unclaimed in open source rather than requiring a platform subscription. Knowing which one you actually need before you buy either is the whole decision.&lt;/p&gt;

&lt;h2&gt;
  
  
  &lt;strong&gt;Honest limitations&lt;/strong&gt;
&lt;/h2&gt;

&lt;p&gt;We'd rather list these than have you find them yourself. The &lt;code&gt;circuit&lt;/code&gt; command is an approximation: it ranks components by logit attribution, then measures each one's individual causal effect through single-component ablation, and it says so directly in its own &lt;code&gt;--json&lt;/code&gt; output's &lt;code&gt;method&lt;/code&gt; field. It doesn't do full path-patching with clean and corrupted prompt pairs, and it won't catch interaction effects between components that only show up when you remove two of them together.&lt;/p&gt;

&lt;p&gt;NeuronScope's MCP server currently has no built-in resource limit on model size or forward-pass time. If you expose it to an untrusted agent, bounding it with a container or process limit is on you: the tool doesn't enforce that yet. And the underlying library's model-loading function is already flagged deprecated upstream, in favor of a newer API we haven't migrated to. It still works, every command in this article ran on it, but it's a tracked, open item in the repo. A tool that hides gaps like these doesn't deserve the trust the rest of this article is asking for.&lt;/p&gt;

&lt;h2&gt;
  
  
  &lt;strong&gt;Try it against your own model&lt;/strong&gt;
&lt;/h2&gt;

&lt;p&gt;&lt;code&gt;pip install neuronscope-cli&lt;br&gt;
neuronscope trace gpt2 "The capital of France is Paris. The capital of Japan is" --top-k 5&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;No GPU required for small models. &lt;code&gt;neuronscope mcp-server&lt;/code&gt; starts the MCP server over stdio if you want to wire it into Claude Code, Claude Desktop, or any other MCP host.&lt;/p&gt;

&lt;p&gt;We have one open, genuinely undecided question: the next backend to add behind the same interface. Arbitrary PyTorch model support with no fixed list, or deeper, feature-level circuit discovery through sparse autoencoders. They pull in different directions, and we haven't built either yet. Which one would you actually reach for first?&lt;/p&gt;

&lt;p&gt;If this is useful to you, a star on the repo helps other people working on interpretability tooling find it.&lt;/p&gt;

&lt;p&gt;GitHub: &lt;a href="https://github.com/RudrenduPaul/NeuronScope" rel="noopener noreferrer"&gt;github.com/RudrenduPaul/NeuronScope&lt;/a&gt; &lt;br&gt;
PyPI: &lt;a href="https://pypi.org/project/neuronscope-cli/" rel="noopener noreferrer"&gt;pypi.org/project/neuronscope-cli&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Co-authored by &lt;a href="https://github.com/Sourav-nandy-ai" rel="noopener noreferrer"&gt;Sourav Nandy&lt;/a&gt; and &lt;a href="https://github.com/RudrenduPaul" rel="noopener noreferrer"&gt;Rudrendu Paul&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;&lt;a href="https://github.com/Sourav-nandy-ai" rel="noopener noreferrer"&gt;Sourav Nandy&lt;/a&gt; and &lt;a href="https://github.com/RudrenduPaul" rel="noopener noreferrer"&gt;Rudrendu Paul&lt;/a&gt; build open-source developer tools for the AI agent ecosystem. They are the co-authors of &lt;a href="https://github.com/RudrenduPaul/NeuronScope" rel="noopener noreferrer"&gt;NeuronScope&lt;/a&gt;, a model-agnostic CLI and MCP server for mechanistic interpretability, along with a related set of AI-agent infrastructure projects. Find the code at &lt;a href="https://github.com/RudrenduPaul" rel="noopener noreferrer"&gt;github.com/RudrenduPaul&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>ai</category>
      <category>opensource</category>
      <category>python</category>
      <category>showdev</category>
    </item>
  </channel>
</rss>
