Skip to content

fix(server): skip redundant Hermes session/set_model

MacroscopeApp / Macroscope - Effect Service Conventions failed Sep 2, 2026 in 4m 8s

Effect Service Conventions: 2 issues found

  • apps/server/src/provider/Drivers/HermesDriver.ts — duplicates the canonical withInstanceIdentity helper from Drivers/instanceIdentity.ts instead of importing it; every other driver (Codex, Claude, Cursor, Grok, OpenCode) uses the shared module. Inline comment posted with the fix.
  • apps/server/src/provider/Layers/HermesAdapter.ts — new backend behavior (steer/merge turn accounting via promptsInFlight, plan fingerprint de-duplication, full-access permission auto-approval, ACP mode alias resolution) ships without any adapter test, unlike every peer adapter. Raised in the review body.

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.

Scope reviewed: all new/changed TypeScript under apps/** and packages/** in this PR (HermesDriver, HermesAdapter, HermesProvider, HermesAcpSupport, HermesTextGeneration, Services/HermesAdapter, serverSettings, contracts/settings, updated tests).

Checked and found conforming (compared against the established Codex/Cursor/Grok equivalents at the merge base):

  • Imports: Effect modules are imported as subpath namespaces (effect/Effect, effect/Layer, effect/unstable/process/ChildProcessSpawner); @t3tools/contracts uses named imports, which the conventions permit.
  • Service definition/layout: Hermes ships no new Context.Service tag; Services/HermesAdapter.ts only re-declares the shared ProviderAdapterShape alias, mirroring Services/GrokAdapter.ts, and the driver bundles the adapter per instance. No compatibility shims or leftover files.
  • Dependency acquisition: makeHermesAdapter, makeHermesTextGeneration, and checkHermesProviderStatus all acquire Crypto, ChildProcessSpawner, FileSystem, Path, ServerConfig, and ServerSettingsService from the Effect environment. The childProcessSpawner/httpClient parameters on makeHermesAcpRuntime and enrichHermesSnapshot reproduce the existing GrokAcpSupport/CursorProvider signatures required by AcpSessionRuntime.layer, and the resolveSettings option is the same documented test seam used by CursorAdapter/OmpAdapter. No ManagedRuntime.make, runPromise, or runPromiseExit was introduced.
  • Errors: failures are Schema.TaggedErrorClass instances with structural fields and a preserved cause; Effect.catchTags is used for the known SchemaError tag; no new lint/type-checker suppressions were added beyond the existing @effect-diagnostics nodeBuiltinImport:off in the test file, which matches sibling test modules.
  • Schema.is usage (isTextGenerationError) is a direct predicate, not a redundant wrapper function.

🔍 Agent activity — 25 tool calls

💭 I'm looking at the diff across HermesDriver.ts, HermesAdapter.ts, HermesProvider.ts, and related acp/text-generation files to check for Effect service convention violations, keeping in mind this PR largely mirrors the existing Grok/Cursor driver pattern in the repo. I want to verify against the analogous Grok files before flagging things: the HermesAdapterShape interface in Services/HermesAdapter.ts might just mirror an existing GrokAdapterShape, and the manual Layer.build plus passing a raw ChildProcessSpawner service instance into makeHermesAcpRuntime could similarly be a pattern already established in GrokAcpSupport rather than a new violation. I'm also noting the error construction using template-string details in ProviderDriverError as a potential issue to check against existing error-handling conventions. I'm also spotting several places where cause.message gets copied straight into an error's detail field — in the adapter's process error, the request error during sendTurn, and the driver's snapshot-build failure — all of which look like they violate the rule against deriving wrapper messages from cause.message rather than stable structural data. Meanwhile the Effect.catchTags and Effect.catch usages in the text-generation and adapter files seem fine, and the private Schema.is predicate constant appears to be used directly rather than wrapped redundantly, so that's probably not a problem. I'm moving on to check the imports in HermesAdapter.ts, starting with the ChildProcessSpawner namespace import. I should treat the instructions as authoritative even where older code diverges, focusing only on concrete violations within the PR's changed scope rather than demanding unrelated cleanup — and since these are new files, they count as changed scope. Time to actually dig into the repo itself.

1. browse_code — apps/server/src/provider/**/Grok*.ts

