Service notices
- The system finds the user: when product events happen — workspace invites, join-request results, friend requests — the user sees a fixed entry “Service notices” in Messages, with
Source docs/en/site/mech-notices.md
Status: Spec confirmed (2026-05-25).
Truth: User-visible capabilities in docs/产品规格.md §4.7; this page is for design and implementation mapping.
Voice: UI and help use user language; table names, APIs, and conversation types appear only in §6–§8.
1. Goal
- The system finds the user: when product events happen — workspace invites, join-request results, friend requests — the user sees a fixed entry “Service notices” in Messages, without having to remember which module to open.
- Separate from chat: service notices are not colleague DMs, not business-agent conversations, not help-assistant Q&A. The shape is close to WeChat “Service notices” — mainly system-sent explanation + a tappable action.
- Trustworthy and auditable: notice bodies come from server templates (or structured-field render); by default do not let a live model invent the event, so an “assistant hallucination” cannot become already approved / already rejected.
1.1 Non-goals (not in phase 1)
- Do not replace SMS/email verification-code channels (still OTP).
- Do not require users to small-talk with service notices; optional phase 2 “summarize recent notices for me” read-only Q&A.
- After a workspace enables WeCom and binds members, the same service notice is also sent as a personal WeCom app message (unbound members are skipped silently). DingTalk and other off-site channels are still not done.
- Do not delete collaboration detail pages (banners and my-application lists inside workspace collaboration) — service notices own reach; each module still keeps a handling UI.
2. Relation to existing capabilities
| Existing capability | What users think | Actual today | Relation to service notices |
|---|---|---|---|
| Help assistant | Ask how to use the product | Conversational assistant with no workspace / onboarding | Alongside: help = you ask, I answer; service notices = the system comes to tell you |
| Messages (AI chat) | All “messages” | Only chat_sessions assistant unread + 20s poll | Service notices join the Messages entry, but are grouped / pinned separately |
| Contacts | Colleagues / groups | im_* unread does not enter the top-bar badge | Friend requests waiting move into service-notice reach; handling stays in contacts |
| Workspace collaboration | Invites and applications | Only that page has banners / sidebar lists | Invite/application results are pushed by service notices; in-page UI remains |
3. Product decisions (confirmed, 2026-05-25)
| # | Topic | Decision |
|---|---|---|
| 1 | User-visible name | Always “Service notices”; icon/title in the Messages list is consistent and does not change with workspace |
| 2 | Is it an “agent”? | Product-wise it is a fixed entry; implementation may use session_kind=notification + a system persona; does not consume “My agents” quota; does not go through user-agent Runtime free generation |
| 3 | Can you reply? | Phase 1 no (read-only timeline); phase 2 optional “ask: what did I miss” read-only summary |
| 4 | Knowledge documents | Do not inject the three-layer knowledge tree; templates + action links are enough |
| 5 | No workspace selected | Still deliver account-level notices (invites, friend requests, and so on); the Messages entry still shows Service notices |
| 6 | Versus §4.6 unified chat | List UI may merge long-term, but conversation type must distinguish notification vs direct vs agent chat |
| 7 | Read | Entering the Service notices conversation updates the read watermark; top-bar Messages badge = AI unread + service-notice unread + (later) IM unread |
| 8 | Retention | At least 90 days queryable (implementation configurable); users cannot delete a single item (phase 1), to avoid deleting an approval receipt by mistake |
4. User-visible experience
4.1 Entry
- Top-bar Messages: unread badge includes service-notice unread count (may merge with AI unread as a total; after entering, show sources separately).
- Messages module list: Service notices is always pinned (above the help assistant, business agents, and conversation list, or first in its own group).
- Subtitle/preview: shows a summary of the latest notice (e.g. “R&D invited you to join a workspace”).
4.2 Inside the conversation
- Timeline: cards or bubbles are both OK, but visually less like a chat (system send, gray background / icon distinction).
- Each notice includes:
- Title (one-sentence conclusion) - Explanation (optional, template-filled) - Time - Primary action button (e.g. “View invite”, “Go to workspace collaboration”) — uses mindlink://action/… (extended allowlist, see §5.2)
- No composer (phase 1); the bottom may hold a static link such as “Go to workspace collaboration to see all to-dos”.
4.3 Split from each module
| Scene | What service notices do | Where the user still handles it |
|---|---|---|
| Received a workspace invite | Push “xxx invited you to join yyy” | Workspace collaboration / in-notice accept/decline if phase 1 does not do it, jump only |
| Join request approved/rejected | Push the result | Workspace collaboration “My applications” |
| Someone applied to join your workspace | Push to admins | Workspace collaboration “Join requests” approval |
| Friend request | Push “xxx wants to add you as a friend” | Contacts |
5. Notice types (event list)
5.1 Phase 1 (suggested first batch)
| Event type (implementation id) | Reaches | Example user-visible title | Primary action |
|---|---|---|---|
workspace.invitation.received | Invitee | {inviter} invited you to join workspace “{workspace name}” | Go handle (collaboration page / accept or decline) |
workspace.invitation.accepted | Inviter | {other} has joined workspace “{workspace name}” | Open the workspace |
workspace.join_request.submitted | Workspace admins | {applicant} applied to join “{workspace name}” | Go approve |
workspace.join_request.approved | Applicant | You have joined workspace “{workspace name}” | Open the workspace |
workspace.join_request.rejected | Applicant | “{workspace name}” could not approve your join request | View explanation / collaboration page |
workspace.join_request.cancelled | Workspace admins | {applicant} withdrew the join request | (optional) none |
5.2 Action links (extended allowlist)
Incrementally register on the existing docs/core-mechanisms/帮助动作链接.md allowlist, for example:
| action | User outcome |
|---|---|
module.workspace | Open workspace collaboration |
workspace.invitation.accept?id=… | Accept the invite (with id; server checks) |
workspace.invitation.decline?id=… | Decline the invite |
workspace.join_request.review?id=… | Open the collaboration page and locate that request |
module.im | Open contacts |
Safety: same as help links — allowlist only; operations that carry an id must re-check the current user’s permission.
5.3 Phase 2 and later (placeholder)
im.friend_request.received/acceptedagent.train_job.completed/failedworkspace.member.removed(removed from a workspace)workflow.task.assigned/workflow.task.cancelled(workflow to-dos; already implemented)- Admin broadcast (company announcement, optional)
6. Message shape and copy rules
6.1 Templates first
- Each notice stores
event_type+payload(JSON); display server or front end renders Chinese title/body from a template. - Template versioning (
template_version) so copy can change without rewriting history. - Do not hand notice bodies to an LLM (except a clearly scoped phase-2 “summary Q&A”, which must be marked “summarized by the assistant; the detail page is authoritative”).
6.2 Align with user language
- Use workspace, invite, join request, not
workspace_idorjoin_requestas the subject. - IDs go in detail / collapsed, not the first screen of the title.
6.3 Dedupe and merge
- The same
dedupe_key(e.g.invitation:{id}) updates in pending state instead of flooding the timeline. - Optional: several applications in the same workspace merge into “you have 3 join requests to handle” (phase 2).
7. Unread, sync, and realtime
| Item | Suggestion |
|---|---|
| Unread count | Notice messages with created_at > last_read_at; count in a separate table from chat unread; API may merge |
| Front-end refresh | Short term: poll (align with existing 20s or a separate 30s); mid term: SSE/WS notice events |
| Offline | Next login, pull GET /notifications or conversation history |
8. Implementation model (draft)
R&D mapping below — not user copy.
8.1 Data (illustrative)
Option A (recommended phase 1): independent table + a fixed conversation
notification_messages
id, user_id, event_type, dedupe_key,
title, body, payload_json,
action_primary_json, -- { "label", "action", "params" }
read_at NULL,
created_at
Each user has one chat_sessions or equivalent notification_inbox pointer for the Messages UI; session_kind = 'notification'.
Option B: write directly into chat_messages with role=system — easy to mix with assistant messages, not recommended.
8.2 API (illustrative)
| Method | Path | Note |
|---|---|---|
| GET | /api/v1/notifications | Paged list (including unread count) |
| GET | /api/v1/notifications/unread-count | Top-bar badge |
| POST | /api/v1/notifications/read | Mark read (enter the conversation or mark all) |
| GET | /api/v1/chat/unread-summary | Extend: add notification_unread (or a separate summary API the front end merges) |
Write: call notify.Emit(ctx, event) only after the business handler commits successfully, so you do not “send a notice but the business failed”.
8.3 Relation to a “notice agent” implementation
- Product name: Service notices (entry)
- Implementation persona (optional): an undeletable instance with
user_agents.is_system=1andagent_kind=notification, or no agent row and only a fixed UI shell - Runtime: writing notices does not call
POST /chat/ streaming LLM; isolated from the help assistant and business agents
9. Evolution with unified chat (§4.6)
flowchart LR
subgraph now [Today]
M[Messages module chat]
H[Help assistant]
I[Contacts im]
W[In-page collaboration banners]
end
subgraph target [Target]
U[Unified Messages entry]
U --> N[Service notices pinned read-only]
U --> H2[Help / agents / colleague conversations]
N --> Actions[mindlink://action]
end
M --> U
I --> U
W --> N- Short term: add a pinned Service notices conversation in the existing Messages module; do not wait for full IM merge.
- Long term:
GET /im/conversationsand the notice inbox share one list component, filtered by Tab withconversation.kind/notification.
10. Phases and acceptance
10.1 Phase 1 (MVP)
Scope: §5.1 six workspace events + pinned entry + unread into the top bar + action jump to the collaboration page.
Acceptance (users can do this):
- User A invites B → B receives an item in Messages → Service notices, top-bar badge +1.
- B opens Service notices → badge clears (or per-item read policy).
- Tap “Go handle” → enter workspace collaboration and see the matching invite/request.
- An admin approves a join request → the applicant receives “joined” in Service notices; collaboration “My applications” status matches.
- Notice body matches database facts; no LLM in generation.
10.2 Phase 2
- Friend requests move in; accept/decline inside the notice (fewer jumps).
- “Recent notices summary” read-only Q&A (optional, separate switch).
- Merge unread APIs; IM unread into the top bar.
10.3 Phase 3
- Off-site Push / email digest; admin announcements.
11. Risks and constraints
| Risk | Mitigation |
|---|---|
| Confused with the help assistant | Fixed name, fixed pin, no composer, visual distinction |
| Duplicate notices | dedupe_key + state machine (pending → resolved) |
| Permission leak | Notice payload contains no secrets; action chain server-checked |
| Performance | Async write (goroutine/queue after the transaction); paged list |
12. References
- Gap analysis: “message prompt mechanism” notes in conversation (2026-05-25).
- [帮助动作链接.md](/docs/mech-action-links) — jump protocol and allowlist.
- [统一对话与联络.md](/docs/mech-im) — long-term IM merge.
- [docs/产品规格.md](/docs/mech-spec) §4.5 workspace collaboration, §4.6 unified chat.
*After review: add a user-visible § in 产品规格.md; hang phases and cases on 实施计划.md / 实施验收.md; before implementation, optionally open a field-level API contract page.*