Skip to content

Put Claude behind an AgentProviderAdapter so a second product is an impl - #71

Merged
ndr-ds merged 1 commit into
mainfrom
ndr-ds/agentctl-provider-adapter
Aug 24, 2026
Merged

ndr-ds merged 1 commit into
mainfrom
ndr-ds/agentctl-provider-adapter

Conversation

@ndr-ds

@ndr-ds ndr-ds commented Aug 24, 2026

Copy link
Copy Markdown

Task 5 of the multi-provider plan: Claude's product-specific behaviour moves behind one interface, so the second product becomes an implementation rather than a new branch in each of discovery, process, transcript and terminals.

Parity holds by construction, not by inspection

Every ClaudeProvider method delegates to the function that already implemented it:

fn matches_process(&self, command: &str) -> bool {
    crate::process::is_claude_process(command)
}

Nothing was moved into the adapter. There is still exactly one implementation of each behaviour, now reachable through a trait — so "did the extraction change anything?" is answerable by reading the delegations rather than by diffing logic.

Trait methods return plain data and do no I/O; spawning, reading and writing stay in the wrappers that already own them.

A missing adapter is None, not a stand-in

pub fn for_provider(provider: AgentProvider) -> Option<&'static dyn AgentProviderAdapter> {
    match provider {
        AgentProvider::Claude => Some(&claude::ClaudeProvider),
        AgentProvider::Codex => None,
    }
}

My first draft mapped Codex to ClaudeProvider "since nothing constructs a Codex session yet". That is the silent-wrong-answer shape: it would parse a Codex transcript with Claude's schema and launch the wrong binary, succeeding at both. None makes callers decide, and codex_has_no_adapter_yet_and_does_not_fall_back_to_claude is the tripwire that forces Task 6 to fill it in.

Written test-first

The contract tests were written before the module existed and confirmed red (unresolved import agentctl::providers). They state what Claude does today, so they are parity tests first and design tests second — notably that process matching keeps its current strictness, rejecting claudectl --list, agentctl --list, grep claude and /usr/bin/env claude, and that resume precedes prompt in the launch argv.

Proven to catch a real regression by making the adapter report Codex's transcript layout:

test the_claude_adapter_reports_claudes_identity ... FAILED
test discovery_still_scans_the_same_claude_directory ... FAILED

That second test pins discovery::projects_dir() to the literal path it returned before the change — a wrong transcript root would otherwise surface as an empty dashboard, not a failing test.

One test was also checked for vacuity: claude_transcript_parsing_is_unchanged compared two is_some() calls, which would hold just as well if the sample stopped parsing and both sides went None. It now asserts the positive case first.

Dispatch, and what is deliberately deferred

Two call sites now go through the adapter: discovery::projects_dir and the executable in terminals::build_claude_command.

The remaining sites — is_claude_process in the ps scan, and build_claude_args in the five terminal backends — are not converted here. Neither has an AgentProvider in hand at the point of call; supplying one is process discovery, which is exactly what Task 6 specifies. Threading it now would mean inventing that design a task early and guessing at it.

Verification

cargo fmt --check and cargo clippy --all-targets -- -D warnings clean — clippy caught an unused trait import, since &dyn Trait method calls resolve without the trait in scope.

753 lib tests pass plus 8 new contract tests. The two usage_ledger perf failures are the known pair on a loaded box (this run took 960s): perf_eviction_stays_linear_with_many_rows is a wall-clock assertion, and when it panics it poisons cache_test_lock, taking perf_summarize_cached_costs_far_less_than_parsing with it. Both pass single-threaded and predate this branch.

@ndr-ds
ndr-ds marked this pull request as ready for review August 24, 2026 17:59
@ndr-ds
ndr-ds merged commit 3d5383c into main Aug 24, 2026
5 checks passed
@ndr-ds
ndr-ds deleted the ndr-ds/agentctl-provider-adapter branch August 24, 2026 17:59
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