2. browse_code — apps/server/src/provider/acp/*.ts

💭 I want to check GrokAcpSupport.ts and GrokDriver.ts for comparison.

3. browse_code — apps/server/src/provider/acp/GrokAcpSupport.ts

4. browse_code — apps/server/src/provider/Drivers/GrokDriver.ts

💭 I notice HermesDriver duplicates a local withInstanceIdentity instead of importing the shared helper GrokDriver uses from instanceIdentity.ts. That's duplication rather than an actual Effect Service Convention violation, so I want to check instanceIdentity.ts to see if it's worth flagging anyway. The Hermes snapshot error's detail interpolates cause.message, mirroring an existing repo pattern in Grok — flagging it is defensible under the guidance against copying cause.message, but since this seems to be a wholesale copy of a repo-wide convention, I should check ProviderDriverError and other drivers' detail usage before deciding whether to report it.

5. git_tools — Failed to build .* snapshot

6. browse_code — apps/server/src/provider/Services/GrokAdapter.ts

💭 I notice HermesDriver and its adapter follow the same uniform pattern used across all six drivers, so that's not a deviation worth flagging—it's just the repo convention. But I'm second-guessing whether to flag the detail: Failed to build X snapshot pattern, since the guidance explicitly says not to add a detail field that merely duplicates cause.message. I want to be careful about false positives, so I'm narrowing my focus to genuine Hermes-specific deviations from the repo's own conventions and the explicit rules. One thing that stands out: HermesDriver defines its own local withInstanceIdentity helper instead of importing the shared instanceIdentity.ts module, which could be duplicated code if that shared file already existed at the merge base—I need to check its git history to confirm whether this duplication was actually introduced by this PR. Since GrokDriver at the PR head does import the shared helper from instanceIdentity.ts, HermesDriver's local duplicate version (which also stamps a driver: DRIVER_KIND field) is a real, if minor, inconsistency worth flagging. Checking the rest of the Hermes files against the Grok equivalents—the adapter's service usage, the text generation factory, the provider status check, and the ACP model discovery pattern—everything else appears to mirror the established conventions correctly. I'm also weighing whether discoverHermesModelsViaAcp accepting a raw ChildProcessSpawner service type should be flagged, but since it matches the repo's existing pattern for Grok and Cursor ACP support (driven by what AcpSessionRuntime.layer requires), I won't call it out as a new issue. Now I'm scanning the diff for genuinely novel deviations, checking things like import naming and ordering conventions in HermesAdapter.ts. I notice HermesAdapter.ts constructs ProviderAdapterProcessError with a detail field that just copies cause.message, which runs against the convention against duplicating the cause's message into a separate detail field used for the wrapper message. That looks like a real violation worth flagging. I want to check whether GrokAdapter follows this same pattern to see if it's a repo-wide convention or a real inconsistency, so I should grep for detail: cause.message across the codebase. I'm also noting that the Hermes ACP CLI probe test file mirrors the structure used in the Grok probe test, and that HermesProvider.ts threads an HttpClient.HttpClient service through via Effect.provideService. Now I'm considering whether HttpClient.HttpClient as a type here refers to the service tag's interface type in this version of Effect, since the driver yields the actual service instance and passes it into enrichHermesSnapshot as a plain parameter. This looks like the kind of manual service injection pattern the rule about flagging factories accepting Foo["Service"] would call out, but before flagging it I want to grep for enrichGrokSnapshot to confirm it follows the same convention.

7. git_tools — detail: cause.message

8. browse_code — apps/server/src/provider/Drivers/instanceIdentity.ts

💭 I'm checking whether the shared instanceIdentity.ts helper already existed at the merge base, since HermesDriver duplicates its logic locally — since the file isn't part of the diff but exists at head, it must have already been present at the merge base, unless the PR branch merged main in afterward. Stepping back, I'm weighing whether flagging this duplication actually fits the review scope, which focuses on Effect service conventions like imports, service definition, dependency handling, error patterns, and file layout. It's not explicitly listed but feels like a legitimate change-discipline concern around reusing canonical modules, so I want to verify it at merge base before deciding whether to flag it. Then I want to move on and scrutinize the other new Hermes files at head more closely, including checking the enrichGrokSnapshot signature for an httpClient parameter.

9. git_tools — withInstanceIdentity

10. git_tools — withInstanceIdentity

💭 It's odd that HermesDriver.ts seems to already exist at the merge base per the grep, even though the diff lists it as a new file. I suspect the grep tool isn't actually resolving to the tree at that specific commit despite passing the URL with /tree/, so I should double-check by explicitly specifying the rev parameter.

11. git_tools — withInstanceIdentity

💭 I confirm that at the merge base, HermesDriver.ts didn't exist and all five drivers relied on the shared withInstanceIdentity helper — but this new driver introduces its own duplicate local copy instead of reusing the canonical module. That's a legitimate finding worth flagging as a minor issue, since it fits the convention of keeping construction logic centralized rather than duplicated. Now I want to check for stronger findings by comparing other Hermes files against their Grok counterparts — looking at GrokProvider.ts's enrichGrokSnapshot for httpClient parameter consistency, comparing GrokTextGeneration.ts with HermesTextGeneration.ts, and verifying whether AcpSessionRuntime.ts properly exports its options type and layer as expected.

12. browse_code — apps/server/src/textGeneration/GrokTextGeneration.ts

💭 HermesTextGeneration mirrors the same pattern closely, just resolving environment slightly differently, and it hardcodes the mode while silently swallowing any errors from setting it via Effect.ignore — nothing in the conventions explicitly bars that, so I'm not sure it counts as a violation. Now I'll move on to checking GrokProvider's enrich signature.

13. git_tools — export const enrich(Grok|Cursor|Codex|OpenCode|Claude)Snapshot

💭 HermesProvider's enrich logic mirrors CursorProvider exactly, confirming the established pattern, with the main deviation being a local duplicate of withInstanceIdentity instead of using the shared instanceIdentity.ts. Now I'm checking HermesAdapter.ts against CursorAdapter/GrokAdapter for anything unusual in the server connection handling. I notice a more interesting issue: discoverHermesModelsViaAcp uses process.cwd() directly instead of getting cwd from the ServerConfig service like Grok's equivalent does, which could be a convention violation around hiding dependencies in module globals. I should check how Cursor's ACP discovery handles cwd, so let me search for process.cwd() usage across the provider directory.

14. git_tools — process.cwd()

15. git_tools — discoverCursorModelsViaAcp|checkCursorProviderStatus =

💭 HermesProvider follows the same pattern as CursorProvider, including its use of process.cwd(), so that's consistent with established convention. The main deviation I'm noticing is the duplicated withInstanceIdentity logic in HermesDriver.ts rather than importing the shared helper — I want to scan the rest of the new Hermes code for similar issues. I'm also comparing HermesAdapter against GrokAdapter to see how each handles Effect.service(ServerConfig), so I'll search the repo for other usages of that pattern.

16. git_tools — Effect.service(ServerConfig)

17. git_tools — const serverConfig = yield*

💭 The main concrete deviation I found is the duplicated withInstanceIdentity in HermesDriver.ts. Now I'm checking whether the test-only resolveSettings hook in HermesAdapterLiveOptions has an equivalent in GrokAdapter's options, since introducing a test-only escape hatch into production code would be a mild concern if unique to Hermes. I'm also noting that HermesTextGeneration swallows errors by ignoring the result of runtime.setMode("ask"), which Grok doesn't do and isn't in the listed conventions -- worth flagging as an unlisted deviation.

18. git_tools — resolveSettings

💭 I notice HermesAdapter has no corresponding test file, while CursorAdapter and GrokAdapter both do. Since this PR introduces a whole new provider adapter with substantial backend logic but zero unit tests, that's a legitimate gap under the change discipline requirement that new backend behavior needs focused tests. The duplicated helper is a smaller, more clear-cut convention violation worth flagging inline, whereas the missing adapter tests feel more like a summary-level note given the ambiguity around scope. I'm now cross-checking the error constructions in HermesAdapter against sibling adapters like Cursor and Grok to see if the patterns genuinely match, since the detail/cause fields look consistent with existing precedent so far. I'm confirming HermesDriver is properly registered in builtInDrivers and checking whether its environment dependencies, and the provider status check requirements, line up with what Grok's adapter does, since everything so far checks out as matching the established conventions. Though this is technically outside the scope of checking Effect service conventions, I want to quickly verify whether the client has a per-provider registry (icons, accent colors) that also needs a hermes entry, since a missing registration there would be a real bug even if unrelated to the service pattern itself — I'll grep for "grok" in apps/client to see if such an enumeration exists.

19. git_tools — "grok"

💭 With no client enumeration issue, I want to check packages/contracts for other places where provider kinds might be enumerated that hermes needs to be added to, like a provider order list or display settings, beyond the ProviderDriverKind branded string which already looks fine.

20. git_tools — grok

💭 I'm checking the driver-kind-to-default-model mapping and whether defaultEnabledForDriver in settings.ts accounts for hermes, since the PR's settings test change didn't seem to add it there.

21. git_tools — defaultEnabledForDriver

22. browse_code — packages/contracts/src/model.ts

💭 The missing default model for Hermes seems intentional given the docs, so it's not a real convention issue. My main finding is that HermesDriver.ts duplicates the shared withInstanceIdentity helper instead of importing it like the other five drivers do — that's concrete and actionable with a clear fix. I'm now weighing whether to also flag the missing HermesAdapter tests as a separate point. I'm considering whether the brand-new 1060-line HermesAdapter lacking any test file is worth flagging, given every other adapter (Cursor, Grok, Codex, Claude, OpenCode) has extensive tests. It's more of a "change discipline" gap than an Effect service convention violation, so rather than an inline comment I'll note it in the review body instead. First let me confirm there's really no Hermes test file anywhere by globbing for it.

23. browse_code — **/Hermes

