diff --git a/plugins/ai-bot/src/rest.ts b/plugins/ai-bot/src/rest.ts
index 04b243ba28..d6a71da8a7 100644
--- a/plugins/ai-bot/src/rest.ts
+++ b/plugins/ai-bot/src/rest.ts
@@ -74,4 +74,10 @@ export interface PostTranscriptRequest {
   transcript: string
   participant: Ref<Person>
   roomName: string
+  final: boolean
+}
+
+export interface IdentityResponse {
+  identity: Ref<Person>
+  name: string
 }
diff --git a/plugins/love-resources/src/components/Room.svelte b/plugins/love-resources/src/components/Room.svelte
index 8fc32ddd3e..66a18590dc 100644
--- a/plugins/love-resources/src/components/Room.svelte
+++ b/plugins/love-resources/src/components/Room.svelte
@@ -58,6 +58,7 @@
     connecting: boolean
     muted: boolean
     mirror: boolean
+    isAgent: boolean
   }
 
   let participants: ParticipantData[] = []
@@ -80,7 +81,7 @@
         const element = track.attach()
         attachTrack(element, participant)
       }
-      updateStyle(participants.length, $screenSharing)
+      updateStyle(getActiveParticipants(participants).length, $screenSharing)
     } else {
       const part = participants.find((p) => p._id === participant.identity)
       if (part !== undefined) {
@@ -105,7 +106,7 @@
       } else {
         track.detach(screen)
       }
-      updateStyle(participants.length, $screenSharing)
+      updateStyle(getActiveParticipants(participants).length, $screenSharing)
     }
   }
 
@@ -117,7 +118,7 @@
         const element = publication.track.attach()
         void attachTrack(element, participant)
       }
-      updateStyle(participants.length, $screenSharing)
+      updateStyle(getActiveParticipants(participants).length, $screenSharing)
     } else {
       const part = participants.find((p) => p._id === participant.identity)
       if (part !== undefined) {
@@ -135,7 +136,8 @@
         name: participant.name ?? '',
         muted: !participant.isMicrophoneEnabled,
         mirror: participant.isLocal,
-        connecting: false
+        connecting: false,
+        isAgent: participant.isAgent
       })
     }
     participants = participants
@@ -166,11 +168,12 @@
       name: participant.name ?? '',
       muted: !participant.isMicrophoneEnabled,
       mirror: participant.isLocal,
-      connecting: false
+      connecting: false,
+      isAgent: participant.isAgent
     }
     participants.push(value)
     participants = participants
-    updateStyle(participants.length, $screenSharing)
+    updateStyle(getActiveParticipants(participants).length, $screenSharing)
   }
 
   function handleParticipantDisconnected (participant: RemoteParticipant): void {
@@ -179,7 +182,7 @@
       participants.splice(index, 1)
       participants = participants
     }
