> **For OMP:** Create one isolated task worktree per implementation task. Run dependent tasks serially, merge each completed task branch into `feat/signals`, resolve integration conflicts on that branch, and verify the merged result before dispatching the next dependent task.
**Goal:** Build the first product-grade Signal slice: the authenticated global agent turns one or more exact user messages into one durable, Effect-modeled Signal containing immutable raw evidence and a separate processed problem statement.
**Architecture:** Keep the Signal domain pure in `@code/primitives`; keep authorization, idempotency, storage, and source resolution in Convex; keep conversation transport and the agent decision in Flue. The global Zopu agent becomes organization-scoped and receives two narrow tools: list eligible user-message sources, then create a Signal from selected message IDs and a processed problem statement. AgentOS remains downstream execution infrastructure and is not involved in Signal capture.
1.`PRODUCT.md` separates **Signal**, **Candidate Work**, and **Work Unit**. This slice must not create candidate, plan, priority, run, or execution fields.
3. The current global chat is `zopu/main`, so every user shares one Flue instance. Flue routes are pass-through and the browser sends no identity. This must be corrected before Signals can be safe.
4. Canonical Flue `user_message` records already contain exact text plus `messageId`, `conversationId`, timestamp, and `submissionId`, but Convex stores them inside opaque JSON batches. Product commands should not parse those batches ad hoc.
5. Existing `projectSignals` is an execution event feed, not the product Signal entity. Rename it to `projectEvents` before introducing `signals`.
6. The Signal primitive will use Effect schemas and a pure Effect constructor. It will not contain a repository service or depend on Convex, Flue, Hono, AgentOS, or generated IDs.
7. A Signal is created only when the global agent decides the conversation contains a coherent problem statement. Individual user messages remain conversation evidence until then.
8. One Signal may compose multiple ordered user messages. Raw text remains per-message and exact; `problemStatement` is the agent-produced interpretation.
9. Signal creation is idempotent by organization + conversation + ordered source-message IDs so Flue retries cannot create duplicates.
10. Use a minimal organization foundation because `ownerId` is a user identifier, not the hard tenancy boundary required by the product. Do not build full organization administration in this slice.
readonlyrawText: string;// exact; never trim or rewrite
readonlycreatedAt: TimestampMs;
}
interfaceProblemStatement{
readonlytitle: string;
readonlysummary: string;
readonlydesiredOutcome: string;
readonlyconstraints: readonlystring[];
}
```
**Invariants:**
- Every Signal has exactly one organization scope and zero or one project scope.
-`sourceMessages` is non-empty, ordered, and unique by `(conversationId, messageId)`.
- Every source is a user message from the same organization-scoped global conversation.
-`rawText` is copied server-side from the stored source message; the agent never supplies it.
-`problemStatement.title`, `summary`, and `desiredOutcome` are non-empty; constraints are structured as individual non-empty items and remain distinct from raw evidence.
- Signal does not contain candidate/work-unit/run fields.
- IDs are branded non-empty strings in primitives; adapters translate Convex/Flue IDs.
- Timestamps are non-negative integer epoch milliseconds.
## Persistence model
Add these application tables in `packages/backend/convex/schema.ts`:
-`organizations`: minimal durable tenant record with `name`, `createdBy`, `createdAt`.
-`organizationMembers`: `organizationId`, `userId` (`identity.tokenIdentifier`), `role`, `createdAt`; indexes by user and organization/user.
-`conversationMessages`: normalized user-message evidence with `organizationId`, `conversationId`, `messageId`, `submissionId?`, `clientRequestId`, `role: "user"`, exact `rawText`, `status: "admitting" | "admitted" | "failed"`, and `createdAt`; unique lookup indexes by organization/message and organization/client request.
-`signals`: `organizationId`, optional `projectId`, `sourceKey`, structured `problemStatement` (`title`, `summary`, `desiredOutcome`, `constraints`), `processedByAgentName`, `processedByAgentInstanceId`, `createdAt`; indexes by organization/createdAt, project/createdAt, and organization/sourceKey.
-`signalSources`: `signalId`, `organizationId`, `conversationId`, `messageId`, `ordinal`, `rawTextSnapshot`, `sourceCreatedAt`, optional `submissionId`; indexes by signal/ordinal and organization/message.
-`projectEvents`: the renamed current execution event feed.
Do not add lifecycle/candidate status to `signals` yet. A later Candidate component will own promotion and relationships.
---
### Task 1: Normalize the workspace dependency catalog
**Objective:** Centralize every dependency repeated across workspace manifests without forcing intentionally different platform versions into one range.
**Files:**
- Modify: `package.json`
- Modify: `apps/daemon/package.json`
- Modify: other `apps/*/package.json` and `packages/*/package.json` entries identified by the catalog audit
- Modify: `bun.lock` through `bun install`
**Steps:**
1. Add default catalog entries for shared versions that should converge: `effect`, AgentOS packages, `typescript`, `@types/bun`, `@types/react`, `heroui-native`, `vite`, `vitest`, and `convex-test`.
2. Add named catalogs for intentional platform splits, especially Node type majors and React/React Native compatibility ranges. Use `catalog:<name>` rather than silently upgrading Expo/native consumers.
3. Replace direct Effect and AgentOS pins in `apps/daemon/package.json` with `catalog:`.
4. Replace repeated direct versions in workspace manifests with their default or named catalog reference. Leave peer dependency compatibility ranges as peer ranges when they intentionally describe supported versions rather than installation versions.
5. Run `bun install` and verify the lockfile resolves the same intended platform families.
**Acceptance:**
- No repeated installed dependency version remains scattered across manifests without an explicit reason.
- Effect v4 and AgentOS resolve from the root catalog everywhere.
- Native React remains on its Expo-compatible exact family; web/TUI ranges are not accidentally collapsed.
**Verification:**
```bash
bun install
bun run check-types
```
---
### Task 2: Add the pure Effect Signal primitive
**Objective:** Establish the portable Signal vocabulary and validation rules before storage or agents depend on it.
- Define branded schemas for `SignalId`, `OrganizationId`, `ProjectId`, `ConversationId`, `ConversationMessageId`, and `TimestampMs`.
- Define `SignalScope`, `SignalSourceMessage`, `ProblemStatement`, `ProcessedByAgent`, `SignalInput`, and `Signal` with `Schema.Struct` / discriminated unions.
- Add `SignalValidationError` with `Schema.TaggedErrorClass`.
- Add `composeSignal = Effect.fn("Signal.compose")` that decodes input, rejects duplicate/out-of-order sources, preserves raw whitespace exactly, and returns a validated Signal.
- Export the module from the root and an explicit `./signal` package subpath.
- Add a package `test` script using cataloged Vitest.
- Optional migration file only if existing rows are present: `packages/backend/convex/migrations.ts`
**Steps:**
1. Inspect the active Convex deployment count for `projectSignals` before changing schema.
2. If empty, cleanly rename the table and call sites to `projectEvents`.
3. If non-empty, add a one-off internal migration that copies rows with preserved relations/timestamps, verify source/destination counts, then remove the old schema/table references. Do not leave a compatibility alias.
4. Update `signals.md` seed copy to say “project events,” not product Signals.
**Acceptance:**
-`projectSignals` no longer exists in source or schema.
- Modify: `packages/auth/src/web/index.ts` only if the access-token helper is not already exported
**Ingress sequence:**
1. Web obtains the Better Auth/Convex access token with the installed `fetchAccessToken` API.
2. The custom Flue fetch adds `Authorization: Bearer <token>` and a stable `x-zopu-request-id` to agent POSTs.
3. Zopu route middleware requires the bearer token, sets it on a short-lived `ConvexHttpClient`, resolves current organization membership, and rejects an agent instance ID that is not that organization ID.
4. Before `next()`, middleware clones and validates the direct payload and calls `conversationMessages.beginUserMessage` with exact text and request ID.
5. After Flue returns its admission receipt, middleware patches `submissionId` and status `admitted`; on failure it marks the message `failed` and rethrows/returns the original failure.
6. The web global agent ID becomes the current organization ID rather than `main`.
**Important constraints:**
- Do not trust organization ID, raw text, timestamp, or source role from an agent tool.
- Do not parse `flueConversationBatches.recordsJson` for product commands.
- Do not persist auth tokens.
- Keep failed admissions as evidence with explicit status; do not silently delete them.
- Ensure GET/SSE observation routes use the same organization authorization.
**Tests:**
- Unauthorized Flue route is rejected.
- Organization A cannot use or observe organization B's agent ID.
- Duplicate request ID is idempotent.
- Exact whitespace/casing is stored.
- Success records submission ID; failure records failed status.
**Verification:**
- Send one message containing leading/trailing whitespace through the running web app.
- Confirm the agent replies.
- Query Convex and confirm the normalized user message has exact raw text, organization scope, conversation/message identity, request ID, and admitted submission ID.
- Sign in as another identity and confirm it cannot observe the first conversation.
---
### Task 6: Persist Signals atomically in Convex
**Objective:** Resolve selected message IDs server-side, run the Effect constructor, and atomically insert one idempotent Signal plus ordered source snapshots.
**Objective:** Give the global agent a narrow, validated mechanism to inspect eligible user evidence and create a Signal when the problem statement is coherent.
- Calls the Convex `createFromMessages` mutation with `agentName: "zopu"` and the current organization-scoped instance ID.
- Returns only the Signal ID and source count.
**Agent instruction:**
- Continue normal planning conversation until the user's instructions form a coherent problem statement.
- Do not create a Signal for greetings, exploration without a concrete problem, or every individual message.
- When ready, call `list_signal_source_messages`, select only the messages composing the problem, then call `create_signal` once.
- Tell the user the captured problem-statement title, summary, desired outcome, and constraints.
- Do not create Candidate Work or start AgentOS in this slice.
**Retry safety:**
- Flue may replay tool calls after interruption. Convex source-key idempotency is authoritative.
- The tool must surface domain errors; it must not swallow or convert them into fake success.
**Verification:**
Run the focused smoke script and a live browser scenario:
1. Send three messages: an initial problem, a clarification, and an explicit final constraint.
2. Observe Zopu create exactly one Signal after the statement is coherent.
3. Confirm the Signal contains all selected raw messages in order and one structured problem statement with title, summary, desired outcome, and constraints.
4. Restart the agent service/retry the final turn and confirm no duplicate Signal is inserted.
5. Confirm no AgentOS actor, candidate, work unit, issue, or run is created.
**Objective:** Prove the complete Signal slice works and remove temporary migration/scaffolding.
**Steps:**
1. Run all focused tests and smoke scenarios from Tasks 2–7.
2. Run Ultracite fix if necessary, then check every changed source/script/agent file.
3. Run affected package type checks: primitives, backend, agents, auth if touched, and web.
4. Run root `bun run check` and report unrelated pre-existing failures separately.
5. Remove a one-off `projectSignals` migration only after row-count verification and successful cutover, unless the repository convention keeps completed migrations.
6. Update existing backend/agent documentation only where names or commands became stale; do not add speculative docs.
**Final acceptance criteria:**
- Global conversations are organization-scoped and authenticated; `zopu/main` is gone.
- Exact user text is stored before/with Flue admission and cannot cross organizations.
- Zopu can intentionally compose one or more user messages into exactly one durable Signal.
- Signal raw evidence and the structured processed problem statement are separate and queryable.
- Source provenance is immutable and ordered.
- Retry produces no duplicate Signal.
- Existing project execution events still work under `projectEvents`.
- Signal creation does not create Candidate Work, Work Unit, run, or AgentOS resources.
- Effect v4 and AgentOS dependency versions come from workspace catalogs.
- Focused tests, package type checks, runtime smoke, and root check are reported with evidence.
## Risks and deliberate tradeoffs
- **Minimal organization bootstrap:** This adds only the tenancy needed for Signals. Multi-org switching, invites, billing, and policy are separate components.
- **Normalized user evidence plus Flue transcript:** There are temporarily two durable representations. Flue remains the canonical conversational transcript; `conversationMessages` is the application-domain evidence index required for authorization and provenance. Idempotent request IDs and submission links keep them correlated.
- **No automatic Signal for every message:** This follows the product thesis and avoids task noise. The agent explicitly decides when the instructions are coherent.
- **No separate Flow agent yet:** Zopu performs the first extraction using narrow tools. A future Flow Agent can adopt the same Convex command without changing the Signal primitive.
- **No AgentOS here:** AgentOS begins only after future Candidate/Work Unit readiness and scheduling.
- **No Signal status model yet:** Candidate attachment, knowledge capture, dismissal, and work promotion belong to later components. Adding them now would pre-design unimplemented workflows.
Blocking a user prevents them from interacting with repositories, such as opening or commenting on pull requests or issues. Learn more about blocking a user.