Skip to main content
The internal Infrastructure → AI Agents module manages root-only agent configs that are served by apps/web webhook routes. It is the first production path for bidirectional agent presence across Discord and Zalo.

Runtime Boundary

Deploying an agent enables an apps/web webhook endpoint:
/api/v1/webhooks/ai-agents/:adapter/:channelId
apps/web loads the channel config from root workspace_secrets, creates a Chat SDK runtime through @tuturuuu/ai/chat-sdk, and runs the response loop with AI SDK ToolLoopAgent. Production deployments use Redis so thread subscriptions, dedupe, and locks survive cold starts and multiple instances. Runtime resolution checks, in order:
  1. Root workspace secret AI_AGENT_CHAT_SDK_STATE_REDIS_URL.
  2. Environment variables AI_AGENT_CHAT_SDK_STATE_REDIS_URL, REDIS_URL, or DOCKER_WEB_REDIS_URL.
  3. The bundled blue/green Docker Redis service at redis://redis:6379 when a blue/green runtime mount or Docker SRH URL is present.
Local development may use the Chat SDK memory state adapter when no durable Redis runtime is configured. Agent response loops and manual external-chat draft generation use the shared Tuturuuu AI memory layer. Memory is scoped to the operator or channel actor plus the channel workspace, then stored through the first-party memory sidecar using the ai_agents product metadata. Do not add agent-specific browser memory calls; all recall, writes, and deletes must stay behind the server-owned @tuturuuu/ai/memory boundary and workspace memory APIs. Webhook URLs are generated from the canonical public platform origin, not the incoming request host. Set AI_AGENT_WEBHOOK_ORIGIN for an explicit override; otherwise WEB_APP_URL, NEXT_PUBLIC_WEB_APP_URL, NEXT_PUBLIC_APP_URL, PLATFORM_BUILD_DEPLOYMENT_URL, and production fallback https://tuturuuu.com are used before local request-origin fallback.

Configuration Storage

Agent configuration stays under root workspace secrets:
AI_AGENT_REGISTRY:<agentId>:meta
AI_AGENT_REGISTRY:<agentId>:instructions
AI_AGENT_REGISTRY:<agentId>:channel:<channelId>:meta
AI_AGENT_REGISTRY:<agentId>:channel:<channelId>:secret:<name>
AI_AGENT_IDENTITY:<wsId>:zalo:<oaId>:<zaloUserId>
AI_AGENT_ZALO_PERSONAL_ENABLED
Admin APIs redact channel secrets. One-time generated values are shown only after rotation. Feature flags for this surface are also root workspace secrets. Experimental personal Zalo account support requires AI_AGENT_ZALO_PERSONAL_ENABLED = true; do not gate it with process environment variables. External chat history is mirrored separately in the private schema:
private.ai_agent_external_threads
private.ai_agent_external_messages
Only server-owned apps/web routes call the private RPCs for listing, syncing, drafting, and sending. Browser clients never read these private tables directly.

Chat Discovery

The shared chat UI displays mirrored external AI-agent threads as read-only AI conversations in the workspace selected for the agent channel. This works in apps/web and the standalone apps/chat app. Chat users can inspect mirrored history there, while root AI-agent admins can use the setup, operations, and manual-response controls from Infrastructure > AI Agents or the apps/chat agent details sidebar. The root workspace may still show enabled agent channel setup entries so operators can jump to channel configuration. Those setup entries are not the message mirror; mirrored external threads use ai-agent-thread-<uuid> conversation IDs. Root AI-agent admins can also manage those setup entries from the standalone apps/chat internal workspace. The internal workspace resolves to ROOT_WORKSPACE_ID, and admin-visible setup conversations include the minimum agent/channel IDs needed by the sidebar operations panel. Non-admin chat viewers still receive scrubbed setup metadata.

Manual Response Console

Infrastructure > AI Agents is the full setup and operations surface:
  • Configure Discord and Zalo credentials, workspace mapping, auto-response, and history sync per channel.
  • Inspect mirrored external threads and messages.
  • Trigger an on-demand external history sync when the adapter supports it, including phone-approved transfer sync for personal Zalo accounts.
  • Generate a draft with a custom operator prompt and mirrored history context.
  • Send the reviewed draft back to the external platform exactly as edited.
Discord can fetch recent thread/channel history through the Chat SDK adapter when the bot token has the required scopes. Official Zalo channels still rely on webhook/runtime events. Personal Zalo channels can import Zalo Web-visible history through Sync history and can request phone-approved mobile transfer history through Sync phone when older messages are only present on the paired mobile device. The same external-thread operations are available from apps/chat for root AI-agent admins when an external AI-agent thread is selected: sync external history, draft a manual response, send the reviewed response, and refresh the mirrored conversation.

Testing From Apps/Chat

