feat(server): let sibling threads exchange messages - #6567
feat(server): let sibling threads exchange messages#6567brian-missionplus wants to merge 1 commit into
Conversation
|
Important Review skippedAuto reviews are disabled on this repository. Please check the settings in the CodeRabbit UI or the ⚙️ Run configurationConfiguration used: Repository UI Review profile: CHILL Plan: Pro Plus Run ID: You can disable this status message by setting the Use the checkbox below for a quick retry:
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
| commandType: command.type, | ||
| detail: "A thread cannot send a message to itself.", | ||
| }); | ||
| } |
There was a problem hiding this comment.
🟠 High orchestration/decider.ts:947
Relay commands for a deleted source or target are accepted and emit a message/turn request, because requireThreadNotArchived only checks archivedAt. A deleted thread remains in the read model, so explicitly reject deletedAt !== null for both relay endpoints before emitting these events.
🤖 Copy this AI Prompt to have your agent fix this:
In file @apps/server/src/orchestration/decider.ts around line 947:
Relay commands for a deleted source or target are accepted and emit a message/turn request, because `requireThreadNotArchived` only checks `archivedAt`. A deleted thread remains in the read model, so explicitly reject `deletedAt !== null` for both relay endpoints before emitting these events.
There was a problem hiding this comment.
Effect service conventions review: two error-modeling violations in the new thread relay toolkit. Details inline.
Posted via Macroscope — Effect Service Conventions
| export class ThreadRelayError extends Schema.TaggedErrorClass<ThreadRelayError>()( | ||
| "ThreadRelayError", | ||
| { | ||
| code: Schema.Literals([ | ||
| "source_unavailable", | ||
| "target_not_found", | ||
| "self_send", | ||
| "cross_project", | ||
| "query_failed", | ||
| "dispatch_failed", | ||
| ]), | ||
| detail: Schema.String, | ||
| }, | ||
| ) { | ||
| override get message(): string { | ||
| return this.detail; | ||
| } | ||
| } |
There was a problem hiding this comment.
ThreadRelayError stores an unstructured detail sentence and returns it verbatim as message, so the prose (not the structural attributes) is the only real data, and the code literal is what selects the caller-visible message. It also has no cause, so the underlying ProjectionRepositoryError / engine failure is lost for query_failed and dispatch_failed.
Consider splitting these into distinct Schema.TaggedErrorClass errors with stable structured fields (e.g. threadId, sourceThreadId/targetThreadId, and cause: Schema.Defect() on the two wrapping failures), deriving message from those fields, and exposing the tool failure channel as a Schema.Union — the same shape as PreviewAutomationError in packages/contracts/src/previewAutomation.ts. For example:
export class ThreadRelaySourceUnavailableError extends Schema.TaggedErrorClass<ThreadRelaySourceUnavailableError>()(
"ThreadRelaySourceUnavailableError",
{ threadId: ThreadId },
) {
override get message(): string {
return `Invoking T3 thread '${this.threadId}' is no longer active.`;
}
}
export class ThreadRelayQueryError extends Schema.TaggedErrorClass<ThreadRelayQueryError>()(
"ThreadRelayQueryError",
{ threadId: Schema.NullOr(ThreadId), cause: Schema.Defect() },
) {
override get message(): string {
return "T3 could not read the current thread catalog.";
}
}
export const ThreadRelayError = Schema.Union([
ThreadRelaySourceUnavailableError,
ThreadRelayQueryError,
// ...
]);Posted via Macroscope — Effect Service Conventions
| const queryFailure = () => | ||
| new ThreadRelayError({ | ||
| code: "query_failed", | ||
| detail: "T3 could not read the current thread catalog.", | ||
| }); | ||
|
|
||
| const dispatchFailure = () => | ||
| new ThreadRelayError({ | ||
| code: "dispatch_failed", | ||
| detail: "T3 could not durably accept the thread message.", | ||
| }); |
There was a problem hiding this comment.
queryFailure and dispatchFailure are helpers whose only behavior is () => new ThreadRelayError({ ... }), used solely via Effect.mapError (lines 46, 62, 123, 141). Because they ignore their argument, the incoming ProjectionRepositoryError / dispatch failure is discarded entirely rather than preserved as cause, and the same dispatch_failed error covers both UUID/clock acquisition and engine dispatch.
Consider deleting both helpers and constructing the error at each failure boundary with the real error attached, e.g. Effect.mapError((cause) => new ThreadRelayQueryError({ threadId, cause })), so the attributes and the full error chain stay visible at the site that failed.
Posted via Macroscope — Effect Service Conventions
No description provided.