mirror of
https://github.com/multica-ai/multica.git
synced 2026-08-13 03:15:34 +02:00
The first message a new member ever read cost a runtime cold start plus two
model round trips: the opening was produced by a full chat task, so they
watched a spinner for tens of seconds, and a run that failed introduced Mika
as a red error bubble.
Nothing in that reply needed an agent. The multica-onboarding skill already
pinned it to four beats under a length budget, it reads no workspace state and
calls no tool, and every input it personalizes on — language, workspace name,
agent display name — is already in the request. It also never generated quick
actions (starter cards replace them), so the run bought nothing.
The endpoint now writes two rows in one transaction and enqueues nothing:
- the opening the member reads, final on arrival, kind onboarding_opening so
the starter cards still render under it;
- the hidden kickoff, deliberately WITHOUT a task.
The member's first real message adopts that kickoff into its input batch. That
is what carries the onboarding skill instruction and the profile block into the
run that does the first real work, and — because the kickoff quotes the opening
verbatim — what stops Mika introducing herself a second time. She has no memory
of an opening the server wrote.
The cold start does not disappear; it moves to the member's first real request,
where they are waiting on something they asked for with content and three
one-click starter cards already in front of them.
Three shared invariants that a two-row input batch newly exposes:
- Both rows are written in one transaction, so DEFAULT now() gives them an
identical timestamp. The session-list LATERAL picks the last message with
no tiebreaker and ids are random UUIDs, so a tie could select the kickoff —
whose kind makes buildChatLastMessage return nil, reporting no last message
and bringing back the "Start with Mika" card after a perfect onboarding.
The opening is written one microsecond after the kickoff.
- DeleteUserChatMessageByTask deleted every user row of a cancelled turn, so
cancelling the first message would destroy the only copy of the onboarding
context and could hand the member the product's internal prompt as their
restored draft. It now excludes the kickoff, and callers release it back to
unowned so the next send re-adopts it.
- Both reanchor queries would move the kickoff to dispatch time, putting the
product's context AFTER the member's message inside one batch. Both now
exclude it. The regression test fails without that guard.
Also drops the completion-path rule that stamped a kickoff-input turn's reply
as the opening: with the kickoff riding into the first real turn, it would
render the starter cards a second time under a reply that is not an opening.
Co-authored-by: multica-agent <github@multica.ai>