feat(core): choose a transport per node, so a node without a share can work - #78
Merged
Conversation
…n work The design always called for two ways an agent reaches media and only one was ever built. This adds the concept the second needs. Mount is what every agent did: read and write the library through a share, with the server translating canonical paths to that node's local paths. Stream is the other half -- the server sends the source bytes, the agent works on a local copy, and the result comes back for the server to install, so the node needs no share, no credentials and no knowledge of the server's storage layout. The behavioural change is one line and it is the whole point: MountCovers no longer binds a streaming agent. Such a node advertises no mounts at all, so under the old rule it satisfied nothing and would have been handed no work forever while reporting healthy. The requirement still rides on the job, because the same job must stay dispatchable to a mount-mode agent and the server needs the canonical path either way -- to translate, or to read the bytes from. Concretely: on 2026-08-10 all three of windows-rtx2070's SMB mounts reported Unavailable, because Windows drive mappings are per-logon-session and the session an agent runs in is not the interactive one holding them. Under mount-only transport that node is undispatchable however well its encoder works, and NVENC there is confirmed working. Older agents are unaffected: #[serde(default)] on the domain type and TM_MOUNT=0 on the wire, so an agent that never heard of transports keeps its behaviour. A field that silently switched an existing agent to streaming would be the worst way to ship this. Both directions of the conversion boundary carry it, with a note at each. That boundary is where the muxers field was dropped, and the failure was identical in shape: an agent registering healthy and matching nothing. The round-trip test now uses a streaming fixture, so dropping the field on the way out turns it red -- verified by doing exactly that. Also fixes `agent survey` telling a streaming node it is broken: with no mounts it printed "can be given no work", which sends an operator hunting a share problem on a node deliberately not using one. Streaming's byte-moving RPCs and the server-side install are the next two changes; this is what they attach to. Workspace suite 517 -> 524. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01U9keBRnGXBP2DiQmrcqm33
…eaming contract The rule was already stated and already checked: transcodarr-agent must never depend on transcodarr-store, because it "has to stay copyable to the Windows node without dragging SQLite along", verified with cargo tree. The crate honoured it. Every shipped artifact defeated it. There was one binary, transcodarr, and it links transcodarr-server -> store -> SQLite, so the thing an operator would actually copy to a worker node contained the whole orchestrator and a C library it will never open. The check passed because it asked about the dependency graph while the property that mattered was about the binary. That is the fifth time in this project an invariant has held exactly where it was cheap to measure and failed where it was needed. transcodarr-agent is now its own binary: 0 sqlite crates against the CLI's 3, 19M against 38M. transcodarr keeps its agent verbs unchanged. Also lands the streaming wire contract -- FetchSource, PushOutput, and FileChunk with an explicit offset, an explicit last, and a content_sig on the final chunk. Offset is carried rather than implied so a restarted stream is detectable instead of appending into a corrupt file of exactly the right length. Streaming does not work yet, and the server methods say so: they return unimplemented with a reason rather than an empty stream or an accepted-but-ignored push. Either of those would look like success and produce a job reporting done having moved no bytes, which is this repository's most frequent failure shape. Fixes .gitignore silently swallowing the new binary. `bin/` matched crates/transcodarr-agent/src/bin/, where Cargo keeps source, so the file was invisible to git status and would not have been committed -- a fresh clone would have built a workspace with no agent in it while every local check passed. This is the second instance of exactly this defect; the first was 0001_initial.sql, kept out of every commit by a global *.sql rule, which made main compile on one machine and nowhere else. The fix is the same negation the repository already carries for migrations, two lines below it. KNOWN BAD, do not merge yet: `cargo test --workspace` hangs, locally and in CI. It reproduces on this machine past 600s where the suite used to take 45s, so the hung Test Rust on #78 was a real defect and not a stuck runner. Suspect the new unimplemented stubs or the TransportMode change interacting with a test that waits on a stream it now never receives. Find it before merging. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01U9keBRnGXBP2DiQmrcqm33
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
The design always called for two ways an agent reaches media. Only one was ever built. This adds the concept the second one needs — the byte-moving RPCs and the server-side install follow in the next two PRs.
Mount(default, unchanged)Stream(new)A streaming node needs no share, no credentials, and no knowledge of the server's storage layout.
The behavioural change is one line
Requirement::MountCoversno longer binds a streaming agent. Such a node advertises no mounts at all, so under the old rule it would satisfy nothing and be handed no work forever while reporting healthy.The requirement still rides on the job — the same job must stay dispatchable to a mount-mode agent, and the server needs the canonical path either way: to translate it, or to read the bytes from.
Why this is not theoretical
On 2026-08-10, all three of
windows-rtx2070's SMB mounts to the server reportedUnavailable. Windows drive mappings are per-logon-session, and the session an agent runs in is not the interactive one holding them. Under mount-only transport that node is undispatchable no matter how well its encoder works — and NVENC there is confirmed working (RTX 2070 SUPER, driver 610.47,hevc_nvencverified).Backward compatibility
#[serde(default)]on the domain type,TM_MOUNT = 0on the wire. An agent that has never heard of transports keeps exactly the behaviour it had. A new field that silently switched an existing agent to streaming would have been the worst possible way to ship this — there's a test asserting an older document still deserialises asMount.The conversion boundary
Both directions carry the field, with a note at each. That boundary is where the muxers field was dropped, and the failure shape was identical: an agent registering healthy and matching nothing.
The round-trip test now uses a streaming fixture, so dropping the field on the way out turns it red. Verified by doing exactly that:
Also fixed
agent surveytold a streaming node it was broken — with no mounts it printed "can be given no work", which sends an operator hunting a share problem on a node deliberately not using one. It now prints the transport and says the right thing for each.Verification
cargo test --workspace— 524 passing, 0 failed (was 517)cargo fmt -- --check— exit 0 ·cargo clippy --all-targets --all-features -- -D warnings— exit 0Mount🤖 Generated with Claude Code
https://claude.ai/code/session_01U9keBRnGXBP2DiQmrcqm33