Host LLM service
If the host backend used to call a large model directly, switch to: a chosen work agent in Cadau answers instead. Users need not sign in to Cadau or open the page widget.
Source docs/en/site/sdk-host-llm.md
If the host backend used to call a large model directly, switch to: a chosen work agent in Cadau answers instead. Users need not sign in to Cadau or open the page widget.
This phase: the whole host shares one Cadau workspace (see
Host agent run and data scope§0.3).Reference implementation:
examples/hr-multi-tenantPOST …/me/agent-run(Q&A) andPOST …/positions/suggest-for-org(staffing suggestions).
1. How this differs from the page widget
| Page assistant (embed widget) | Host LLM service (this page) | |
|---|---|---|
| Who starts it | Widget in the browser | Host backend |
| What the user sees | Bottom-right conversation overlay | You decide (approval summary, bot API, batch job… the user may never see Cadau) |
| Which agent | The one assigned to the signed-in user | Chosen on the server: everyday Q&A can use the same assignment; generate/analyze uses a task agent (§5.1), not the page assistant |
| Who is speaking | host_actor passed to the widget | Same host_actor in the request body |
| Where the conversation lands | Embed conversation | The same kind of embed conversation (isolated by signed-in user) |
Both paths share agent knowledge, data-connection policy, and “who this is”. Do not run one permission model on the page and another on the server.
Do not expose this API to the browser; page conversation still goes through the widget. The integration-account password stays on the host server only.
2. Call chain (user view)
sequenceDiagram participant App as Host business (server) participant BFF as Host integration layer participant ML as Cadau participant Agent as Chosen work agent App->>BFF: Signed-in user needs one answer BFF->>BFF: Look up this user's assigned agent + assemble current-user identity BFF->>ML: Integration account signs in and enters the workspace BFF->>ML: Ask this agent to answer (message + current-user identity) ML->>Agent: Answer using knowledge, tools, and data policy Agent-->>ML: Reply ML-->>BFF: Reply + conversation id BFF-->>App: Return as your business API already does
The agent is assigned by the host admin. End users do not pick “which model”.
3. What to prepare on Cadau
Same as path B embed; you do not need a second account:
- Workspace + template agent (knowledge already mounted)
- Integration account already in that workspace (need not own the agent)
- (If you query a business DB) workspace data connection and access policy already written
Details: Host agent run and data scope §1.
4. Integration: Cadau API
Call from the host server only. Auth is the access token after the integration account signs in (first login, then switch workspace as needed).
POST /api/v1/host/agent-runs
Authorization: Bearer <integration-account access token>
Content-Type: application/json
{
"user_agent_id": "<agent UUID>",
"app_id": "your-app-id",
"workspace_id": "<workspace UUID; omit to use the token's current workspace>",
"message": "What is my job level this month?",
"session_id": "",
"stream": false,
"fresh": false,
"host_actor": {
"external_user_id": "host user id",
"actor_kind": "employee",
"display_name": "Chen Chen",
"tenant_external_id": "host tenant id",
"employee_id": "employee file id",
"roles": ["employee"]
}
}
4.1 Request fields
| Field | Required | Meaning |
|---|---|---|
user_agent_id | Yes | The work agent that answers; must be in that workspace |
message | Yes | User question or task description |
host_actor | Yes | Current host signed-in user. When actor_kind is employee, employee_id is required |
app_id | No | Default mindlink-embed; match embed registration so conversations can be grouped |
workspace_id | No | Default: integration account’s current workspace; this phase same as MINDLINK_WORKSPACE_ID |
session_id | No | For follow-up, pass the id from last time; empty defaults to this user’s latest under this agent, or creates one |
fresh | No | When true and no session_id, always create a new conversation (fits one-shot jobs such as “generate jobs for a department”, so department A’s context does not continue into department B) |
stream | No | See §4.3 |
4.2 Non-stream response (stream false or omitted)
{
"session_id": "…",
"request_id": "…",
"reply": "full assistant text",
"message_id": "…",
"user_agent_id": "…",
"workspace_id": "…",
"app_id": "…",
"host_actor": { }
}
| Field | Meaning |
|---|---|
reply | Full answer for the business side to show or store |
session_id | Pass on the next follow-up; the same signed-in user will not continue someone else’s |
request_id | Trace id for this turn |
Common failures: unauthorized (not signed in), forbidden_embed_token (used an embed token by mistake), validation_error (missing message / agent / invalid identity), workspace_required, not_found (agent not in that workspace), host_agent_run_failed (answer failed).
4.3 About stream: true
The API accepts the field, but it is not token-by-token like the page widget: the server still finishes the run, then emits SSE session, delta (the whole reply), done in order.
The HR example integration layer always takes the full reply non-streaming. For real streaming, use the page widget conversation channel.
5. How the host should wrap this
Do not let every business module take the integration account and call Cadau itself. Wrap it as “one Q&A for the current signed-in user”:
- Check host sign-in and permissions
- Look up this user’s assigned agent (if none, fail clearly; do not silently pick another)
- Assemble the same current-user identity as embed-session
- After the integration account enters the workspace, call the API above
- Return
reply/session_idto the business
HR example:
- Everyday Q&A:
POST /api/v1/tenants/{tenant}/me/agent-run(uses this user’s assigned conversation assistant) - Task-style generate:
POST /api/v1/tenants/{tenant}/positions/suggest-for-org(calls the chosen generate/analyze agent) - How to choose:
MINDLINK_GENERATE_USER_AGENT_IDin server.env, or host “User management → Embed assistant registry” - Client: mint/run helpers in
examples/hr-multi-tenant/server/internal/mindlinkclient/ - Acceptance:
npm run smoke:host-agentinexamples/hr-multi-tenant/web(Q&A path); job suggestions accepted by hand on the Jobs and levels page
5.1 Conversation assistant vs generate/analyze agent
The page widget uses a conversation assistant (admin / manager / employee self-service): people chat, live support can be on. Generate, analyze, fill forms are called by the host backend on another chosen agent (may differ from the conversation assistant, or reuse an existing one).
Create and configure it in Cadau first (knowledge, data connection, and similar), then point at it on the host. Do not have a script silently create one. There are only these two places to point; all analyze calls go here:
- Server
.env:MINDLINK_GENERATE_USER_AGENT_ID=<Cadau agent UUID>(deploy default) - Host settings: “User management → Embed assistant registry → Generate and analyze agent” (after save, overrides the env var)
Do not confuse with HRMS_SEED_MINDLINK_USER_AGENT_ID: that only backfills register/assign for the demo account when the HR example starts. It is not the generate/analyze agent.
| Conversation assistant | Generate / analyze | |
|---|---|---|
| Who uses it | Signed-in user asks on the page | A host button / batch job |
| How it is chosen | Assigned per user | .env or host settings: one |
| App id | Widget MINDLINK_APP_ID | Default mindlink-embed-hr-generate (conversations stay separate from chat; HR example can override with MINDLINK_GENERATE_APP_ID, need not put it in .env) |
| Conversation | Follow-up allowed by default | Each job passes fresh: true |
| Write to DB | Assistant only answers | Host previews first; a person confirms before writing business tables |
HR example: pick a department → suggest which jobs that department should have (Jobs and levels page). Results are shown only; they are not written to the job catalog automatically.
6. Conversations and isolation
- Conversations are stored in Cadau, owned by the integration account, tagged with the current host user id.
- Under the same agent, user A’s and user B’s answer history do not continue into each other.
- Follow-up: pass last
session_idback; if the conversation already belongs to another user, the API rejects. - Without
session_id: default is this user’s latest under “same workspace + same agent + same app id”; if none, create one. - One-shot generate jobs: pass
fresh: true(and do not sendsession_id); create new each time so the previous department’s suggestions do not pollute this run.
Page-widget conversations and answer-on-behalf conversations are isolated by the same user id, but they are not forced onto one timeline. If you want “continue on the page what the server just asked”, agree whether to share session_id (usually keep the two entries separate).
7. What the agent actually does
Answering uses that agent’s knowledge, skills, and data connections — the same as asking the same assistant on the page:
- The system includes “which host user this is” (name, roles, employee file, and similar), so it does not ask the user for an employee number to “confirm identity”
- Business-DB queries must go through the data connection; rows/columns are forced by access policy; the model cannot drop them
- Employee self-service can only help look up self (policy + identity hint together)
How to write policy: Host agent run and data scope §3.
8. Integration checklist
- [ ] Integration account can sign in and enter the agent’s workspace
- [ ] The chosen agent ID is in that workspace
- [ ] Request carries full current-user identity; employee self-service includes employee file id
- [ ] Users with no assigned assistant: host API fails clearly, instead of picking a random agent
- [ ] Two signed-in users answering in sequence get different
session_ids and content does not leak - [ ] The same user on a second call with
session_id(or without, using auto-continue) can continue - [ ] Task-style calls use a separate agent + separate
app_id+fresh: true; preview before writing to the DB - [ ] (Optional)
npm run smoke:host-agentpasses - [ ] Integration password is not in the frontend or a public repo
9. FAQ
| Symptom | Check |
|---|---|
| Forbidden / agent not found | Integration account not in the workspace; agent ID and workspace mismatch; did not switch |
| Provide a valid host user identity | Missing host_actor or external_user_id; employee missing employee_id |
| Employee user must bind an employee file | Self-service account is not yet bound to a person file in this tenant |
| Calling the assistant failed / model not configured | Cadau-side model is not ready |
| Answered rows or columns they should not see | Data-connection access policy not written or not narrowed by current-user identity |
| Browser calling this API directly | Don’t; the page uses the widget, answering on behalf is host-server only |
10. Maintenance
| Item | Value |
|---|---|
| Doc type | Host-backend LLM answer-on-behalf integration notes |
| Cadau API | POST /api/v1/host/agent-runs |
| Reference example | examples/hr-multi-tenant → POST …/me/agent-run; generate/analyze POST …/positions/suggest-for-org (agent chosen by .env / host settings) |
| Related | Host agent run and data scope, HR embed example, Embed contract V1.5.12 |