-    updateStyle(participants.length, $screenSharing)
+    updateStyle(getActiveParticipants(participants).length, $screenSharing)
   }
 
   function muteHandler (publication: TrackPublication, participant: Participant): void {
@@ -207,7 +210,7 @@
     if (publication?.track?.kind === Track.Kind.Video) {
       if (publication.track.source === Track.Source.ScreenShare) {
         publication.track.detach(screen)
-        updateStyle(participants.length, $screenSharing)
+        updateStyle(getActiveParticipants(participants).length, $screenSharing)
       } else {
         const index = participants.findIndex((p) => p._id === participant.identity)
         if (index !== -1) {
@@ -285,10 +288,6 @@
 
   onDestroy(
     infos.subscribe((data) => {
-      const aiParticipant = aiPersonId !== undefined ? participants.find(({ _id }) => _id === aiPersonId) : undefined
-      if (aiParticipant && !data.some((it) => it.room === room._id && it.person === aiParticipant._id)) {
-        participants = participants.filter(({ _id }) => _id !== aiPersonId)
-      }
       for (const info of data) {
         if (info.room !== room._id) continue
         const current = participants.find((p) => p._id === info.person)
@@ -298,12 +297,13 @@
           name: info.name,
           muted: true,
           mirror: false,
-          connecting: true
+          connecting: true,
+          isAgent: aiPersonId === info.person
         }
         participants.push(value)
       }
       participants = participants
-      updateStyle(participants.length, $screenSharing)
+      updateStyle(getActiveParticipants(participants).length, $screenSharing)
     })
   )
 
@@ -342,6 +342,12 @@
     }
   }
   $: if (((document.fullscreenElement && !$isFullScreen) || $isFullScreen) && roomEl) toggleFullscreen()
+
+  function getActiveParticipants (participants: ParticipantData[]): ParticipantData[] {
+    return participants.filter((p) => !p.isAgent || $infos.some(({ person }) => person === p._id))
+  }
+
+  $: activeParticipants = getActiveParticipants(participants)
 </script>
 
 <div bind:this={roomEl} class="flex-col-center w-full h-full right-navpanel-border" class:theme-dark={$isFullScreen}>
@@ -376,7 +382,7 @@
         style={$screenSharing ? '' : gridStyle}
         class:scroll-m-0={$screenSharing}
       >
-        {#each participants as participant, i (participant._id)}
+        {#each activeParticipants as participant, i (participant._id)}
           <ParticipantView
             bind:this={participantElements[i]}
             {...participant}
diff --git a/services/ai-bot/love-agent/package.json b/services/ai-bot/love-agent/package.json
index 61a50862a0..8e681df53e 100644
--- a/services/ai-bot/love-agent/package.json
+++ b/services/ai-bot/love-agent/package.json
@@ -19,11 +19,6 @@
     "lint:fix": "eslint --fix src/**/*.ts",
     "format": "prettier --write src/**/*.ts && pnpm lint:fix"
   },
-  "pnpm": {
-    "overrides": {
-      "livekit-server-sdk": "2.7.3"
-    }
-  },
   "devDependencies": {
     "@types/node": "~20.11.16",
     "@typescript-eslint/eslint-plugin": "^6.11.0",
@@ -40,7 +35,7 @@
   },
   "dependencies": {
     "@deepgram/sdk": "^3.9.0",
-    "@livekit/agents": "^0.3.5",
+    "@livekit/agents": "^0.4.1",
     "@livekit/rtc-node": "^0.11.1",
     "dotenv": "^16.4.5"
   }
diff --git a/services/ai-bot/love-agent/pnpm-lock.yaml b/services/ai-bot/love-agent/pnpm-lock.yaml
index f6d11bcaa8..26f981af21 100644
--- a/services/ai-bot/love-agent/pnpm-lock.yaml
+++ b/services/ai-bot/love-agent/pnpm-lock.yaml
@@ -4,9 +4,6 @@ settings:
   autoInstallPeers: true
   excludeLinksFromLockfile: false
 
-overrides:
-  livekit-server-sdk: 2.7.3
-
 importers:
 
   .:
@@ -15,8 +12,8 @@ importers:
         specifier: ^3.9.0
         version: 3.9.0
       '@livekit/agents':
-        specifier: ^0.3.5
-        version: 0.3.5
+        specifier: ^0.4.1
+        version: 0.4.1
       '@livekit/rtc-node':
         specifier: ^0.11.1
         version: 0.11.1
@@ -246,8 +243,8 @@ packages:
     resolution: {integrity: sha512-93zYdMES/c1D69yZiKDBj0V24vqNzB/koF26KPaagAfd3P/4gUlh3Dys5ogAK+Exi9QyzlD8x/08Zt7wIKcDcA==}
     deprecated: Use @eslint/object-schema instead
 
-  '@livekit/agents@0.3.5':
-    resolution: {integrity: sha512-qjEIRkr/HdOvEOnvLKZMfnQ472bShQ1Ai2KKng8a1caSDHiLhQBeSb1tzA1Evsmm1k8hilXqh+81/SDj4cfiDA==}
+  '@livekit/agents@0.4.1':
+    resolution: {integrity: sha512-zZnd19CWvm1i6PKzAgUw6gLdZOJ/QKzbIVSLNAZPQphCEEg3cPoe3fEf9XE7o9+n6e5sZ6FfmSUh/c7jxWmujw==}
 
   '@livekit/mutex@1.1.0':
     resolution: {integrity: sha512-XRLG+z/0uoyDioupjUiskjI06Y51U/IXVPJn7qJ+R3J75XX01irYVBM9MpxeJahpVoe9QhU4moIEolX+HO9U9g==}
@@ -1021,8 +1018,8 @@ packages:
     resolution: {integrity: sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ==}
     engines: {node: '>= 0.8.0'}
 
-  livekit-server-sdk@2.7.3:
-    resolution: {integrity: sha512-dBiyMJ2o3Adw7aBVuFxVOlYHmiZtGGS9zVksMuv/wiEVHY+6XSDzo0X67pZVkyGlq1moF4YZAReVY2Dbxve8NQ==}
+  livekit-server-sdk@2.8.1:
+    resolution: {integrity: sha512-l8egXU10jPuRJM2Df9Gk/KPEk6tBV0JEGG19cD5QeQtyIMgqULCCd/5yyG2FRvcWRf7pEyZZMXi63zDn7uaKHQ==}
     engines: {node: '>=19'}
 
   locate-path@6.0.0:
@@ -1069,8 +1066,8 @@ packages:
       encoding:
         optional: true
 
-  object-inspect@1.13.2:
-    resolution: {integrity: sha512-IRZSRuzJiynemAXPYtPe5BoI/RESNYR7TYm50MC5Mqbd3Jmw5y790sErYw3V6SryFJD64b74qQQs9wn5Bg/k3g==}
+  object-inspect@1.13.3:
+    resolution: {integrity: sha512-kDCGIbxkDSXE3euJZZXzc6to7fCrKHNI/hSRQnRuQ+BWjFNzZwiFF8fj/6o2t2G9/jTj8PSIYTfCLelLZEeRpA==}
     engines: {node: '>= 0.4'}
 
   object-keys@1.1.1:
@@ -1559,13 +1556,14 @@ snapshots:
 
   '@humanwhocodes/object-schema@2.0.3': {}
 
-  '@livekit/agents@0.3.5':
+  '@livekit/agents@0.4.1':
     dependencies:
       '@livekit/mutex': 1.1.0
       '@livekit/protocol': 1.27.1
       '@livekit/rtc-node': 0.11.1
+      '@livekit/typed-emitter': 3.0.0
       commander: 12.1.0
-      livekit-server-sdk: 2.7.3
+      livekit-server-sdk: 2.8.1
       pino: 8.21.0
       pino-pretty: 11.3.0
       ws: 8.18.0
@@ -1977,7 +1975,7 @@ snapshots:
       is-string: 1.0.7
       is-typed-array: 1.1.13
       is-weakref: 1.0.2
-      object-inspect: 1.13.2
+      object-inspect: 1.13.3
       object-keys: 1.1.1
       object.assign: 4.1.5
       regexp.prototype.flags: 1.5.3
@@ -2488,7 +2486,7 @@ snapshots:
       prelude-ls: 1.2.1
       type-check: 0.4.0
 
-  livekit-server-sdk@2.7.3:
+  livekit-server-sdk@2.8.1:
     dependencies:
       '@livekit/protocol': 1.27.1
       camelcase-keys: 9.1.3
@@ -2527,7 +2525,7 @@ snapshots:
     dependencies:
       whatwg-url: 5.0.0
 
-  object-inspect@1.13.2: {}
+  object-inspect@1.13.3: {}
 
   object-keys@1.1.1: {}
 
@@ -2748,7 +2746,7 @@ snapshots:
       call-bind: 1.0.7
       es-errors: 1.3.0
       get-intrinsic: 1.2.4
-      object-inspect: 1.13.2
+      object-inspect: 1.13.3
 
   slash@3.0.0: {}
 
diff --git a/services/ai-bot/love-agent/src/agent.ts b/services/ai-bot/love-agent/src/agent.ts
index 8493e7aa4b..e442c2bddb 100644
--- a/services/ai-bot/love-agent/src/agent.ts
+++ b/services/ai-bot/love-agent/src/agent.ts
@@ -13,12 +13,13 @@
 // limitations under the License.
 //
 
-import { cli, defineAgent, type JobContext, WorkerOptions, WorkerPermissions } from '@livekit/agents'
+import { cli, defineAgent, type JobContext, JobRequest, WorkerOptions } from '@livekit/agents'
 import { fileURLToPath } from 'node:url'
 import { RemoteParticipant, RemoteTrack, RemoteTrackPublication, RoomEvent, TrackKind } from '@livekit/rtc-node'
 
 import { STT } from './stt.js'
 import { Metadata, TranscriptionStatus } from './type.js'
+import config from './config.js'
 
 function parseMetadata (metadata: string): Metadata {
   try {
@@ -30,6 +31,45 @@ function parseMetadata (metadata: string): Metadata {
   return {}
 }
 
+async function requestIdentity (roomName: string): Promise<{ identity: string, name: string } | undefined> {
+  try {
+    const res = await fetch(`${config.PlatformUrl}/love/${roomName}/identity`, {
+      method: 'GET',
+      headers: {
+        'Content-Type': 'application/json',
+        Authorization: 'Bearer ' + config.PlatformToken
+      }
+    })
+
+    if (!res.ok) {
+      return undefined
+    }
+    return await res.json()
+  } catch (e) {
+    console.error('Error during request identity', e)
+  }
+}
+
+const requestFunc = async (req: JobRequest): Promise<void> => {
+  const roomName = req.room?.name
+
+  if (roomName == null) {
+    console.error('Room name is undefined', { room: req.room })
+    await req.reject()
+    return
+  }
+
+  const identity = await requestIdentity(roomName)
+
+  if (identity?.identity == null) {
+    console.error('No ai identity', { roomName })
+    await req.reject()
+    return
+  }
+
+  await req.accept(identity.name, identity.identity)
+}
+
 function applyMetadata (data: string | undefined, stt: STT): void {
   if (data == null || data === '') return
   const metadata = parseMetadata(data)
@@ -39,11 +79,13 @@ function applyMetadata (data: string | undefined, stt: STT): void {
   }
 
   if (metadata.transcription === TranscriptionStatus.InProgress) {
+    console.log('Starting transcription', stt.name)
     stt.start()
   } else if (
     metadata.transcription === TranscriptionStatus.Completed ||
     metadata.transcription === TranscriptionStatus.Idle
   ) {
+    console.log('Stopping transcription', stt.name)
     stt.stop()
   }
 }
@@ -109,7 +151,7 @@ export function runAgent (): void {
   cli.runApp(
     new WorkerOptions({
       agent: fileURLToPath(import.meta.url),
-      permissions: new WorkerPermissions(true, true, true, true, [], true)
+      requestFunc
     })
   )
 }
diff --git a/services/ai-bot/love-agent/src/stt.ts b/services/ai-bot/love-agent/src/stt.ts
index 103f5204a0..02ee5aae86 100644
--- a/services/ai-bot/love-agent/src/stt.ts
+++ b/services/ai-bot/love-agent/src/stt.ts
@@ -55,25 +55,8 @@ export class STT {
   private readonly dgConnectionBySid = new Map<string, ListenLiveClient>()
   private readonly intervalBySid = new Map<string, NodeJS.Timeout>()
 
-  private readonly transcriptsBySid = new Map<string, { value: string, startedOn: number }>()
-
-  private readonly interval: NodeJS.Timeout
-
-  constructor (private readonly name: string) {
+  constructor (readonly name: string) {
     this.deepgram = createClient(config.DeepgramApiKey)
-    this.interval = this.interval = setInterval(() => {
-      this.sendTranscriptToPlatform()
-    }, config.TranscriptDelay)
-  }
-
-  sendTranscriptToPlatform (): void {
-    const now = Date.now()
-    for (const [sid, transcript] of this.transcriptsBySid.entries()) {
-      if (now - transcript.startedOn > config.TranscriptDelay) {
-        void this.sendToPlatform(transcript.value, sid)
-        this.transcriptsBySid.delete(sid)
-      }
-    }
   }
 
   updateLanguage (language: string): void {
@@ -175,32 +158,16 @@ export class STT {
     dgConnection.on(LiveTranscriptionEvents.Open, () => {
       dgConnection.on(LiveTranscriptionEvents.Transcript, (data: LiveTranscriptionEvent) => {
         const transcript = data?.channel?.alternatives[0].transcript
-        if (transcript != null && transcript !== '') {
-          const prevData = this.transcriptsBySid.get(sid)
-          const prevValue = prevData?.value ?? ''
-          if (data.is_final === true) {
-            // TODO: how to join the final transcript ?
-            this.transcriptsBySid.set(sid, {
-              value: prevValue + ' ' + transcript,
-              startedOn: prevData?.startedOn ?? Date.now()
-            })
-          }
-          if (data.speech_final === true) {
-            const result = this.transcriptsBySid.get(sid)?.value
-            if (result != null) {
-              void this.sendToPlatform(result, sid)
-            }
-            this.transcriptsBySid.delete(sid)
-          }
+        const hasTranscript = transcript != null && transcript !== ''
+
+        if (!hasTranscript) {
+          return
         }
-      })
 
-      dgConnection.addListener(LiveTranscriptionEvents.UtteranceEnd, () => {
-        const result = this.transcriptsBySid.get(sid)?.value ?? ''
-        if (result.length > 0) {
-          void this.sendToPlatform(result, sid)
-
-          this.transcriptsBySid.delete(sid)
+        if (data.speech_final === true) {
+          void this.sendToPlatform(transcript, sid, true)
+        } else if (data.is_final === true) {
+          void this.sendToPlatform(transcript, sid, false)
         }
       })
 
@@ -232,11 +199,12 @@ export class STT {
     }
   }
 
-  async sendToPlatform (transcript: string, sid: string): Promise<void> {
+  async sendToPlatform (transcript: string, sid: string, isFinal = false): Promise<void> {
     const request = {
       transcript,
       participant: this.participantBySid.get(sid)?.identity,
-      roomName: this.name
+      roomName: this.name,
+      final: isFinal
     }
 
     try {
@@ -254,10 +222,9 @@ export class STT {
   }
 
   close (): void {
-    clearInterval(this.interval)
-    for (const sid of this.transcriptsBySid.keys()) {
-      this.trackBySid.delete(sid)
-      this.participantBySid.delete(sid)
+    this.trackBySid.clear()
+    this.participantBySid.clear()
+    for (const sid of this.dgConnectionBySid.keys()) {
       this.stopDeepgram(sid)
     }
   }
diff --git a/services/ai-bot/pod-ai-bot/src/controller.ts b/services/ai-bot/pod-ai-bot/src/controller.ts
index 584ef7cb1b..fdc7b74905 100644
--- a/services/ai-bot/pod-ai-bot/src/controller.ts
+++ b/services/ai-bot/pod-ai-bot/src/controller.ts
@@ -21,6 +21,7 @@ import {
   AITransferEventRequest,
   ConnectMeetingRequest,
   DisconnectMeetingRequest,
+  IdentityResponse,
   OnboardingEvent,
   OnboardingEventRequest,
   OpenChatInSidebarData,
@@ -282,17 +283,28 @@ export class AIControl {
     await wsClient.loveDisconnect(request)
   }
 
-  async processLoveTranscript (request: PostTranscriptRequest): Promise<void> {
-    const parsed = request.roomName.split('_')
-
-    if (parsed.length < 3) return
-
+  async getLoveIdentity (roomName: string): Promise<IdentityResponse | undefined> {
+    const parsed = roomName.split('_')
     const workspace = parsed[0]
-    const roomId = parsed[parsed.length - 1]
+
+    if (workspace === null) return
 
     const wsClient = await this.getWorkspaceClient(workspace)
     if (wsClient === undefined) return
 
-    await wsClient.processLoveTranscript(request.transcript, request.participant, roomId as Ref<Room>)
+    return await wsClient.getLoveIdentity()
+  }
+
+  async processLoveTranscript (request: PostTranscriptRequest): Promise<void> {
+    const parsed = request.roomName.split('_')
+    const workspace = parsed[0]
+    const roomId = parsed[parsed.length - 1]
+
+    if (workspace === null || roomId === null) return
+
+    const wsClient = await this.getWorkspaceClient(workspace)
+    if (wsClient === undefined) return
+
+    await wsClient.processLoveTranscript(request.transcript, request.participant, roomId as Ref<Room>, request.final)
   }
 }
diff --git a/services/ai-bot/pod-ai-bot/src/server/server.ts b/services/ai-bot/pod-ai-bot/src/server/server.ts
index 9a385d84a5..9cbda42522 100644
--- a/services/ai-bot/pod-ai-bot/src/server/server.ts
+++ b/services/ai-bot/pod-ai-bot/src/server/server.ts
@@ -23,7 +23,8 @@ import {
   AIEventRequest,
   ConnectMeetingRequest,
   DisconnectMeetingRequest,
-  PostTranscriptRequest
+  PostTranscriptRequest,
+  aiBotAccountEmail
 } from '@hcengineering/ai-bot'
 import { extractToken } from '@hcengineering/server-client'
 
@@ -96,13 +97,18 @@ export function createServer (controller: AIControl): Express {
       await controller.processEvent(token.workspace.name, events as AIEventRequest[])
     })
   )
+
   app.post(
     '/love/transcript',
-    wrapRequest(async (req, res) => {
+    wrapRequest(async (req, res, token) => {
       if (req.body == null || Array.isArray(req.body) || typeof req.body !== 'object') {
         throw new ApiError(400)
       }
 
+      if (token.email !== aiBotAccountEmail) {
+        throw new ApiError(401)
+      }
+
       await controller.processLoveTranscript(req.body as PostTranscriptRequest)
 
       res.status(200)
@@ -140,6 +146,25 @@ export function createServer (controller: AIControl): Express {
     })
   )
 
+  app.get(
+    '/love/:roomName/identity',
+    wrapRequest(async (req, res, token) => {
+      if (token.email !== aiBotAccountEmail) {
+        throw new ApiError(401)
+      }
+
+      const roomName = req.params.roomName
+      const resp = await controller.getLoveIdentity(roomName)
+
+      if (resp === undefined) {
+        throw new ApiError(404)
+      }
+
+      res.status(200)
+      res.json(resp)
+    })
+  )
+
   app.post(
     '/onboarding',
     wrapRequest(async (req, res) => {
diff --git a/services/ai-bot/pod-ai-bot/src/workspace/love.ts b/services/ai-bot/pod-ai-bot/src/workspace/love.ts
index a10b4f0b15..5ff30aaac6 100644
--- a/services/ai-bot/pod-ai-bot/src/workspace/love.ts
+++ b/services/ai-bot/pod-ai-bot/src/workspace/love.ts
@@ -9,7 +9,8 @@ import core, {
   TxCreateDoc,
   TxUpdateDoc,
   MeasureContext,
-  Markup
+  Markup,
+  generateId
 } from '@hcengineering/core'
 import { Person } from '@hcengineering/contact'
 import love, {
@@ -21,11 +22,27 @@ import love, {
   TranscriptionStatus
 } from '@hcengineering/love'
 import { ConnectMeetingRequest } from '@hcengineering/ai-bot'
-import chunter from '@hcengineering/chunter'
+import chunter, { ChatMessage } from '@hcengineering/chunter'
 import { jsonToMarkup, MarkupNodeType } from '@hcengineering/text'
 
 import config from '../config'
 
+class Transcriptions {
+  private readonly transcriptionByPerson = new Map<Ref<Person>, { _id: Ref<ChatMessage>, text: string }>()
+
+  get (person: Ref<Person>): { _id: Ref<ChatMessage>, text: string } | undefined {
+    return this.transcriptionByPerson.get(person)
+  }
+
+  set (person: Ref<Person>, value: { _id: Ref<ChatMessage>, text: string }): void {
+    this.transcriptionByPerson.set(person, value)
+  }
+
+  delete (person: Ref<Person>): void {
+    this.transcriptionByPerson.delete(person)
+  }
+}
+
 export class LoveController {
   private readonly roomSidById = new Map<Ref<Room>, string>()
   private readonly connectedRooms = new Set<Ref<Room>>()
@@ -33,6 +50,7 @@ export class LoveController {
   private participantsInfo: ParticipantInfo[] = []
   private rooms: Room[] = []
   private readonly meetingMinutes: MeetingMinutes[] = []
+  private readonly activeTranscriptions = new Map<Ref<Room>, Transcriptions>()
 
   constructor (
     private readonly workspace: string,
@@ -47,6 +65,13 @@ export class LoveController {
     }, 5000)
   }
 
+  getIdentity (): { identity: Ref<Person>, name: string } {
+    return {
+      identity: this.currentPerson._id,
+      name: this.currentPerson.name
+    }
+  }
+
   txHandler (txes: Tx[]): void {
     const hierarchy = this.client.getHierarchy()
     for (const tx of txes) {
@@ -141,6 +166,8 @@ export class LoveController {
   async disconnect (roomId: Ref<Room>): Promise<void> {
     this.ctx.info('Disconnecting', { roomId })
 
+    this.activeTranscriptions.delete(roomId)
+
     const participant = await this.getRoomParticipant(roomId, this.currentPerson._id)
     if (participant !== undefined) {
       await this.client.remove(participant)
@@ -156,7 +183,7 @@ export class LoveController {
     this.connectedRooms.delete(roomId)
   }
 
-  async processTranscript (text: string, person: Ref<Person>, roomId: Ref<Room>): Promise<void> {
+  async processTranscript (text: string, person: Ref<Person>, roomId: Ref<Room>, final: boolean): Promise<void> {
     const room = await this.getRoom(roomId)
     const participant = await this.getRoomParticipant(roomId, person)
 
@@ -168,19 +195,40 @@ export class LoveController {
     const personAccount = this.client.getModel().getAccountByPersonId(participant.person)[0]
     if (doc === undefined) return
 
-    await this.client.addCollection(
-      chunter.class.ChatMessage,
-      core.space.Workspace,
-      doc._id,
-      doc._class,
-      'transcription',
-      {
-        message: this.transcriptToMarkup(text)
-      },
-      undefined,
-      undefined,
-      personAccount._id
-    )
+    const transcriptions = this.activeTranscriptions.get(roomId) ?? new Transcriptions()
+    const activeTranscription = transcriptions.get(participant.person)
+
+    if (activeTranscription === undefined) {
+      const _id = generateId<ChatMessage>()
+      if (!final) {
+        transcriptions.set(participant.person, { _id, text })
+        this.activeTranscriptions.set(roomId, transcriptions)
+      }
+
+      await this.client.addCollection(
+        chunter.class.ChatMessage,
+        core.space.Workspace,
+        doc._id,
+        doc._class,
+        'transcription',
+        {
+          message: this.transcriptToMarkup(text)
+        },
+        _id,
+        undefined,
+        personAccount._id
+      )
+    } else {
+      const mergedText = activeTranscription.text + ' ' + text
+      if (!final) {
+        transcriptions.set(participant.person, { _id: activeTranscription._id, text: mergedText })
+      } else {
+        transcriptions.delete(participant.person)
+      }
+      await this.client.updateDoc(chunter.class.ChatMessage, core.space.Workspace, activeTranscription._id, {
+        message: this.transcriptToMarkup(mergedText)
+      })
+    }
   }
 
   hasActiveConnections (): boolean {
diff --git a/services/ai-bot/pod-ai-bot/src/workspace/workspaceClient.ts b/services/ai-bot/pod-ai-bot/src/workspace/workspaceClient.ts
index 60e1dc8582..7498f327d7 100644
--- a/services/ai-bot/pod-ai-bot/src/workspace/workspaceClient.ts
+++ b/services/ai-bot/pod-ai-bot/src/workspace/workspaceClient.ts
@@ -18,7 +18,8 @@ import aiBot, {
   AIMessageEventRequest,
   AITransferEventRequest,
   ConnectMeetingRequest,
-  DisconnectMeetingRequest
+  DisconnectMeetingRequest,
+  IdentityResponse
 } from '@hcengineering/ai-bot'
 import chunter, {
   ChatMessage,
@@ -710,7 +711,7 @@ export class WorkspaceClient {
     await this.love.disconnect(request.roomId)
   }
 
-  async processLoveTranscript (text: string, participant: Ref<Person>, room: Ref<Room>): Promise<void> {
+  async processLoveTranscript (text: string, participant: Ref<Person>, room: Ref<Room>, final: boolean): Promise<void> {
     // Just wait initialization
     await this.opClient
 
@@ -719,7 +720,16 @@ export class WorkspaceClient {
       return
     }
 
-    await this.love.processTranscript(text, participant, room)
+    await this.love.processTranscript(text, participant, room, final)
+  }
+
+  async getLoveIdentity (): Promise<IdentityResponse | undefined> {
+    // Just wait initialization
+    await this.opClient
+
+    if (this.love === undefined) return
+
+    return this.love.getIdentity()
   }
 
   canClose (): boolean {