Skip to content

feat(providers): add ACP registry driver for Gemini, Pi, and more

MacroscopeApp / Macroscope - Effect Service Conventions failed Aug 10, 2026 in 3m 10s

Effect Service Conventions: 2 issues found

Two convention violations in newly added server code, reported inline:

  • apps/server/src/provider/acp/GenericAcpSupport.ts — makeGenericAcpRuntime accepts a ChildProcessSpawner.ChildProcessSpawner["Service"] instance and re-provides it via Layer.succeed, so an owned Effect dependency is injected as a value and kept out of the returned Effect's requirements. Production callers (GenericAcpAdapter.startSession, AcpRegistryProvider.discoverModelsViaAcp) acquire the spawner from the environment only to pass it back in.
  • apps/server/src/provider/Layers/GenericAcpAdapter.ts (lines 626 and 941) — ProviderAdapterProcessError / ProviderAdapterRequestError are constructed with detail: cause.message, so the wrapper's caller-visible message is derived from the cause instead of stable structural attributes.

Note (not commented inline): apps/server/src/provider/Drivers/AcpRegistryDriver.ts builds ProviderDriverError.detail from cause.message ?? String(cause). It mirrors the existing drivers, but new code should use a fixed structural detail and preserve the failure only as cause.

Details

Note

Your check run agent prompt is: .macroscope/check-run-agents/effect-service-conventions.md
More information about how Check Run Agents work can be found in our Help Center.

Files examined in scope: apps/server/src/provider/Drivers/AcpRegistryDriver.ts, apps/server/src/provider/Layers/AcpRegistryProvider.ts, apps/server/src/provider/Layers/GenericAcpAdapter.ts, apps/server/src/provider/acp/GenericAcpSupport.ts, apps/server/src/provider/acp/AcpRegistryCatalog.ts, apps/server/src/provider/acp/AcpSessionRuntime.ts, apps/server/src/provider/builtInDrivers.ts, apps/server/src/ws.ts, apps/server/src/auth/RpcAuthorization.ts, packages/contracts/src/{acpRegistry,rpc,settings,model,index}.ts, and the touched web/mobile modules.

Checked and found acceptable:

  • Namespace subpath imports for Effect modules; named imports retained for @t3tools/contracts, schemas, and pure helpers.
  • Effect.service(ServerConfig) / yield* Crypto.Crypto / yield* ChildProcessSpawner.ChildProcessSpawner acquisition inside the driver and adapter constructors; HttpClient acquired from the environment in listAcpRegistryCatalog.
  • No ManagedRuntime.make, runPromise, or runPromiseExit introduced in domain/service code.
  • Effect.provideService(Crypto.Crypto, ...) / Effect.provideService(Scope.Scope, sessionScope) in the driver and adapter are legitimate scope/context plumbing for effects handed to the managed-provider machinery.
  • Error channel handling uses Effect.catch/Effect.catchCause where the whole channel is intentionally handled; no catchTag/predicate-based recovery of known tags was introduced.
  • New tests exercise real behavior (snapshot states, ACP prompt flow against the mock agent, catalog merging, launch-arg parsing) rather than restating a mechanical refactor.

Search performed for the risk patterns named in the check: service-instance parameters (["Service"] in factory inputs), Layer.succeed, ManagedRuntime.make, .runPromise/.runPromiseExit, and detail: cause.message.


🔍 Agent activity — 1 tool call

