backend/apps/core-api/src/modules/assistant/ (assistant.routes.ts, assistant.controller.ts, assistant.service.ts, assistant.schema.ts). The module has no repository file. The service calls Prisma directly.
Mounting and auth
Every handler goes through
callerOf in the controller:
- Missing
studioIdoruserIdonreq.auththrowsUNAUTHORIZED(401). - Role
TRAINEEthrowsFORBIDDEN(403) withcoaches only. OWNER,HEAD_COACHandSUB_COACHare allowed.
req.auth.
Data scope
Each turn resolves the caller’s access withcoachAccessFrom from middleware/coach-access.ts, using the caller’s Coach row (role, permissions, active).
- A studio owner sees every trainee.
- A coach whose permissions limit them to assigned trainees only sees clients that have a
ClientCoachassignment to them. Their task list is limited to tasks assigned to them or tied to their trainees. - A coach with no
Coachrow, or withtrainees: "all", sees the whole studio. - A dismissed coach (
active: false) getsFORBIDDEN(403) withthis team member has been removed.
studioId and deletedAt: null.
Admin. The service treats the caller as admin when the lane role is OWNER or the Coach row has role HEAD_COACH. Admins get the monthly revenue figure. The system prompt tells the model whether the user is an admin.
Environment variables
The assistant uses
createAnthropic from @ai-sdk/anthropic and generateText from the ai package, with stopWhen: stepCountIs(8) and maxOutputTokens: 1600.
Reply shape
chat and confirm both return an AssistantReply.
textis Hebrew and may contain**bold**and newlines.statsare number cards, at most 8.listis a bullet list, at most 12 lines.proposalis present when the turn produced a pending change.summaryholds human readable “before to after” lines.expiresAtis an ISO timestamp 15 minutes ahead.
Endpoints
POST /v1/web/assistant/chat
Sends one message and returns the assistant’s reply.
Auth: web lane, roles OWNER, HEAD_COACH, SUB_COACH.
string
required
1 to 4000 characters.
string
Accepted by the schema but not used. The controller passes only
message to the service. The thread is always the caller’s single thread.- Throws if no Anthropic provider is configured.
- Reads or creates the thread id in Redis and refreshes its 48 hour expiry.
- Increments the daily counter. Over the cap, it returns a fixed “daily limit reached” reply. That reply is not saved to history.
- Resolves the caller’s scope, loads the stored history and builds the message list. Only the
textof earlier turns is replayed to the model. Stats, lists and proposals from earlier turns are not. - Runs the model with the tool set below.
- Builds the reply. The model is told to finish with the
replytool, and that tool’stext,statsandlistare used. If it did not callreply, the raw model text is used. Ifget_business_statsran and the model sent nostats, the cards from that tool are attached anyway. If anypropose_*tool ran, the last proposal is attached. - Appends the user message and the reply to history.
200.
POST /v1/web/assistant/confirm
Executes a stored proposal.
Auth: web lane, roles OWNER, HEAD_COACH, SUB_COACH.
string
required
The
proposal.id from a chat reply.GETDEL, so it can be used once. A double click, a retry or a second tab gets NOT_FOUND. The stored studioId and userId must match the caller. A mismatch throws FORBIDDEN, and the proposal is still consumed.
The caller’s scope is resolved again at confirm time, so a coach who lost access to a trainee after the proposal was made cannot execute it. After execution, a “confirmed” user line and the result are appended to history.
What each kind does on confirm:
send_message reports soft failures in the reply text with status 200, not as errors: the studio has the TRAINER_MESSAGE notification switched off, the trainee has no registered device, WhatsApp is not connected, or the trainee has no phone. A WhatsApp message sent this way is not stored as a WhatsappMessage row.
Program edits made through the assistant write straight to Program.content. This code path does not send a plan-updated notification.
Response: 200.
A failed confirm does not restore the proposal. The coach has to ask again.
GET /v1/web/assistant/thread
Returns the caller’s stored conversation.
Auth: web lane, roles OWNER, HEAD_COACH, SUB_COACH.
Response: 200. threadId is null when no conversation exists. Messages are oldest first, up to 24.
proposal object after that proposal expired or was confirmed.
DELETE /v1/web/assistant/thread
Starts a new conversation by deleting the thread id and the history.
Auth: web lane, roles OWNER, HEAD_COACH, SUB_COACH.
Response: 204 with no body. Pending proposals and the daily counter are not cleared.
Tools
The model gets eleven tools. Tool inputs are validated twice: bygenerateText and again inside each tool. A validation or lookup failure is returned to the model as an Error: ... string so it can recover. An AppError is rethrown.
Read tools
Program selection for
get_program and the program proposals: the active program of that type, or else the most recently updated of the trainee’s last five. File plans (anything isFilePlan from modules/programs/plan-kind.ts matches) are refused with an error string because they have no days, meals or targets.
get_business_stats returns these cards, all limited to the caller’s scope:
- Active trainees (clients with status
ACTIVE). - New this month (clients created since the first of the month).
- Open tasks.
- Urgent tasks (open tasks with
priority2 or higher). - Renewals in 30 days (active clients whose
endsOnfalls in the next 30 days and who have no scheduled subscription ending later). - Check-ins today (clients whose
lastCheckInAtis today). - Workouts this week (
WorkoutLogrows in the last 7 days). - Monthly revenue, admins only. The sum of
priceAgorotover live subscriptions in the whole studio whose span touches the current month, shown in shekels.
Proposal tools
Each of these stores a proposal in Redis and returns it to the model. Nothing is written to the database.
When several proposals are made in one turn, each is stored but only the last one is returned in the reply.
The reply tool
reply takes text (required), stats (up to 8) and list (up to 12). It writes nothing. It is how the model hands back a structured answer.
What the assistant cannot do
There are no tools for deleting anything, for charges and payments, or for team management. The system prompt tells the model to say that these are done in the app.Redis keys
History, thread and cap keys are keyed by user id only. They do not include the studio id.
Limits
Related pages
- WhatsApp AI agent for the WhatsApp version, which runs its tools through the automation API.
- Notification configs for the
TRAINER_MESSAGEtype the push channel uses. - WhatsApp for connecting the SmartSend key the WhatsApp channel needs.