HR embed example
- Create/pick the embed agent in Cadau, agree on app_id
Source docs/en/site/sdk-host-hr-guide.md
What this document is
A self-contained conversion handbook for a third-party business system (ERP, OA, industry SaaS, and similar) connecting Cadau’s website embed assistant.
Readers are host-system developers and admins: reading this page and finishing the checklists is enough to complete “register agents → assign by user → mount the conversation assistant on the page → integrate and accept”, without first reading all Cadau site docs or the entire HR example source.
>
Full third-party handbook: [README.md](/docs/sdk-host-embed) (overview). This page is the HR example conversion walkthrough.
- HR example today: path B only (
MINDLINK_*inserver/.env+ embed-session); it no longer uses a static token inweb/.env.local. This phase: the whole host shares one Cadau workspace (several HR tenants also register into that same workspace). “One workspace per tenant” isHost agent run and data scope§0.3 — not built yet.- Cadau product details (full widget params, SSE protocol, error codes):
Website integration,Embed contract(V1.5.12).- Runnable reference: Cadau repo
examples/hr-multi-tenant(HR multi-tenant example); below, “HR example”. Paths and table names follow that example; map them to your own names.
0. Quick answers (for decision-makers)
| Question | Answer |
|---|---|
| Do I fill an embed token when registering an agent? | No. Register only the Cadau agent ID; the token comes from static config (A) or backend minting (B). |
| Do I generate a token in Cadau for every agent? | No. Path B uses one integration account; assignment swaps tokens at runtime. |
| Which way for production? | B (backend minting); A is local testing or an emergency fallback. |
| Does Cadau store our users? | No. Users and permissions stay on the host; Cadau provides agents and conversation. Path B uses host_actor to mark the signed-in user; history / live support / tickets are isolated by that person. Host data queries: Host agent run and data scope §0. |
| Will two people on the same computer leak conversations? | No (path B must pass the signed-in user into the widget). User A cannot see B’s history, live support, or tickets. |
| Can we embed without changing the business backend? | Path A yes (token never hits the backend); path B no (you must have a host BFF mint API). |
| Do we build live support / tickets on the host? | No. Same widget as a static site; turn live support on in Cadau. |
| The host used to call a model directly — now what? | Ask a chosen agent to answer (server call; the user need not see the widget). See Host LLM service. |
| Can several host customers share one Cadau workspace? | Yes, and this phase only does that. People are isolated by the signed-in user; the support desk is still one copy per workspace. “One workspace per tenant” can be finished later; see Host agent run and data scope §0.3. |
1. What you will have finished after this page
1.1 Admins
- Create/pick the embed agent in Cadau, agree on app_id
- In the host admin UI, register agents that can be connected (name + agent ID)
- For signed-in users (not business-file ids such as employee id), assign an agent and operation permissions (query / create / edit / delete)
- Configure the host server Cadau integration account (path B; one set is enough)
- Confirm the assistant works against the §12 acceptance checklist
1.2 Developers
- Add a registration table + user assignment table (or equivalent modules) in the host DB
- Implement an embed-session API (for example
GET .../me/embed-session) that internally calls Cadauembed-token - On frontend business pages, load the widget script and init; listen for in-reply navigation actions (optional)
- (Optional) Turn on live support for that agent in Cadau (seats in this workspace or an authorized support team) — the widget shows “Live support / Submit a ticket” by itself; do not build a second support desk on the host
- (Optional) Implement a task-style API: after the agent is configured in Cadau, point at it from
.envor host settings; the server answers, the page previews, then writes to the DB (example: suggest jobs by department) - Configure network (same-origin proxy or CORS) and secrets (integration password server-only)
- Finish integration and go-live on path A or B
2. Architecture: who owns what
flowchart LR
subgraph Host["Third-party host system"]
U["Signed-in user"]
ADM["Admin: register + assign"]
BFF["Host BFF: mint via embed-session"]
UI["Business frontend + embed widget"]
U --> UI
ADM --> DB[(register and assign)]
BFF --> DB
UI --> BFF
end
subgraph ML["Cadau"]
UA["User agents"]
CHAT["Conversation / knowledge"]
ET["embed-token API"]
UA --> CHAT
ET --> UA
end
BFF -->|"integration account calls"| ET
UI -->|"embed token conversation"| CHAT| Party | Owns | Does not own |
|---|---|---|
| Cadau | Agents, conversation, knowledge index, embed-token issue, embed widget script | Host business user ids, host permissions, host business APIs |
| Host (you) | Signed-in users, registering several Cadau agents, assigning by user, minting short-lived tokens, page mount and navigation | Maintaining a “register by host user” API inside Cadau |
Relation to host HTTP API keys: API keys govern machine calls to host business APIs; embed governs which agent the in-page conversation uses. Configure them independently.
3. Concept map: your system ↔ HR example
Treat the HR example as a walkthrough. Map concepts like this:
| General concept (your system) | Name in the HR example | Notes |
|---|---|---|
| Host signed-in user | hr_users (phone) | Subject of agent assignment; not the employee file id |
| Tenant / org / books | tenants (workspace) | Register and assign are isolated by tenant |
| Registered connected agent | hr_mindlink_agents | Stores display name, Cadau agent ID, app_id; conversation assistants assigned by role. Generate/analyze is a separate agent in .env or “embed assistant registry” |
| User–agent assignment | hr_user_agent_assignments | Signed-in user → registration row + operation permissions |
| Embed-session BFF | GET .../me/embed-session | Read assignment → mint → return fields the widget needs |
| Static testing config | VITE_MINDLINK_* in web/.env.local | Optional path A for other hosts; HR example has dropped this, B only |
| Server Cadau connection | MINDLINK_* in server/.env | Path B integration account and URLs |
4. Two paths: A and B
4.1 Compare
| Dimension | A: static testing | B: backend minting (recommended for production) |
|---|---|---|
| Token source | Pre-written in config/env | Host BFF mints now when the user opens the page |
| Goes through host backend? | No (browser talks to Cadau) | Yes |
| Different agent per person? | Usually one config for everyone | Yes |
| Click “generate token” in Cadau UI? | Yes (or a script) | No |
| Host must build a BFF? | No | Yes (at least embed-session) |
4.2 How to choose
Need a different agent per signed-in user?
├─ Yes → B
└─ No → testing / demo only → A first is fine; still prefer B for go-live
Can the token appear in frontend build output or a public repo?
├─ No → B (or A in dev only)
└─ Yes (intranet dev only) → A is acceptable
Does the host already have sign-in and permissions?
├─ Yes → B; hang assignment on your user id
└─ No → prove the widget with A; then add sign-in + B
4.3 What “minting” means
Minting = the host server uses a Cadau integration account to call POST /api/v1/user-agents/{agentId}/embed-token and get a short-lived embed token, then issues it to the browser. The user does not sign in to the Cadau main site; the browser does not store a Cadau password.
4.4 HR example vs A + B strategy
HR example (this repo): frontend is path B only — after sign-in it requests embed-session; if unassigned, it does not mount the assistant; it no longer falls back to A or reads web/.env.local.
Other third-party hosts (you may borrow):
- User unassigned → skip embed-session, or try A (dev only)
- User assigned → request B
- B succeeds → use B; B fails → fall back to A (dev only recommended)
Production: assign everyone + B only, drop A’s long-lived token.
5. Cadau-side prep (admin)
Before you change the host, finish this in Cadau:
| Step | Action | Output |
|---|---|---|
| 1 | Create or pick a user agent (bind knowledge documents) | Agent ID (UUID) |
| 2 | Agree on app_id (distinguishes the integrator, for example your-corp-hr) | Same as host registration and mint request |
| 3 | Prepare a Cadau integration account (path B) | Email/password; must be a member of the agent’s workspace (need not own the agent) |
| 4 | Invite/add the integration account to the agent’s workspace | Can switch into that workspace before minting; production: invite in Cadau UI; local testing: §7.3 |
| 5 | Confirm public URLs | Widget script URL, API prefix /api/v1 |
| 6 | (Optional) My agents → Manage → Live support: turn on, and assign support seats in this workspace or authorize a support team | Widget shows “Live support / Submit a ticket”; support works them in the Cadau support desk (same as static website embed) |
| 7 | (Path A, optional for other hosts) that assistant Manage → Website embed → Generate token | access_token, expires_at |
Path B does not need you to click “generate token” in the Cadau UI for every user or every agent; minting uses the same API as the UI button. Live support / tickets also do not need a second host-issued token: the embed token from embed-session has the same widget support-API rights as a token pasted on a static site.
Knowledge documents: mount host business notes on the embed agent (menus, bounds, navigation conventions). HR example: docs/宿主知识文档/.
6. Host-system conversion checklist (developers)
6.1 Data layer (required, path B)
At least two tables (names are yours):
Registered connected agents
| Field | Meaning |
|---|---|
| id | Host internal primary key |
| tenant_id | Tenant isolation |
| label | Display name |
| mindlink_user_agent_id | Cadau agent UUID |
| app_id | Default same as global config |
| mindlink_workspace_id | Prefer the Cadau workspace UUID the agent lives in (this phase same as MINDLINK_WORKSPACE_ID). Mint switches with this value first |
| status | active / disabled |
User–agent assignments
| Field | Meaning |
|---|---|
| tenant_id + user_id | Unique; user_id = host signed-in user id |
| agent_id | Points at the registration row |
| scopes_json | Assistant operation permissions (host-defined enums; HR example §8.3) |
| user_label | Optional, for display |
HR example permission enums: two-dimensional feature module × operation, keys like module.permission (for example employees.query). Operations include query, create, edit, delete; edit and delete carry hallucination risk, so the UI has a safety note and they are off by default.
6.2 Admin features (required, path B)
Admins in the host UI or via API:
- Register a Cadau agent (name + agent ID + app_id) — no token
- Assign to a signed-in user: pick a registration row + check operation permissions (at least one; default query only)
HR example entry: sidebar User management (workspace admin). Ops may use External access → User agents (cross-tenant tokens).
6.3 Embed-session BFF (required, path B)
Implement something like:
GET /api/v1/tenants/{tenantId}/me/embed-session Header: Authorization: Bearer <host sign-in JWT>
Server logic:
- Resolve the current signed-in user
- Look up whether this tenant has an active assignment
- No assignment →
200+{ "available": false, "reason": "..." }(do not use 404, which browsers treat as a hard error) - Has assignment → sign in to Cadau with
MINDLINK_INTEGRATION_*→POST /workspaces/{id}/switchusing the registration row’smindlink_workspace_id(if any) orMINDLINK_WORKSPACE_ID→
POST /api/v1/user-agents/{mindlink_user_agent_id}/embed-token body: { "app_id": "...", "ttl_seconds": 3600 } (Cadau requires: the integration account is a member of that workspace; the agent must be in that workspace, need not be owned by the integration account)
- Return the fields the widget needs (see §9.2)
Integration account and URLs live only in server env vars. Do not commit them to the frontend repo.
6.4 Frontend mount (required)
- User opens a page that should show the assistant
- (B) Request embed-session with the host JWT; or (A) read static config
- Load
widget_scriptdynamically - Call
window.MindLinkWidget.init({ app_id, api_base_url, user_agent_id, workspace_id, auth: { token, expires_at }, ... }) - Listen
widget.on("action", ...)for in-reply navigation (see §10) - Before the token expires, the BFF refreshes and
widget.updateAuth({ token, expires_at })(if you do not renew after expiry, widget live-support / ticket requests 401) - Do not write extra host APIs for live support or tickets; see §10.4
HR example: web/src/mindlinkOrgEmbed.ts, web/src/mindlinkHostActions.ts.
6.5 Network (required)
- Browser access to Cadau APIs needs a same-origin reverse proxy or allowed CORS
- Dev example (
web/vite.config.ts):
- /mindlink-api → Cadau :8080 /api (widget conversation API) - /mindlink-embed → Cadau :8080 (widget script /embed/mindlink-widget.min.js)
- Frontend
api_base_url/widget_scriptshould share origin with the browser address bar (for example allhttp://localhost:5180), so mixinglocalhostand127.0.0.1does not break CORS - Production: under the host domain,
/mindlink-api(and the script path) → Cadau gateway
7. Config overview
7.1 Server env (path B, host backend .env)
MINDLINK_API_BASE=https://mindlink.example.com/api/v1
MINDLINK_API_BASE_PUBLIC=https://your-host.example.com/mindlink-api/v1
MINDLINK_WIDGET_SCRIPT=https://mindlink.example.com/embed/mindlink-widget.min.js
MINDLINK_INTEGRATION_EMAIL=integration@your-corp.com
MINDLINK_INTEGRATION_PHONE=
MINDLINK_INTEGRATION_PASSWORD=<server only>
MINDLINK_APP_ID=your-corp-app
MINDLINK_WORKSPACE_ID=
MINDLINK_GENERATE_USER_AGENT_ID=
# The next two are HR example local demo seed only; do not rely on them in production
# HRMS_SEED_MINDLINK_USER_AGENT_ID=
# HRMS_SEED_MINDLINK_DEMO=0
| Variable | Role |
|---|---|
MINDLINK_API_BASE | Host server calls Cadau (mint / run) |
MINDLINK_API_BASE_PUBLIC | Browser calls Cadau (public URL via the host proxy) |
MINDLINK_WIDGET_SCRIPT | Widget script URL |
MINDLINK_INTEGRATION_EMAIL / PHONE | Cadau account used to mint (email or phone, one of them); one set serves all registered agents |
MINDLINK_INTEGRATION_PASSWORD | Integration-account password, server only |
MINDLINK_APP_ID | Default app_id for the page widget; a registration row may override |
MINDLINK_WORKSPACE_ID | Cadau workspace shared by the whole host in this phase |
MINDLINK_GENERATE_USER_AGENT_ID | Cadau agent UUID used for generate/analyze (for example suggest jobs). Configure it in Cadau first. Choosing it on “embed assistant registry” overrides this. Not the page-widget assistant |
HRMS_SEED_MINDLINK_USER_AGENT_ID | Demo seed only. When the HR service starts, it registers that UUID into the DEMO workspace and assigns it to the demo account so local sign-in has a bottom-right assistant. Everyday conversation / minting does not read this (it reads assignments in User management). After setup:hr-work-agents and members already have assignments, leave it empty or delete it. Unrelated to MINDLINK_GENERATE_USER_AGENT_ID |
HRMS_SEED_MINDLINK_DEMO | Set to 0 to turn the demo seed off |
MINDLINK_EMBED_TTL_SECONDS and MINDLINK_WIDGET_INLINE have code defaults (3600 seconds, floating widget); they need not go in .env.
7.2 Frontend static config (path A: other hosts may use; HR example has dropped this)
The HR example frontend no longer reads
VITE_MINDLINK_*/web/.env.local. The following is only for other systems’ local static testing.
VITE_MINDLINK_WIDGET_SCRIPT=...
VITE_MINDLINK_API_BASE_URL=...
VITE_MINDLINK_APP_ID=...
VITE_MINDLINK_USER_AGENT_ID=<Cadau agent UUID>
VITE_MINDLINK_EMBED_TOKEN=<embed token, do not commit>
VITE_MINDLINK_TOKEN_EXPIRES_AT=...
VITE_MINDLINK_WORKSPACE_ID=...
7.3 Agent ID, integration account, and local scripts
| How | Notes |
|---|---|
| Cadau UI | That assistant Manage → Website embed |
| Cadau API | GET /api/v1/user-agents |
HR example: write server .env | In the web directory npm run setup:mindlink (or node scripts/write-mindlink-env.mjs) |
What setup:mindlink does
- Calls Cadau
POST /api/v1/auth/registerfor integration accounthr-embed-demo@mindlink.local/MindLink-HR-demo-2026(if it exists,POST /api/v1/auth/login; script defaultAPI_BASE=…/api/v1) - Probes or creates a usable workspace, reads agent IDs in the workspace
- Writes
MINDLINK_*into../server/.env, andHRMS_SEED_MINDLINK_USER_AGENT_ID(the work-agent UUID it found) so the HR service can one-time register/assign the DEMO demo account at startup. This is not runtime widget config; production should register and assign in host User management and need not depend on this.
So you need not create that email by hand in Cadau. Production: create your own dedicated integration account; do not use the example demo email.
| How | Notes |
|---|---|
| HR example: connect an existing agent | In the web directory npm run link:mindlink-agent -- <MindLink agent UUID>: fix registration mindlink_workspace_id, (when the main DB is SQLite) add the integration account to that workspace, verify embed-token |
| HR example: work agents | In the web directory npm run setup:hr-work-agents: three conversation assistants. After the generate/analyze agent is configured in Cadau, write MINDLINK_GENERATE_USER_AGENT_ID, or pick it on “embed assistant registry” |
If the workspace on the registration row differs from MINDLINK_WORKSPACE_ID, fill mindlink_workspace_id or run link:mindlink-agent, or embed-session may 502.
Postgres main DB: if local Cadau uses Postgres (not
backend/mindlink.db),link:mindlink-agentmay say “agent not found”. Invite the integration account into the workspace in the Cadau UI, or re-runsetup:mindlink; and check that the HR registration row and.envworkspace / agent IDs match.
Automated smoke (web directory; HR and Cadau backends already running):
| Command | Use |
|---|---|
npm run smoke:embed | Embed path B: sign-in → embed-session → Cadau conversation |
npm run smoke:host-agent | Legacy-host enhancement: host_actor, access-policy compile, agent-run conversation isolation |
Details: [README.md](/docs/sdk-host-embed) §8.1; full manual items: examples/hr-multi-tenant/docs/ACCEPTANCE_HOST_LEGACY.md in the repo.
8. Admin operations (HR example walkthrough)
Verify the following in the HR example; your system should offer an equivalent UI or API.
8.1 Prerequisites
- Cadau running
- Host backend and frontend running
- Workspace admin signed in (demo:
13800138000/Demo-HR-2026)
8.2 Register an agent
Path: workspace → User management → Embed assistant registry
Preferred: click Sync from Cadau. The host server uses the integration account to call Cadau GET /api/v1/user-agents?scope=workspace, then upserts agents in the current MINDLINK_WORKSPACE_ID (or the registration workspace) into this tenant’s registration table (updates name and workspace; does not auto-disable local rows that did not appear this time).
You can also register by hand:
| Field | Required |
|---|---|
| Display name | Yes |
| Cadau agent ID | Yes |
| app_id | No (default mindlink-embed-hr) |
API: POST /api/v1/tenants/{tenantId}/mindlink-agents/sync (tenant admin; optional body workspace_id / app_id).
8.3 Assign users
User management → member Configure agent (gear) → pick an agent → left: feature module, right: operation permissions → save.
Feature modules (aligned with HR domains):
| Module id | UI name |
|---|---|
| org_structure | Org chart |
| employees | People |
| positions | Jobs and levels |
| employee_lifecycle | Hire / leave / transfer |
| competency | Competency |
| succession | Succession and pipeline |
Operations under each module:
| Permission id | UI name | Notes |
|---|---|---|
| query | Query | Allow the assistant to look up this module; recommended default on |
| create | Create | Allow the assistant to start create-style writes in this module |
| edit | Edit | Models can hallucinate; for data safety, leave off unless you have a special use |
| delete | Delete | Accidental-delete risk is high; leave off unless you have a special use |
Stored as flat keys moduleId.permissionId (for example employees.query). New assignments in the HR example default to Org chart · Query only.
9. API contract and examples
Paths below follow the HR example; keep equivalent meaning in your system.
9.1 Host user sign-in
POST /api/v1/auth/login
Content-Type: application/json
{ "phone": "13800138000", "password": "Demo-HR-2026" }
Response includes access_token (host JWT). Later tenant APIs need:
Authorization: Bearer <access_token>
9.2 Embed session (B core)
GET /api/v1/tenants/{tenantId}/me/embed-session
Authorization: Bearer <host JWT>
Unassigned — 200
{
"available": false,
"reason": "No agent has been assigned to this user yet"
}
Assigned and mint succeeded — 200
{
"available": true,
"user_id": "...",
"user_label": "Demo admin",
"agent_id": "...",
"agent_label": "HR general assistant",
"user_agent_id": "4b2819de-8fcf-432c-b1ee-5593c78d4e64",
"workspace_id": "be02b465-de92-4e3f-9352-67ff37a51dc9",
"app_id": "mindlink-embed-hr",
"access_token": "<embed JWT>",
"expires_at": "2026-05-21T12:00:00Z",
"scopes": {
"org_structure.query": true,
"org_structure.create": true,
"employees.query": true
},
"host_actor": {
"external_user_id": "...",
"actor_kind": "business",
"display_name": "Demo admin",
"tenant_external_id": "...",
"roles": ["admin", "manager"],
"managed_org_unit_ids": []
},
"widget_script": "http://127.0.0.1:8080/embed/mindlink-widget.min.js",
"api_base_url": "http://127.0.0.1:5180/mindlink-api/v1",
"widget_inline": false
}
The frontend maps access_token → auth.token, and user_agent_id, app_id, api_base_url, widget_script, host_actor → MindLinkWidget.init. On renew, sync updateAuth and updateHostActor. Employee self-service must include employee_id; full field set: Host agent run and data scope §0.1. The widget automatically sends header X-Host-External-User-Id (B: signed-in user id; A: browser visitor id).
Common errors
| HTTP | Meaning |
|---|---|
| 401 | Not signed in to the host |
| 503 | Host missing MINDLINK_API_BASE and similar |
| 502 | Mint failed (integration account, agent id, app_id, and similar) |
9.3 Register an agent (admin)
POST /api/v1/tenants/{tenantId}/mindlink-agents
Authorization: Bearer <host JWT>
Content-Type: application/json
{
"label": "HR general assistant",
"mindlink_user_agent_id": "4b2819de-8fcf-432c-b1ee-5593c78d4e64",
"app_id": "mindlink-embed-hr"
}
9.4 Save a user assignment (admin)
PUT /api/v1/tenants/{tenantId}/members/{userId}/agent-assignment
Authorization: Bearer <host JWT>
Content-Type: application/json
{
"agent_id": "<registration row id>",
"scopes": {
"org_structure.query": true,
"org_structure.create": true,
"employees.query": true,
"employees.create": false,
"employees.edit": false,
"employees.delete": false
}
}
9.5 Membership (optional, for frontend)
GET /api/v1/tenants/{tenantId}/me/membership
Response includes is_admin, has_agent_assignment (when assigned, you can skip unnecessary embed-session failure retries).
9.6 Cadau mint API (host server, not the browser)
Switch workspace first (JWT carries wid), then mint; do not rely on the X-Workspace-Id header (HR example client: mindlinkclient).
POST /api/v1/auth/login
Content-Type: application/json
{ "email": "integration@your-corp.com", "password": "..." }
POST /api/v1/workspaces/{workspaceId}/switch
Authorization: Bearer <access_token from previous step>
POST /api/v1/user-agents/{mindlinkUserAgentId}/embed-token
Authorization: Bearer <access_token returned by switch>
Content-Type: application/json
{
"app_id": "mindlink-embed-hr",
"ttl_seconds": 3600
}
When the browser holds the embed JWT for conversation, Cadau checks agent in the workspace + user_agent_id bound on the token registration (the integration account need not own the agent).
9.7 Task-style: suggest jobs by department (server call, not the widget)
Unlike me/agent-run: this does not use the user’s assigned conversation assistant. It calls the chosen generate/analyze agent, each time fresh: true. Results are preview only, not written to the job catalog.
How to choose (one of two; settings page wins):
server/.env:MINDLINK_GENERATE_USER_AGENT_ID=<Cadau agent UUID>- “User management → Embed assistant registry → Generate and analyze agent”
Create the agent in Cadau and add it to the workspace first; only then can you pick it from the dropdown after registering it on this page.
GET /api/v1/tenants/{tenantId}/mindlink-generate-agent
Authorization: Bearer <host JWT>
{
"agent_id": "<registration row id; empty when not chosen on the settings page>",
"mindlink_user_agent_id": "<Cadau agent UUID>",
"label": "…",
"source": "tenant | env | none",
"env_user_agent_id": "<UUID from .env>"
}
PUT /api/v1/tenants/{tenantId}/mindlink-generate-agent
Authorization: Bearer <host JWT>
Content-Type: application/json
{ "agent_id": "<registration row id of a registered assistant; empty string falls back to .env>" }
Requires workspace admin. source tenant means the settings page chose it; env means env-var fallback; none means not chosen yet.
POST /api/v1/tenants/{tenantId}/positions/suggest-for-org
Authorization: Bearer <host JWT>
Content-Type: application/json
{ "org_unit_id": "<department ID>" }
Page: “Jobs and levels” → pick a department → Suggest jobs for this department. Notes: Host LLM service §5.1.
Full API list: HR example HOST_USER_AGENTS.md.
10. Frontend mount and in-reply navigation
10.1 Minimal init (path-independent)
const widget = window.MindLinkWidget.init({
app_id: session.app_id,
api_base_url: session.api_base_url,
user_agent_id: session.user_agent_id,
workspace_id: session.workspace_id,
auth: {
token: session.access_token,
expires_at: session.expires_at,
},
// Path B required: same host_actor as embed-session (isolates history / support / tickets; also host data queries)
host_actor: session.host_actor,
theme: "auto",
position: "bottom-right",
locale: "zh-CN",
entry: { auto_open: false, auto_execute_navigation: true },
// The example top bar can also pick a “fixed entry”: hide_launcher: true, a host button calls widget.open()
});
widget.on("action", (ev) => {
if (ev.type !== "action" || !ev.action) return;
// Parse mindlink_action (emit_event), jump to a host route
});
// Renew example:
// widget.updateAuth({ token, expires_at });
// widget.updateHostActor(session.host_actor);
Example implementation: examples/hr-multi-tenant/web/src/mindlinkOrgEmbed.ts.
10.2 In-reply navigation (optional but recommended)
Agree Markdown links in the agent knowledge documents:
`Open org chart`
The host page whitelists action names and jumps (HR example: page.overview, page.org, and similar — see mindlinkHostActions.ts).
10.3 Widget experience (aligned with Cadau main site)
The embed widget (mindlink-widget.min.js) already includes: multi-turn conversation quick jump to questions on the right (hover to expand the title), focus back to the input after a reply, floating launcher sticks to the edge and stays in the viewport. Third parties need not reimplement this; upgrade the Cadau-served script.
10.4 Live support and tickets (same as static website embed)
A static marketing site (path A, for example examples/cadau-embed-site) pastes a script into the page; as long as that agent has live support on, “Live support” and “Submit a ticket” appear next to the widget input. Path B (backend minting) uses the same widget script, so both entries are already there. The host must not implement a support queue, message thread, or ticket table.
| | Live support (immediate) | Tickets (async) | |--|---------------------|------------------| | User expectation | Someone chats now | Leave a message and leave; check progress later | | Widget entry | “Live support” | “Submit a ticket”; while waiting, “Don’t wait — submit a ticket instead” | | When available | That agent has live support on, and at least one support person covering this workspace is on duty (a seat in this workspace, or a member of an authorized team who clocked on at the team desk) | Agent has live support on (when nobody is on duty, only this remains) | | Where support works it | Cadau desk Live support | Cadau desk Tickets |
In copy, do not call live help a “ticket”. Mechanism: Cadau docs/core-mechanisms/人工客服.md, 工单.md.
Cadau side (admins)
- Open the agent you are embedding → Manage → Live support → turn on. Assign members of this workspace as support seats, or do not check local people and authorize a support team instead (admins are not support by default).
- Support opens Cadau top bar Support (visible to seats in this workspace or team members; otherwise enter the desk from the Feature menu) and sets status to on duty. Team members covering several customers switch to the team desk on the right of the support desk, then clock on; they need not change the top-bar current workspace.
- After a visitor submits on the host page, the desk list updates (live inbound may play a sound); no full page refresh.
- After you turn live support off on the agent, the widget hides both entries.
Host side (developers, path B)
- The token minted by
embed-sessionalready can call widget live support and tickets; do not build a support desk on the host. - Must pass embed-session’s
host_actorintoMindLinkWidget.init; on signed-in user switch,updateHostActor(orinitagain). If omitted, the same browser may show the previous person’s history / support / tickets. - Frontend still
updateAuthto renew. After token expiry the widget stops empty-spinning requests (avoids 401 floods); after the user re-enters the page or renews, support entries return. - Path B (signed-in host user): history, live support, and tickets are isolated by the current signed-in user — A only sees A’s, not B’s; the same account in another browser is still that person.
- Path A (unsigned website visitor): in-progress requests are isolated by this browser. Opening again in the same browser is the same visitor; a different browser or cleared site data is a new visitor.
- Seat replies always show as “Support” on the customer side; seat phone numbers are not exposed.
- Support colleagues work in Cadau, not in the HR / host admin UI.
Integration tip: the static marketing site and the HR example are often not the same agent or the same workspace. Turning live support on for the static example site does not automatically appear on the HR “action assistant”. Turn it on for the agent you are actually embedding (for example the admin assistant). In this repo npm run setup:hr-work-agents turns live support on for the three demo work agents, uses the integration account as a seat, and sets it on duty. When working inbound: seats in this workspace should confirm the top-bar current workspace is the agent’s workspace; if you use a support team, switch to the team desk on the right to see authorized customer queues — do not rely on switching the top-bar workspace.
11. Recommended rollout order
11.1 Phase 1: prove the widget (A, 1–2 days)
- Cadau deploy is reachable
- Generate an embed token (UI or
write-mindlink-env.mjs) - Static
initon any host page; conversation works - Confirm network (proxy/CORS) has no errors
11.2 Phase 2: host BFF + assignment (B, main conversion)
- Tables + admin API/UI (register, assign)
- Implement embed-session + server
MINDLINK_* - Frontend fetches embed-session after sign-in, then inits
- Finish assignment for test users
11.3 Phase 3: production
- Dedicated integration account, short TTL, HTTPS
- Production build does not contain path A long-lived tokens
- Mount host knowledge documents
- Finish §12 acceptance
12. Acceptance checklist
After conversion, confirm each item:
Cadau
- [ ] Agent created, knowledge documents mounted
- [ ] app_id agreed and matches the host
- [ ] B: integration account can sign in, is in the agent’s workspace, and can call
embed-token - [ ] Registration
mindlink_workspace_idmatches the agent’s real workspace (orlink:mindlink-agenthas been run) - [ ] (Optional) that agent has Live support on, with seats in this workspace or an authorized support team; support can switch on duty / on break / off duty at the desk
Host backend
- [ ] Register and assign APIs work
- [ ] embed-session for assigned users returns
available: trueandaccess_token - [ ] embed-session for unassigned users returns
available: false(not 404) - [ ]
MINDLINK_INTEGRATION_PASSWORDis not in the frontend or a public repo
Host frontend
- [ ] After sign-in the assistant appears bottom-right (or in the chosen container)
- [ ] When different users are assigned different agents, conversation identity is correct (B)
- [ ] (B)
initreceivedhost_actor; two signed-in users cannot see each other’s history, live support, or tickets - [ ] Two visitors can ask at once and each get a reply; when overloaded the widget says “Too many people talking right now. Try again in a moment.”
- [ ] Before token expiry you can refresh, or re-entering the page can mint again
- [ ] (Optional) clicking navigation links in replies jumps to host pages
- [ ] (Optional) when the agent has live support on: the widget has “Live support” (someone must be on duty) and “Submit a ticket”; when nobody is on duty, only submit-ticket remains
- [ ] (Optional) after a visitor submits, Cadau support desk live / ticket lists update without a full page refresh; widget My tickets can show progress
Automation (HR example, optional)
- [ ]
npm run smoke:embedpasses - [ ] (Legacy-host enhancement)
npm run smoke:host-agentpasses
Legacy-host enhancement (if Host Agent Run / business-DB queries are on)
- [ ] embed-session / agent-run include a correct
host_actor(employees includeemployee_id) - [ ] Cadau workspace data connection + access policy configured
- [ ] Employees see only self, manager columns trimmed, conversations do not leak (see
ACCEPTANCE_HOST_LEGACY.md) - [ ] (Optional) generate/analyze agent chosen (
MINDLINK_GENERATE_USER_AGENT_IDin.envor “embed assistant registry”); Jobs and levels can generate job suggestions and does not auto-write the job catalog
Security and ops
- [ ] Production is B only, or A in dev only
- [ ] Cadau API is same-origin proxied by the host or CORS is compliant
- [ ] Revoke/rotate integration-account process is documented
13. FAQ
| Symptom | Check |
|---|---|
| embed-session 404 | Upgrade the host backend; unassigned should return 200 + available:false |
embed-session 502, body contains embed-token / not_found / agent not found | Integration account not in the agent’s workspace; registration mindlink_workspace_id wrong; invite the integration account in Cadau UI, or (SQLite main DB) npm run link:mindlink-agent -- <agent ID>, or re-run setup:mindlink |
link:mindlink-agent says agent not found | Cadau main DB is Postgres so the script cannot read mindlink.db; invite in the UI + check .env, or use setup:mindlink only |
| embed-session 502, cannot switch workspace | MINDLINK_WORKSPACE_ID does not match a workspace the integration account can access |
| Assistant appears but conversation 400 invalid_user_agent | Cadau version is too old; it must allow embed tokens to access agents in the workspace (not only “mine”) |
Deleted HRMS_SEED_MINDLINK_USER_AGENT_ID and the assistant vanished | It will not (when members already have assignments). That var only backfills register for the demo account at startup; everyday widget reads User management assignments. Generate/analyze uses MINDLINK_GENERATE_USER_AGENT_ID or “embed assistant registry” |
| Console CORS / cannot reach Cadau API | api_base_url same origin as the page; in development do not mix localhost and 127.0.0.1 |
| Console 401 embed-session | Host JWT missing or expired |
| A/B assistant behavior differs | Align on the same mindlink_user_agent_id |
| Clicking User management signs you out | Tenant APIs must send Authorization: Bearer |
| A separate token per agent? | No; register the ID, mint a different token at runtime |
| Widget has no “Live support / Submit a ticket” | The agent you are embedding does not have live support on (static site being on does not mean the HR assistant is on); or you are looking at the wrong agent / workspace |
| “Submit a ticket” but no “Live support” | Expected: no support covering this workspace is on duty; a seat in this workspace or an authorized team member clocks on at the Cadau support desk |
| Visitor submitted, support page only updates after refresh | Confirm Cadau backend includes live support push; the seat should keep the desk open and stay signed in |
| Widget support requests keep 401 | Embed token expired without updateAuth; check embed-session TTL and renew |
| Should we build a ticket list in HR? | No; tickets live in the Cadau support desk; the widget has “My tickets” |
| One support group covering several customer workspaces | Use a Cadau support team (the team creates the group, the customer authorizes, listed in service scope); members switch to the team desk; do not add them as members of every customer workspace |
| After switching accounts you still see the previous user’s history / support / tickets | init did not get host_actor, or you did not updateHostActor after switch; see §10.4 |
14. Related docs and reference code
| Resource | Notes |
|---|---|
Website integration | Cadau generic embed, init, navigation |
Embed contract | Token, SSE, security contract |
| [README.md](/docs/sdk-host-embed) | Third-party integration overview |
Host LLM service | Host backend asks a chosen agent to answer |
Host agent run and data scope | Legacy-host user model, new-host checklist, data-connection policy |
HR example docs/HOST_LEGACY_INTEGRATION.md | Legacy-host mapping and SQLite testing (in-repo) |
HR example docs/ACCEPTANCE_HOST_LEGACY.md | Legacy-host enhancement manual acceptance (in-repo) |
HR example docs/HOST_USER_AGENTS.md | HR example API and table summary (in-repo) |
HR example docs/宿主知识文档/00-总则与功能导航.md | Assistant reply bounds (HR example) |
examples/hr-multi-tenant/web/src/mindlinkOrgEmbed.ts | Path B load and mount |
examples/hr-multi-tenant/web/scripts/setup-hr-work-agents.mjs | Three conversation assistants |
examples/hr-multi-tenant/web/scripts/link-mindlink-agent.mjs | Connect an existing Cadau agent locally |
examples/hr-multi-tenant/server/internal/mindlinkclient/ | Mint client |
examples/hr-multi-tenant/server/.env.example | Env template |
15. Maintenance
| Item | Value |
|---|---|
| Doc type | Third-party host conversion handbook |
| Reference implementation | examples/hr-multi-tenant |
| Doc version | 2026-08-17 (aligned with SDK contract V1.5.12) |
| HR example default app_id | Conversation mindlink-embed-hr; generate jobs mindlink-embed-hr-generate |