💭 Looking at the diff to identify where the Effect service conventions are being violated across these provider files. Checking the import patterns — the code uses both import * as ChildProcessSpawner from "effect/unstable/process/ChildProcessSpawner" and consolidated named imports from barrel exports, which aligns with the repo's existing patterns. The key issue appears in GenericAcpSupport.ts where makeGenericAcpRuntime accepts a ChildProcessSpawner.ChildProcessSpawner["Service"] parameter and then constructs a layer from it, which suggests the service is being passed in rather than properly layered through the Effect runtime. I'm noticing that discoverModelsViaAcp relies on process.env and process.cwd() directly instead of injecting them as services, which hides these dependencies in Node globals rather than making them explicit in the Effect environment. This might be an existing pattern in the codebase, but it still violates the principle of not concealing dependencies. Then there's the error handling in ProviderDriverError that seems to be cutting off mid-violation. Looking at the error construction patterns, I'm seeing a consistent issue: multiple places are deriving the detail field directly from cause.message and then using that to build the wrapper error message. This happens in GenericAcpAdapter with ProviderAdapterProcessError, in the fileSystem.readFile error handling with ProviderAdapterRequestError, and it looks like there's another instance starting with EffectAcpEr that got cut off. All of these violate the rule about not copying cause.message into the detail field. Moving past the error construction violations, I'm checking the other patterns: the error normalization helpers like mapAcpCallbackFailure look fine, the Effect error handling with catch and catchCause is appropriate, and now I'm examining whether GenericAcpAdapter and its factory function makeGenericAcpAdapter follow the service module conventions properly. It's not a Context.Service, just an adapter factory that returns a ProviderAdapterShape, which seems consistent with how other adapters like CodexAdapter are structured, but I should verify that pattern. The AcpRegistryProvider.ts file placement in the Layers/ directory matches the existing convention for providers. I'm also looking at how Layer.build is used inside makeGenericAcpRuntime to inject the ChildProcessSpawner service, and how the session setup explicitly provides the Crypto and Scope services — that's a valid approach even if slightly unconventional. Now I'm checking how the provider instances are being merged together. Looking at the contracts in acpRegistry.ts and… I'm noticing that the new AcpRegistryDriver follows the same pattern as CursorDriver by including a detail field that copies cause.message, which is an established convention in the codebase. But the linting rule explicitly prohibits adding a detail field that merely duplicates cause.message, so this is a concrete violation that needs to be flagged even though it mirrors existing code. For GenericAcpSupport, the childProcessSpawner service injection pattern matches CursorAcpSupport and GrokAcpSupport exactly, and the rule is clear that this type of implementation dependency injection should be flagged regardless of whether it's an established pattern elsewhere in the repo. The injection itself is actually avoidable — instead of providing the spawner directly, they could just add ChildProcessSpawner to the return type requirements and let the caller handle it, which would be cleaner. Now I'm checking other potential issues like the naming conventions for adapter factories to see if there are other violations worth flagging.… The process.env and process.cwd patterns are already established elsewhere in the codebase like GrokProvider, so that's not a new issue. The detail: cause.message approach is also widespread, but I'm concerned about the risk here—the check should flag concrete violations that are either introduced or kept in the PR's changed scope. The new code in AcpRegistryD does add a detail… The issue is that these error classes require a detail field by design, so when wrapping lower-level errors, the code currently copies cause.message into detail. But the convention forbids this pattern. The fix is to provide meaningful structural context in the detail string instead—something like describing what operation failed rather than just echoing the underlying error message. I'm spotting a few instances of this in the new code: AcpRegistryDriver uses a template literal with cause.message, and GenericAcpAdapter does it in both ProviderAdapterProcessError and ProviderAdapterRequestError. There's also a case where raw wire payload gets encoded into a runtime event detail, which violates the convention against copying raw payloads into detail fields—though I should check if existing code already follows this pattern. Now I'm considering whether the GenericAcpSupport service injection itself is worth flagging as a structural concern. r Effect.succeed in withDecodingDefault, which is fine. The schema imports like TrimmedNonEmptyString from baseSchemas are allowed. Checking the web files, the named imports from @t3tools/contracts in AddProviderInstanceDialog.logic.ts are also acceptable. The existing pattern of importing ChildProcessSpawner from effect's unstable process module in AcpRegistryDriver matches what CursorDriver already does, so I'll skip that. GenericAcpAdapter correctly uses Effect.service to access ServerConfig, and I need to verify there aren't any helpers being added that just wrap error construction. Looking at the unsupported text generation helper — it's a small wrapper used multiple times that adds shared detail, which seems reasonable for reuse. The mapAcpCallbackFailure normalization wrapper is also fine. Checking AcpRegistryCatalog, the listAcpRegistryCatalog function properly uses HttpClient from the environment without error modeling issues.… I'm seeing that this pattern shows up across three existing modules, so it's genuinely pervasive. The tricky part is that GenericAcpSupport's service injection approach is technically defensible—it's in the changed scope and follows the instructions about flagging when older code differs. But it's also just copying what the rest of the codebase does, and the guidelines explicitly say not to demand unrelated repository-wide cleanup or fail on legacy style preferences. I'm leaning toward reporting it since the new module is in changed scope, but I need to think through whether that's the right balance here. The core question is which findings I actually want to stand behind. GenericAcpSupport accepting childProcessSpawner: ChildProcessSpawner["Service"] and providing it via Layer.succeed is a clear rule match—ChildProcessSpawner is an Effect service that AcpSessionRuntime.layer requires, and callers do yield it directly. I'll flag it with a concrete suggestion to drop the field and let the requirement flow through the Effect signature instead, since that mirrors how the pattern should work. For the detail: cause.message instances across GenericAcpAdapter and AcpRegistryDriver, the rule explicitly calls this out as problematic. I'm seeing three near-identical clones of existing repo idiom, which makes me hesitant, but the rule is clear—don't copy cause.message into detail and then use it to construct the wrapper message. I'll report all three concisely with inline comments on the new lines in the diff, though I'm reconsidering the strength of this finding since the convention in the codebase seems to derive the message from structural attributes rather than just copying the cause. I'm settling on reporting three inline findings: the service injection issue in GenericAcpSupport.ts and the two detail: cause.message violations in GenericAcpAdapter.ts, while noting the AcpRegistryDriver pattern in the summary since it mirrors existing driver implementations. Now I need to check whether TextGenerationError's detail field has similar issues.… I need to add line numbers for… I'm working through the code review now. For the GenericAcpSupport.ts file, I need to flag line 30 and reference line 58, then provide a fix for removing an interface field—since it spans multiple hunks, I'll describe the change rather than show a full diff. For the other comments around line 626, I can suggest inline fixes for the cause.message references since those are self-contained single-line changes.…

1. modify_pr — Reviewed the new Effect service code in this PR (AcpRegistryDriver.ts, AcpRegistryProvider.ts, GenericAcpAdapter.ts, GenericAcpSupport.ts, AcpRegistryCatalog.ts, contracts/settings/rpc additions). Module namespace imports, Effect.fn usage, layer/Effect.service acquisition in the driver and catalog, and Schema.TaggedErrorClass usage all look consistent with the conventions. Two convention issues, both in newly added server code: 1. GenericAcpSupport.makeGenericAcpRuntime takes a ChildProcessSpawner["Service"] instance and re-provides it through Layer.succeed, so an owned Effect dependency is injected as a value instead of being required from the environment. 2. GenericAcpAdapter wraps failures with detail: cause.message, which makes the wrapper's caller-visible message a restatement of the cause instead of stable structural context. AcpRegistryDriver.ts has the same cause-derived detail shape (detail: \Failed to build ACP Registry snapshot: ${cause.message ?? String(cause)}`); it mirrors the existing drivers, so it is noted here rather than commented inline, but new code should prefer a fixed structural detail with the failure preserved only as cause`.


Agent Credits: 152 credits