Skip to content

feat(server): server-side scheduled tasks — start agent runs automati…

MacroscopeApp / Macroscope - Effect Service Conventions failed Aug 23, 2026 in 2m 37s

Effect Service Conventions: 4 issues found

Four convention violations were found in the new scheduled-tasks service code and reported as inline review comments.

  • apps/server/src/orchestration/Services/TaskScheduler.ts — new service defined with a standalone TaskSchedulerShape and split across Services/ + Layers/, with makeTaskSchedulerLive/TaskSchedulerLive instead of canonical make/layer.
  • apps/server/src/orchestration/Services/TaskFireReactor.ts — same split-module/*Shape/*Live pattern for a newly introduced service.
  • apps/server/src/persistence/Services/ProjectionTasks.ts — new repository service uses ProjectionTaskRepositoryShape plus Services/ + Layers/ split and ProjectionTaskRepositoryLive, diverging from the already-migrated persistence modules (AuthSessions.ts, ProviderSessionRuntime.ts).
  • apps/server/src/orchestration/Layers/ProjectionSnapshotQuery.ts — decodeTaskRows builds PersistenceDecodeError with issue copied from the message of an Error manufactured in parseScheduleJson, so the wrapper message is derived from cause.message rather than structural attributes.

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.

Methodology: reviewed every changed .ts/.tsx file in the include scope, focusing on newly created or consumed Effect services, and compared them against the canonical form already present in the repository.

Evidence that the canonical form is established here (so these are not legacy-only patterns):

  • apps/server/src/persistence/AuthSessions.ts, AuthPairingLinks.ts, ProviderSessionRuntime.ts — single module, interface inline in Context.Service, make, export const layer = Layer.effect(...).
  • apps/server/src/persistence/Layers/ProviderSessionRuntime.ts is explicitly marked as a deprecated compatibility alias for the migrated module.
  • ~150 modules across apps/, packages/, and infra/ already export make/layer from a single service module.

Checked and found acceptable:

  • Dependency acquisition in Layers/TaskScheduler.ts and Layers/TaskFireReactor.ts uses yield* Foo from the environment; makeTaskSchedulerLive({ tickIntervalMs }) injects pure configuration, not services.
  • Layer.succeed occurrences in tests and integration harnesses (OrchestrationEngineHarness, TaskScheduler.test.ts, server.test.ts, etc.) are legitimate test seams.
  • No ManagedRuntime.make, runPromise, or runPromiseExit introduced in domain/service code.
  • Effect module imports use subpath namespaces; @t3tools/contracts named imports are within convention.
  • OrchestrationListTasksError is a Schema.TaggedErrorClass with structured fields and a preserved cause.

🔍 Agent activity — 15 tool calls

💭 I'm checking the new service files, TaskFireReactor and TaskScheduler, against convention — they define standalone shape interfaces and split definitions across Services/ and Layers/ directories, when the convention wants inline interfaces combined into a single domain file. But the existing repo actually follows the Services/Layers split pattern already for things like ProjectionSnapshotQuery and ThreadDeletionReactor. Since the instructions say to treat the newer convention as authoritative for any PR creating or refactoring Effect services, and this PR creates TaskFireReactor, TaskScheduler, and ProjectionTaskRepository with FooShape interfaces and Live-suffixed layers, it seems right to flag these as violations despite matching old patterns elsewhere in the codebase. I want to check whether other minor things stand out too — the scheduler's layer construction accepting config options looks fine, the test using a stubbed layer for ProjectionSnapshotQuery is an acceptable seam, and the imports across these new files look properly scoped. One thing worth a closer look is how parseScheduleJson throws a plain Error inside an Effect.try that gets wrapped into a PersistenceDecodeError — I want to see if that's treated as a legitimate cause-population pattern or something to flag. I'm noticing that the wrapper copies cause.message directly into the issue field rather than deriving something structured, which brushes against the "don't just copy cause.message into a new field" guidance. There's also a raw throw new Error(...) for a missing task row that becomes a defect inside an Effect.gen, which I should compare against existing conventions elsewhere in the codebase — let me check how PersistenceDecodeError and its toPersistenceDecodeError helper are typically constructed to judge whether this is consistent or a deviation.

1. browse_code — apps/server/src/orchestration/Services/*.ts

2. browse_code — apps/server/src/orchestration/*.ts

💭 Since the repo mostly follows the Services/Layers split, I'm checking whether there's already a canonical single-file pattern with an inline interface and layer export by searching for export const layer = Layer usages.

3. git_tools — export const layer = Layer

4. git_tools — Shape>()(

💭 I'm noticing this repo is mid-migration, with the canonical single-file convention now dominant while older Services/+Layers/ patterns still linger in orchestration and persistence directories. The PR at hand adds new services that need converting to the canonical style, starting with TaskFireReactor which should be flattened into a single file with an inline interface, make, and layer. I'm also flagging TaskScheduler and ProjectionTasks as needing similar conversion, and noting that persistence files like AuthSessions.ts and ProviderSessionRuntime.ts already follow the single-file convention -- confirming that new split-file services should be flagged. I want to verify the exact persistence directory structure and check that the naming pattern like TaskFireReactorShape["Service"] matches convention. I'm also checking whether the new PersistenceDecodeError usage with issue: cause.message matches an existing pattern, and looking at how parseScheduleJson throws errors with a cause field to see if that aligns with how errors are handled elsewhere in the codebase. I'm now examining whether creating a plain Error just to carry a cause and then mapping it into a PersistenceDecodeError with issue: cause.message is really adding value, or if it's just duplicating the message field unnecessarily -- it's a bit of a borderline case. I also notice Schema.decodeUnknownSync being used synchronously inside a service, which seems to go against typical Effect patterns favoring Schema.decodeUnknownEffect. I want to look at the persistence Errors module and existing decode helpers to confirm expected conventions, while setting aside the new React component in apps/web since it's not a service.

5. browse_code — apps/server/src/persistence/Errors.ts

6. browse_code — apps/server/src/persistence/**/*.ts

