Skip to content

Add visa_read to collect a pending VISA response without writing - #9

Merged
techmanual-ai merged 1 commit into
mainfrom
feat/visa-read-20261005
Oct 6, 2026
Merged

techmanual-ai merged 1 commit into
mainfrom
feat/visa-read-20261005

Conversation

@techmanual-ai

Copy link
Copy Markdown
Owner

What changed

This adds a visa_read MCP tool. It reads whatever is already waiting in an instrument's output buffer and sends nothing. Before this, every VISA read was tied to the write that triggered it. A 30-second sweep therefore had to run inside one visa_query, with a 30-second timeout held open for the whole acquisition. Now you visa_write the sweep, do other work, and visa_read with a timeout sized to the acquisition.

  • lablink/interfaces/visa/driver.py
    • visa_read_impl(alias, timeout_ms=None) sits next to visa_query_impl and visa_write_impl and follows their structure: the same session lookup, the same _no_session_result path, the same ReadResult shape, and an event-log entry on every return path.
    • It sets session.raw.timeout from scratch on every call (ARCHITECTURE §8.2). A long override is the normal case for this tool, so a 40 s read can't leak its timeout into the next fast query.
    • The tool docstring says plainly that it sends nothing, explains when to use it, and shows the visa_write(alias, "READ?") → visa_read(alias, timeout_ms=40000) sequence. It also says what an empty buffer does. One warning goes beyond the task: don't send the instrument another command between the write and the read, because on most instruments a new query discards the pending response. An agent doing other work in the meantime would hit this.
  • Tests
    • tests/interfaces/test_visa.py gets a TestRead class covering:
      • a successful read, checking that nothing is written or queried
      • an empty buffer timing out
      • a non-timeout I/O error failing
      • no session for the alias
      • the per-call override reaching the resource
      • falling back to the config's timeout_ms after an override
    • tests/test_demo_bench.py adds an end-to-end test against the real SCPI simulator over TCP using the real driver. It writes READ?, reads back all 8 channels, then checks that a second read with nothing pending returns timed_out=True, raw=None.
  • Docs
    • README.md: visa_read added to the protocol table and the per-driver tool reference.
    • CHANGELOG.md: entry under [Unreleased] → Added.
    • docs/agent_docs/agent_development.md: one new CLI rule (see below).
    • docs/ARCHITECTURE.md is unchanged. It doesn't list the VISA driver's operations; it only mentions visa_query as an example.

The full suite passes (.venv/bin/python -m pytest -q: 393 passed). It also passed before the change.

Where I departed from the task

  1. An empty buffer returns a timeout result, not an error. The task says a read with nothing pending "blocks until timeout_ms and then errors." But ARCHITECTURE §5.2 marks the read-style result contract as locked: a timeout with no data is success=True, raw=None, timed_out=True, and success=False is reserved for a broken connection. That split is what lets an agent tell "not ready yet, call again" from "something is wrong," and "not ready yet" is exactly what happens when a sweep runs longer than expected. serial_read and ssh_read_stream behave the same way. So visa_read returns timed_out=True, raw=None on a VISA timeout, and still returns success=False for any other VISA I/O error. The task's main point still holds: nothing pending gives a timeout, never an empty string, and the docstring says so. If you really want success=False here, it's a one-line change, but it would break §5.2 and should come with an edit to that section.
  2. No lablink visa read CLI subcommand. Each CLI invocation opens and closes its own session, and connect sends *IDN? first. A CLI read can therefore never see a response left by an earlier lablink visa write: over a TCP socket the new connection has its own buffer, and on USB/GPIB the *IDN? discards the pending response. The command would always time out, which would mislead users. There's precedent: serial_read and ssh_read_stream have no CLI commands either. I wrote this down as a general rule in agent_development.md so the next read-style tool doesn't hit the same question.

Notes

  • The README marks VISA as hardware-validated ✅, and visa_read now appears in that row. It has only been checked against the simulator and mocks, not real hardware.
  • The task asked for Co-Authored-By: Claude Opus 5, while the session's default trailer is Opus 5.5. I used your wording, since your instructions take precedence.

🤖 Generated with Claude Code


🤖 Generated with Claude Code

Every VISA read was tied to the write that triggered it, so a long
acquisition had to run inside a single visa_query whose timeout spanned
the whole sweep. visa_read reads whatever is waiting in the output
buffer and sends nothing: write the sweep, then read it later with a
timeout sized to the acquisition.

The timeout is reset from scratch on every call (ARCHITECTURE §8.2),
since an acquisition-sized override is the normal case for this tool
and must not leak into later queries. An empty buffer follows the
locked read-style contract (§5.2): success=True, raw=None,
timed_out=True, so the agent can tell "not ready yet" from a broken
connection. Other VISA I/O errors still fail.

No CLI subcommand: each CLI invocation opens its own session, so it
could never see a response left by an earlier `lablink visa write`.
serial_read and ssh_read_stream already follow this; the rule is now
written down in agent_development.md.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
@techmanual-ai
techmanual-ai merged commit eab3e5a into main Oct 6, 2026
4 checks passed
@techmanual-ai
techmanual-ai deleted the feat/visa-read-20261005 branch October 6, 2026 03:07
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.

1 participant