Use apps/chat as the operator smoke-test surface after deployment:
  1. Open the internal workspace and select the AI-agent setup conversation.
  2. Confirm setup data loads instead of the metadata-hidden notice.
  3. Run Test as a readiness check for required credentials and channel status. The diagnostics list should pass for agent enabled, channel enabled, deployed status, required secrets, webhook URL, workspace mapping, adapter account mapping, and recent error state. This does not replace a live external-platform round trip.
  4. For Discord, set the generated webhook URL as the Interactions Endpoint URL so Discord performs its endpoint verification. Then install the app and send a live mention/message in the mapped channel. The official references are Discord interactions and the Discord quickstart.
  5. For personal Zalo, set the channel to Personal account, open the operations tab, run Pair QR, scan the QR code from the Zalo mobile app, then run Validate, Sync history, Sync phone, Deploy, and Start. Approve the transfer-sync prompt on the phone when Sync phone requests it. Use a second Zalo account or group sender for the live inbound-message test.
  6. From the setup conversation, open the Thread tab to list recent mirrored external threads for the selected agent channel. Use Sync external thread from that list when a mirrored thread exists.
  7. Select a mirrored ai-agent-thread-<uuid> conversation and use Sync external thread. A zero-message result means the adapter found no newer messages.
  8. Draft and send a manual response from the thread panel, then confirm the mirrored conversation refreshes.

Discord And Zalo Setup

For Discord, configure the app with the generated webhook URL, then store the application ID, public key, bot token, guild ID, and optional mention role IDs on the Discord channel. Discord message events require Gateway delivery. For a self-hosted watcher, run apps/discord/ai_agent_gateway_watcher.py with:
DISCORD_AI_AGENT_GATEWAY_BOT_TOKEN=<the AI-agent Discord bot token>
DISCORD_AI_AGENT_GATEWAY_PLATFORM_URL=https://<platform>
DISCORD_AI_AGENT_GATEWAY_WATCHER_SECRET=<root watcher secret>
Store the same AI_AGENT_DISCORD_GATEWAY_WATCHER_SECRET value as a root workspace secret in apps/web. The watcher calls the deployed apps/web watcher config endpoint to discover enabled, deployed Discord channel webhooks, and apps/web only returns channels mapped to ROOT_WORKSPACE_ID. Set DISCORD_AI_AGENT_GATEWAY_CHANNEL_ID when one watcher should pin a single root-internal Discord channel. The watcher forwards raw Gateway packets using the Chat SDK Discord forwarding contract (GATEWAY_<event> JSON plus x-discord-gateway-token) so apps/web continues to own AI-agent runtime, identity mapping, mirroring, and responses. Gateway-token forwarded webhook requests are rejected for non-root workspaces; normal Discord HTTP interactions still use the configured channel webhook path. Keep the bot token value in runtime secrets only. For local smoke tests, DISCORD_AI_AGENT_GATEWAY_WEBHOOK_URL may be set to one generated Discord channel webhook URL instead of using auto-discovery. For Zalo, configure the bot webhook URL in the Zalo Bot dashboard, set the secret token to the channel’s webhookSecret, and store the bot token plus OA ID on the Zalo channel. For experimental personal Zalo account testing, set the Zalo account mode to Personal account, then use Pair QR from the apps/chat setup conversation or the shared AI-agent operations panel. The QR flow runs zca-js loginQR() server-side, stores personalCookieJson, personalImei, and personalUserAgent as channel secrets, and records zaloPersonalOwnId after confirmation. The browser receives only QR/session status, never raw cookies, IMEI, or user agent values. Manual cookie JSON, IMEI, and user agent entry remains a fallback for local debugging. After either setup path, use Validate to confirm login, then use Sync history to import Zalo Web-visible personal user and group threads into Tuturuuu Chat. When the mobile device owns additional history, use Sync phone and approve the transfer-sync request in the Zalo mobile app. The phone-transfer action is owned by apps/web; apps/chat reaches it only through the internal API proxy, and the browser never receives cookies, IMEI, user agent values, private transfer keys, or raw transfer payloads. Historical sync persists mirrored conversations and messages only; it does not trigger AI auto-responses for old messages. Deploy the channel and use Start/Stop for the live listener lifecycle. Personal Zalo channels do not receive webhooks, and only one Zalo Web listener can be active per personal account at a time. Use test accounts only; the upstream zca-js package is an unofficial Zalo Web automation API and can trigger account locks or bans. For experimental root-internal external chatbots, choose the explicit Internal workspace option in the AI-agent workspace picker. It stores workspaceId = ROOT_WORKSPACE_ID on the channel and keeps the runtime, test, deploy, and mirror paths scoped to the internal workspace.

Mapped User Requirement

Agents never write as a global service user in v1. Each inbound external user must map to a Tuturuuu workspace user before task or calendar tools can run. Discord uses the existing discord_guild_members mapping for the configured guild. Zalo uses root secret identity links:
AI_AGENT_IDENTITY:<wsId>:zalo:<oaId>:<zaloUserId> = <platformUserId>
The mapped Tuturuuu user’s permissions are enforced. Task writes require manage_projects; calendar writes require manage_calendar. If permissions are missing, the agent replies with a permission-aware message instead of writing.

Tool Allowlist

V1 exposes only workspace context, members, task handoff, and calendar tools. Finance, time tracking, memory, image generation, web search, delete tools, and broad marketplace tool selection stay out of scope until there is a separate approval and audit model.