💭 I notice that small file is probably just a re-export shim, which isn't really my concern here. But the bigger issue is that the new services in this PR follow the legacy split-file pattern rather than the canonical single-file style with inline interface, make, and layer—which does seem like a real violation given the instructions apply whenever a PR creates or refactors an Effect service. Since this is a feature PR, not a cleanup PR, I want to be careful only to flag the newly created services rather than demand repo-wide changes. Let me actually look at the new service files themselves so I can write precise, targeted comments.

7. browse_code — apps/server/src/orchestration/Services/TaskScheduler.ts

8. browse_code — apps/server/src/orchestration/Services/TaskFireReactor.ts

9. browse_code — apps/server/src/orchestration/Services/ThreadDeletionReactor.ts

💭 Since TaskFireReactor and TaskScheduler mirror ThreadDeletionReactor exactly, these are new services following the legacy split-file Shape convention, so flagging them is legitimate rather than unrelated cleanup. I want to check the corresponding Layers files for these two to see if they use "Live" naming versus "layer" naming. I also notice makeTaskSchedulerLive isn't exported as a plain make with a separate layer export, which fits the same naming-convention finding. And in ProjectionSnapshotQuery's error handling, the issue field just copies cause.message, which violates the convention against duplicating error details that get reused to build the wrapper message. I'm weighing whether parseScheduleJson manufacturing a synthetic Error to wrap a schema parse failure is itself a violation — it likely should construct PersistenceDecodeError directly from the SchemaError rather than throwing and catching an intermediate Error just to harvest its message. This seems like a legitimate, moderately strong finding since it's new code in a touched file, though the correlation type may need a taskId field added to fully support it. The getCommandReadModel throwing on corrupt JSON also seems like a deliberate loud-failure choice given corrupted schedule data represents a database integrity issue. I'm also checking the catchCause usages in TaskScheduler and TaskFireReactor — these seem intentional since they handle the whole error channel including defects, so I won't flag those, and I'm scanning the web file for other issues like the random ID generation not going through an Effect service. Continuing through the other files, I see the RpcClientError import and Crypto import both look fine, and I'm now checking the forkParked import in TaskScheduler.ts. The remaining pattern is the same TaskSchedulerShape["tick"] usage that should be TaskScheduler["Service"]["tick"], part of the same finding. I want to compare ProjectionTaskRepository's service definition against an already-migrated module like AuthSessions.ts to see the canonical form for these interfaces.

10. browse_code — apps/server/src/persistence/AuthSessions.ts

11. browse_code — apps/server/src/persistence/Layers/ProviderSessionRuntime.ts

💭 I've confirmed the canonical convention already exists in the repo, seen clearly in AuthSessions.ts with its single-file, inline-interface, make/layer pattern—while the new task services deviate from this and follow a deprecated structure instead. Now I'm working through the specific findings to flag, starting with ProjectionTasks.ts, which splits into standalone shape/service/layer files rather than following the established convention. I identify TaskScheduler.ts and TaskFireReactor.ts as having the same structural deviation, then turn to a decoding issue in ProjectionSnapshotQuery.ts where errors are manufactured artificially just to carry context, when a cleaner approach would use schema-based decoding so the error structure derives naturally from the actual SchemaError as cause. I'm also weighing whether other areas like TaskSchedulerShape's tick method or the Layer.succeed test usages warrant flagging, concluding those look fine, then start considering another file in the orchestration layers. I'm now planning the four inline comments I'll post, working out the correct line ranges for each new file so the comments align properly on the diff.

12. browse_code — apps/server/src/persistence/Services/ProjectionTasks.ts

13. browse_code — apps/server/src/orchestration/Layers/ProjectionSnapshotQuery.ts

💭 Now I need to locate the line numbers for decodeTaskRows in ProjectionSnapshotQuery.ts at head.

14. git_tools — decodeTaskRows|parseScheduleJson

💭 Now I'm working out which line ranges to anchor my review comments on, since I need them to fall within the diff's added lines. For the ProjectionSnapshotQuery.ts changes, I'll target the added lines around the Effect.try block, roughly lines 2419-2431. I'm drafting four review comments: three flag new service files (TaskScheduler.ts, TaskFireReactor.ts, ProjectionTasks.ts) that split shape and layer definitions across separate Services/ and Layers/ files rather than following the codebase's convention of a single module with inline Context.Service interface, exported make, and exported layer -- pointing to persistence/AuthSessions.ts as the canonical pattern. The fourth comment addresses the decodeTaskRows logic in ProjectionSnapshotQuery. I'll keep these concise without inline diff suggestions since they involve cross-file restructuring, and mark the overall review status as requesting changes due to these convention violations.

15. modify_pr — Effect service conventions: 4 findings on the newly introduced task services. The three new services are defined in the legacy Services/ + Layers/ split with standalone *Shape interfaces and *Live layer names, instead of the canonical single-module form (inline interface in Context.Service, exported make, exported layer) already used by e.g. apps/server/src/persistence/AuthSessions.ts. One error-construction site copies cause.message into a structural field.


Agent Credits: 133 credits