💭 No HermesAdapter.test.ts exists. Now I'm drafting the review, flagging the duplicated helper in HermesDriver.ts around line 76 where the instance identity wrapper starts, and I need to check the exact file range to craft a precise inline suggestion.

24. browse_code — apps/server/src/provider/Drivers/HermesDriver.ts

💭 Since the fix touches multiple spots—adding the import, passing driverKind: DRIVER_KIND at the call site, and removing the now-unused ServerProvider/ServerProviderDraft type imports—this isn't a clean single-hunk change, so I'll describe it as a unified diff rather than an inline suggestion. I'm drafting the review comment pointing out that HermesDriver duplicates withInstanceIdentity instead of using the shared helper in instanceIdentity.ts that every other driver already imports, and recommending it switch over. I'm also noting a second issue: there are no tests for HermesAdapter, despite it introducing nontrivial new behavior like steering turn merging, plan fingerprint dedupe, full-access auto-approval selection, and mode alias resolution—none of which is currently covered. I'm deciding to post the duplicated-helper issue as an inline comment and the missing test coverage as a body note, and I'm settling on marking the overall check as failure since these are genuine convention violations and the check defaults to failure when findings exist.

25. modify_pr — New Hermes adapter code (apps/server/src/provider/Layers/HermesAdapter.ts, ~1060 lines) ships without a HermesAdapter.test.ts, while every peer adapter (Codex, Claude, Cursor, Grok, OpenCode) has one. The adapter carries non-trivial new backend behavior that the existing Hermes tests (provider snapshot, ACP support, text generation) do not exercise: the promptsInFlight steer/merge rule in sendTurn (turn id reuse and "only the last prompt settles the turn"), the lastPlanFingerprint de-duplication in emitPlanUpdate, selectAutoApprovedPermissionOption under full-access, and the mode-alias resolution in resolveRequestedModeId. Consider adding focused tests for at least the steer path and the auto-approval selection against the existing ACP mock agent.


Agent Credits: 235 credits

Token usage: 36 uncached input · 1,876,017 cache-read input · 146,423 cache-write input · 15,070 output
Agent Credits may also include non-token charges from external tools such as web research.