hindsight-livekit 0.1.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,21 @@
1
+ name: Release
2
+
3
+ # Publishes to PyPI when a version tag (e.g. v0.1.0) is pushed, using PyPI
4
+ # Trusted Publishing: no API token is stored in the repo.
5
+ on:
6
+ push:
7
+ tags: ["v*"]
8
+
9
+ jobs:
10
+ publish:
11
+ runs-on: ubuntu-latest
12
+ environment: pypi
13
+ permissions:
14
+ id-token: write
15
+ steps:
16
+ - uses: actions/checkout@v6
17
+ - uses: astral-sh/setup-uv@v7
18
+ - name: Check the tag matches the package version
19
+ run: test "v$(uvx --from toml-cli toml get --toml-path pyproject.toml project.version)" = "$GITHUB_REF_NAME"
20
+ - run: uv build
21
+ - run: uv publish --trusted-publishing always
@@ -0,0 +1,42 @@
1
+ name: Test
2
+
3
+ on:
4
+ push:
5
+ branches: [main]
6
+ pull_request:
7
+
8
+ jobs:
9
+ test:
10
+ runs-on: ubuntu-latest
11
+ strategy:
12
+ fail-fast: false
13
+ matrix:
14
+ include:
15
+ # Oldest supported versions
16
+ - python: "3.11"
17
+ deps: "livekit-agents==1.2.0 hindsight-client==0.6.0"
18
+ # Latest versions
19
+ - python: "3.12"
20
+ deps: ""
21
+ - python: "3.14"
22
+ deps: ""
23
+ steps:
24
+ - uses: actions/checkout@v6
25
+ - uses: astral-sh/setup-uv@v7
26
+ - name: Lint
27
+ run: |
28
+ uvx ruff check .
29
+ uvx ruff format --check .
30
+ - name: Test
31
+ run: |
32
+ uv venv --python ${{ matrix.python }}
33
+ # LiveKit 1.2-1.5 import httpx without declaring it
34
+ uv pip install -e . httpx pytest pytest-asyncio ${{ matrix.deps }}
35
+ uv run --no-sync pytest tests -v
36
+
37
+ build:
38
+ runs-on: ubuntu-latest
39
+ steps:
40
+ - uses: actions/checkout@v6
41
+ - uses: astral-sh/setup-uv@v7
42
+ - run: uv build
@@ -0,0 +1,9 @@
1
+ .venv/
2
+ __pycache__/
3
+ *.egg-info/
4
+ dist/
5
+ build/
6
+ .env
7
+ .env.local
8
+ .pytest_cache/
9
+ .ruff_cache/
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Naman
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
@@ -0,0 +1,242 @@
1
+ Metadata-Version: 2.5
2
+ Name: hindsight-livekit
3
+ Version: 0.1.0
4
+ Summary: LiveKit Agents integration for Hindsight - persistent memory for voice AI agents
5
+ Project-URL: Homepage, https://github.com/namandidthis/hindsight-livekit
6
+ Project-URL: Repository, https://github.com/namandidthis/hindsight-livekit
7
+ Project-URL: Issues, https://github.com/namandidthis/hindsight-livekit/issues
8
+ Project-URL: Hindsight, https://github.com/vectorize-io/hindsight
9
+ Author-email: Naman <naman6176@gmail.com>
10
+ License: MIT
11
+ License-File: LICENSE
12
+ Keywords: agents,ai,hindsight,livekit,memory,realtime,voice
13
+ Classifier: Development Status :: 4 - Beta
14
+ Classifier: Intended Audience :: Developers
15
+ Classifier: License :: OSI Approved :: MIT License
16
+ Classifier: Programming Language :: Python :: 3
17
+ Classifier: Programming Language :: Python :: 3.11
18
+ Classifier: Programming Language :: Python :: 3.12
19
+ Classifier: Programming Language :: Python :: 3.13
20
+ Classifier: Programming Language :: Python :: 3.14
21
+ Classifier: Topic :: Scientific/Engineering :: Artificial Intelligence
22
+ Requires-Python: >=3.11
23
+ Requires-Dist: hindsight-client>=0.6.0
24
+ Requires-Dist: livekit-agents<2.0,>=1.2
25
+ Description-Content-Type: text/markdown
26
+
27
+ # Hindsight LiveKit Integration
28
+
29
+ Persistent long-term memory for [LiveKit Agents](https://github.com/livekit/agents) voice AI via [Hindsight](https://vectorize.io/hindsight). Your agent knows what it learned in earlier calls, and stores each new conversation as it happens. Works with STT-LLM-TTS pipelines and realtime (speech-to-speech) models.
30
+
31
+ ## Quick Start
32
+
33
+ ```bash
34
+ pip install hindsight-livekit
35
+ # or the latest from GitHub:
36
+ pip install git+https://github.com/namandidthis/hindsight-livekit
37
+ ```
38
+
39
+ > ✨ **Recommended: [Hindsight Cloud](https://ui.hindsight.vectorize.io)** — free tier, no self-hosting required. Sign up and grab an API key in under a minute.
40
+
41
+ ```python
42
+ from livekit.agents import AgentServer, AgentSession, JobContext, inference
43
+ from hindsight_livekit import HindsightAgent, HindsightMemory
44
+
45
+ server = AgentServer()
46
+
47
+
48
+ @server.rtc_session()
49
+ async def entrypoint(ctx: JobContext):
50
+ session = AgentSession(stt=..., tts=...)
51
+
52
+ memory = HindsightMemory(
53
+ bank_id="user-123", # one memory bank per end user
54
+ document_id=ctx.room.name, # one transcript per call
55
+ hindsight_api_url="https://api.hindsight.vectorize.io",
56
+ api_key="hsk_...", # or set HINDSIGHT_API_KEY
57
+ )
58
+ memory.attach(session) # retain the conversation
59
+ ctx.add_shutdown_callback(memory.aclose) # store the end of the call on hang-up
60
+
61
+ await session.start(
62
+ agent=HindsightAgent(
63
+ memory=memory,
64
+ llm=inference.LLM(model="google/gemma-4-31b-it"),
65
+ instructions="You are a friendly voice assistant.",
66
+ ),
67
+ room=ctx.room,
68
+ )
69
+ await ctx.connect()
70
+ ```
71
+
72
+ `HindsightAgent` is a regular LiveKit `Agent`: subclass it, add `@function_tool`s, override `on_enter` (call `await super().on_enter()` first), and so on.
73
+
74
+ Runnable agents built on [agent-starter-python](https://github.com/livekit-examples/agent-starter-python):
75
+
76
+ - [`examples/voice_agent.py`](./examples/voice_agent.py) — STT-LLM-TTS pipeline
77
+ - [`examples/realtime_agent.py`](./examples/realtime_agent.py) — realtime model with memory tools
78
+
79
+ ### Self-hosting (local development)
80
+
81
+ ```bash
82
+ docker run --rm -p 8888:8888 -e HINDSIGHT_API_LLM_API_KEY=$OPENAI_API_KEY ghcr.io/vectorize-io/hindsight:latest
83
+ ```
84
+
85
+ ```python
86
+ memory = HindsightMemory(bank_id="user-123", hindsight_api_url="http://localhost:8888")
87
+ ```
88
+
89
+ See the [Hindsight installation guide](https://hindsight.vectorize.io/developer/installation) for other options. LiveKit itself can be [self-hosted](https://docs.livekit.io/transport/self-hosting/local/) too; the integration works the same either way.
90
+
91
+ ## How It Works
92
+
93
+ ```
94
+ memory.attach(session)
95
+ └─ Session recall starts: "who is this user?" (overlaps with session.start)
96
+
97
+ Agent starts (on_enter)
98
+ └─ Known facts about the user are added to the agent's instructions
99
+
100
+ Before each LLM reply (llm_node) — STT-LLM-TTS pipelines only
101
+ └─ Recall memories relevant to the user's latest message
102
+ └─ Insert them before that message, in a copy of the chat context
103
+
104
+ As the conversation happens (conversation_item_added)
105
+ └─ Append "User (time): …" / "Assistant (time): …" lines to the call's Hindsight document
106
+ ```
107
+
108
+ | | STT-LLM-TTS pipeline | Realtime model |
109
+ |---|---|---|
110
+ | Recall when the agent starts | ✅ | ✅ |
111
+ | Recall before each reply | ✅ | — (no step before a realtime reply; use the tools) |
112
+ | Memory tools (`create_hindsight_tools`) | ✅ | ✅ |
113
+ | Retain the conversation | ✅ | ✅ (needs the model's input audio transcription for the user's side) |
114
+
115
+ **Session recall** runs once per call. `attach()` starts it, so it overlaps with LiveKit connecting the room and the model, and `HindsightAgent.on_enter` adds the result to the instructions. LiveKit runs `on_enter` in the background, so with a pipeline the first `llm_node` call also waits for the lookup and uses the instructions with memories, even if the user speaks first. A realtime model has no such step: a reply that starts before the lookup finishes (bounded by `recall_timeout`) won't include those memories, which is rare in practice since the lookup starts before the call connects.
116
+
117
+ **Per-turn recall** runs in [`llm_node`](https://docs.livekit.io/agents/build/nodes/), the step every LLM call in a pipeline goes through. That means:
118
+
119
+ - It works for spoken turns, chat text input and `session.generate_reply(user_input=...)`. The `on_user_turn_completed` hook only runs for spoken turns.
120
+ - It works with [preemptive generation](https://docs.livekit.io/agents/build/audio/#preemptive-generation): recall starts while LiveKit is still confirming the end of the user's turn, and the preemptive reply is kept. Editing the context in `on_user_turn_completed` would make LiveKit discard it.
121
+ - Memories are only added to a copy of the context for that call, so they never pile up in the session history. When the LLM calls a tool, the follow-up call reuses the same recall result.
122
+
123
+ As in LiveKit's [RAG example](https://docs.livekit.io/agents/build/external-data/), per-turn memories are added as an `assistant` message just before the user's message, wrapped in `<hindsight_memories>` tags with a note telling the model not to read them out.
124
+
125
+ **Retain** follows Hindsight's [guidance for conversations](https://hindsight.vectorize.io/developer/api/retain#retaining-a-conversation): each call is one document (`document_id`), written as `Name (timestamp): text` lines, grown with [`update_mode="append"`](https://hindsight.vectorize.io/developer/api/retain#update_mode) so Hindsight only extracts facts from the new part. Lines are sent once both the user and the agent have spoken, sorted by time, because realtime models can deliver the user's transcript after the reply it prompted. Appends are sent one at a time, in order, in the background, so they never delay a reply. `aclose()` sends whatever is left when the call ends.
126
+
127
+ **Failures never break the call.** If recall fails or takes longer than `recall_timeout`, the agent carries on without memories. If retain fails, the error is logged.
128
+
129
+ ## Memory Tools
130
+
131
+ `create_hindsight_tools()` gives the model `hindsight_retain`, `hindsight_recall` and `hindsight_reflect` tools, like the other Hindsight framework integrations. They are the way for a realtime model to look things up mid-call, and work with pipelines too:
132
+
133
+ ```python
134
+ from hindsight_livekit import HindsightAgent, create_hindsight_tools
135
+
136
+ agent = HindsightAgent(
137
+ memory=memory,
138
+ llm=inference.RealtimeModel(model="openai/gpt-realtime-mini"),
139
+ tools=create_hindsight_tools(bank_id=memory.bank_id, client=memory.client, include_reflect=False),
140
+ instructions=(
141
+ "You have long-term memory of past calls. When the user asks about themselves, "
142
+ "their past, or their preferences, call hindsight_recall first. When they share "
143
+ "something worth remembering, call hindsight_retain."
144
+ ),
145
+ )
146
+ ```
147
+
148
+ Tell the model when to use the tools, as above: models call them far more reliably with explicit guidance. `hindsight_reflect` runs an LLM on the Hindsight server, so it is the slowest; leave it out for snappier calls. Tool errors are raised as LiveKit `ToolError`, so the model hears what went wrong and can tell the caller.
149
+
150
+ ## Latency
151
+
152
+ Recall happens while a caller waits, so the defaults are tuned for voice:
153
+
154
+ - `recall_budget="low"`: Hindsight's [fast lookup](https://hindsight.vectorize.io/developer/api/recall#budget) setting.
155
+ - `recall_max_tokens=1024`: keeps the prompt small.
156
+ - `recall_timeout=3.0`: the longest a reply waits on memory before going ahead without it.
157
+
158
+ With preemptive generation turned on, most per-turn recall time overlaps with end-of-turn detection.
159
+
160
+ ## Configuration
161
+
162
+ ```python
163
+ HindsightMemory(
164
+ bank_id="user-123", # Required: memory bank to use
165
+ client=None, # Pre-built hindsight_client.Hindsight (optional)
166
+ hindsight_api_url="...", # Defaults to HINDSIGHT_API_URL, then Hindsight Cloud
167
+ api_key="hsk_...", # Defaults to HINDSIGHT_API_KEY
168
+ document_id=None, # Defaults to a random "livekit-<uuid>"
169
+ recall_budget="low", # "low", "mid", or "high"
170
+ recall_max_tokens=1024, # Max tokens of memories per recall
171
+ recall_timeout=3.0, # Seconds before carrying on without memories
172
+ recall_types=None, # e.g. ["world", "experience"]; None = all
173
+ tags=None, # Tags for retained content
174
+ recall_tags=None, # Tags to filter recall by
175
+ recall_tags_match="any", # "any", "all", "any_strict", "all_strict"
176
+ retain_context="voice conversation", # Source label that guides fact extraction
177
+ user_name="User", # Speaker labels in the stored transcript
178
+ agent_name="Assistant",
179
+ enable_session_recall=True, # Recall known facts when the agent starts
180
+ session_recall_query="Who is this user? ...",
181
+ enable_recall=True, # Recall before each reply (pipelines)
182
+ enable_retain=True, # Store the conversation
183
+ )
184
+ ```
185
+
186
+ ### Global configuration
187
+
188
+ ```python
189
+ from hindsight_livekit import configure
190
+
191
+ configure(
192
+ hindsight_api_url="https://api.hindsight.vectorize.io",
193
+ api_key="hsk_...",
194
+ recall_budget="low",
195
+ )
196
+
197
+ memory = HindsightMemory(bank_id="user-123")
198
+ ```
199
+
200
+ ### Using your own `Agent` or `llm_node`
201
+
202
+ `memory.attach(session)` works with any `Agent`. To add recall to an agent that already overrides `llm_node` or `on_enter`, call the memory directly instead of subclassing `HindsightAgent`:
203
+
204
+ ```python
205
+ from livekit.agents import Agent
206
+
207
+ class MyAgent(Agent):
208
+ def __init__(self, memory: HindsightMemory) -> None:
209
+ super().__init__(instructions="...")
210
+ self.memory = memory
211
+
212
+ async def on_enter(self):
213
+ if memories := await self.memory.recall_session_context():
214
+ await self.update_instructions(f"{self.instructions}\n\n{memories}")
215
+
216
+ async def llm_node(self, chat_ctx, tools, model_settings):
217
+ chat_ctx = await self.memory.inject_memories(chat_ctx)
218
+ async for chunk in Agent.default.llm_node(self, chat_ctx, tools, model_settings):
219
+ yield chunk
220
+ ```
221
+
222
+ ## Prerequisites
223
+
224
+ - `livekit-agents` 1.2 or later
225
+ - A running Hindsight instance, self-hosted or [Hindsight Cloud](https://ui.hindsight.vectorize.io)
226
+
227
+ ## Running Tests
228
+
229
+ ```bash
230
+ uv sync
231
+ uv run pytest tests -v
232
+ ```
233
+
234
+ The unit tests run a real LiveKit `AgentSession` with a scripted LLM and a mocked Hindsight client. The live tests in `tests/test_live_integration.py` run against a real Hindsight server when `HINDSIGHT_API_URL` is set, and also against real pipeline and realtime models through LiveKit Inference when LiveKit Cloud credentials are set:
235
+
236
+ ```bash
237
+ docker run --rm -p 8888:8888 -e HINDSIGHT_API_LLM_PROVIDER=none ghcr.io/vectorize-io/hindsight:latest
238
+ HINDSIGHT_API_URL=http://localhost:8888 uv run pytest tests -v
239
+
240
+ # optional: real models via LiveKit Inference
241
+ LIVEKIT_URL=wss://... LIVEKIT_API_KEY=... LIVEKIT_API_SECRET=... HINDSIGHT_API_URL=http://localhost:8888 uv run pytest tests -v
242
+ ```
@@ -0,0 +1,216 @@
1
+ # Hindsight LiveKit Integration
2
+
3
+ Persistent long-term memory for [LiveKit Agents](https://github.com/livekit/agents) voice AI via [Hindsight](https://vectorize.io/hindsight). Your agent knows what it learned in earlier calls, and stores each new conversation as it happens. Works with STT-LLM-TTS pipelines and realtime (speech-to-speech) models.
4
+
5
+ ## Quick Start
6
+
7
+ ```bash
8
+ pip install hindsight-livekit
9
+ # or the latest from GitHub:
10
+ pip install git+https://github.com/namandidthis/hindsight-livekit
11
+ ```
12
+
13
+ > ✨ **Recommended: [Hindsight Cloud](https://ui.hindsight.vectorize.io)** — free tier, no self-hosting required. Sign up and grab an API key in under a minute.
14
+
15
+ ```python
16
+ from livekit.agents import AgentServer, AgentSession, JobContext, inference
17
+ from hindsight_livekit import HindsightAgent, HindsightMemory
18
+
19
+ server = AgentServer()
20
+
21
+
22
+ @server.rtc_session()
23
+ async def entrypoint(ctx: JobContext):
24
+ session = AgentSession(stt=..., tts=...)
25
+
26
+ memory = HindsightMemory(
27
+ bank_id="user-123", # one memory bank per end user
28
+ document_id=ctx.room.name, # one transcript per call
29
+ hindsight_api_url="https://api.hindsight.vectorize.io",
30
+ api_key="hsk_...", # or set HINDSIGHT_API_KEY
31
+ )
32
+ memory.attach(session) # retain the conversation
33
+ ctx.add_shutdown_callback(memory.aclose) # store the end of the call on hang-up
34
+
35
+ await session.start(
36
+ agent=HindsightAgent(
37
+ memory=memory,
38
+ llm=inference.LLM(model="google/gemma-4-31b-it"),
39
+ instructions="You are a friendly voice assistant.",
40
+ ),
41
+ room=ctx.room,
42
+ )
43
+ await ctx.connect()
44
+ ```
45
+
46
+ `HindsightAgent` is a regular LiveKit `Agent`: subclass it, add `@function_tool`s, override `on_enter` (call `await super().on_enter()` first), and so on.
47
+
48
+ Runnable agents built on [agent-starter-python](https://github.com/livekit-examples/agent-starter-python):
49
+
50
+ - [`examples/voice_agent.py`](./examples/voice_agent.py) — STT-LLM-TTS pipeline
51
+ - [`examples/realtime_agent.py`](./examples/realtime_agent.py) — realtime model with memory tools
52
+
53
+ ### Self-hosting (local development)
54
+
55
+ ```bash
56
+ docker run --rm -p 8888:8888 -e HINDSIGHT_API_LLM_API_KEY=$OPENAI_API_KEY ghcr.io/vectorize-io/hindsight:latest
57
+ ```
58
+
59
+ ```python
60
+ memory = HindsightMemory(bank_id="user-123", hindsight_api_url="http://localhost:8888")
61
+ ```
62
+
63
+ See the [Hindsight installation guide](https://hindsight.vectorize.io/developer/installation) for other options. LiveKit itself can be [self-hosted](https://docs.livekit.io/transport/self-hosting/local/) too; the integration works the same either way.
64
+
65
+ ## How It Works
66
+
67
+ ```
68
+ memory.attach(session)
69
+ └─ Session recall starts: "who is this user?" (overlaps with session.start)
70
+
71
+ Agent starts (on_enter)
72
+ └─ Known facts about the user are added to the agent's instructions
73
+
74
+ Before each LLM reply (llm_node) — STT-LLM-TTS pipelines only
75
+ └─ Recall memories relevant to the user's latest message
76
+ └─ Insert them before that message, in a copy of the chat context
77
+
78
+ As the conversation happens (conversation_item_added)
79
+ └─ Append "User (time): …" / "Assistant (time): …" lines to the call's Hindsight document
80
+ ```
81
+
82
+ | | STT-LLM-TTS pipeline | Realtime model |
83
+ |---|---|---|
84
+ | Recall when the agent starts | ✅ | ✅ |
85
+ | Recall before each reply | ✅ | — (no step before a realtime reply; use the tools) |
86
+ | Memory tools (`create_hindsight_tools`) | ✅ | ✅ |
87
+ | Retain the conversation | ✅ | ✅ (needs the model's input audio transcription for the user's side) |
88
+
89
+ **Session recall** runs once per call. `attach()` starts it, so it overlaps with LiveKit connecting the room and the model, and `HindsightAgent.on_enter` adds the result to the instructions. LiveKit runs `on_enter` in the background, so with a pipeline the first `llm_node` call also waits for the lookup and uses the instructions with memories, even if the user speaks first. A realtime model has no such step: a reply that starts before the lookup finishes (bounded by `recall_timeout`) won't include those memories, which is rare in practice since the lookup starts before the call connects.
90
+
91
+ **Per-turn recall** runs in [`llm_node`](https://docs.livekit.io/agents/build/nodes/), the step every LLM call in a pipeline goes through. That means:
92
+
93
+ - It works for spoken turns, chat text input and `session.generate_reply(user_input=...)`. The `on_user_turn_completed` hook only runs for spoken turns.
94
+ - It works with [preemptive generation](https://docs.livekit.io/agents/build/audio/#preemptive-generation): recall starts while LiveKit is still confirming the end of the user's turn, and the preemptive reply is kept. Editing the context in `on_user_turn_completed` would make LiveKit discard it.
95
+ - Memories are only added to a copy of the context for that call, so they never pile up in the session history. When the LLM calls a tool, the follow-up call reuses the same recall result.
96
+
97
+ As in LiveKit's [RAG example](https://docs.livekit.io/agents/build/external-data/), per-turn memories are added as an `assistant` message just before the user's message, wrapped in `<hindsight_memories>` tags with a note telling the model not to read them out.
98
+
99
+ **Retain** follows Hindsight's [guidance for conversations](https://hindsight.vectorize.io/developer/api/retain#retaining-a-conversation): each call is one document (`document_id`), written as `Name (timestamp): text` lines, grown with [`update_mode="append"`](https://hindsight.vectorize.io/developer/api/retain#update_mode) so Hindsight only extracts facts from the new part. Lines are sent once both the user and the agent have spoken, sorted by time, because realtime models can deliver the user's transcript after the reply it prompted. Appends are sent one at a time, in order, in the background, so they never delay a reply. `aclose()` sends whatever is left when the call ends.
100
+
101
+ **Failures never break the call.** If recall fails or takes longer than `recall_timeout`, the agent carries on without memories. If retain fails, the error is logged.
102
+
103
+ ## Memory Tools
104
+
105
+ `create_hindsight_tools()` gives the model `hindsight_retain`, `hindsight_recall` and `hindsight_reflect` tools, like the other Hindsight framework integrations. They are the way for a realtime model to look things up mid-call, and work with pipelines too:
106
+
107
+ ```python
108
+ from hindsight_livekit import HindsightAgent, create_hindsight_tools
109
+
110
+ agent = HindsightAgent(
111
+ memory=memory,
112
+ llm=inference.RealtimeModel(model="openai/gpt-realtime-mini"),
113
+ tools=create_hindsight_tools(bank_id=memory.bank_id, client=memory.client, include_reflect=False),
114
+ instructions=(
115
+ "You have long-term memory of past calls. When the user asks about themselves, "
116
+ "their past, or their preferences, call hindsight_recall first. When they share "
117
+ "something worth remembering, call hindsight_retain."
118
+ ),
119
+ )
120
+ ```
121
+
122
+ Tell the model when to use the tools, as above: models call them far more reliably with explicit guidance. `hindsight_reflect` runs an LLM on the Hindsight server, so it is the slowest; leave it out for snappier calls. Tool errors are raised as LiveKit `ToolError`, so the model hears what went wrong and can tell the caller.
123
+
124
+ ## Latency
125
+
126
+ Recall happens while a caller waits, so the defaults are tuned for voice:
127
+
128
+ - `recall_budget="low"`: Hindsight's [fast lookup](https://hindsight.vectorize.io/developer/api/recall#budget) setting.
129
+ - `recall_max_tokens=1024`: keeps the prompt small.
130
+ - `recall_timeout=3.0`: the longest a reply waits on memory before going ahead without it.
131
+
132
+ With preemptive generation turned on, most per-turn recall time overlaps with end-of-turn detection.
133
+
134
+ ## Configuration
135
+
136
+ ```python
137
+ HindsightMemory(
138
+ bank_id="user-123", # Required: memory bank to use
139
+ client=None, # Pre-built hindsight_client.Hindsight (optional)
140
+ hindsight_api_url="...", # Defaults to HINDSIGHT_API_URL, then Hindsight Cloud
141
+ api_key="hsk_...", # Defaults to HINDSIGHT_API_KEY
142
+ document_id=None, # Defaults to a random "livekit-<uuid>"
143
+ recall_budget="low", # "low", "mid", or "high"
144
+ recall_max_tokens=1024, # Max tokens of memories per recall
145
+ recall_timeout=3.0, # Seconds before carrying on without memories
146
+ recall_types=None, # e.g. ["world", "experience"]; None = all
147
+ tags=None, # Tags for retained content
148
+ recall_tags=None, # Tags to filter recall by
149
+ recall_tags_match="any", # "any", "all", "any_strict", "all_strict"
150
+ retain_context="voice conversation", # Source label that guides fact extraction
151
+ user_name="User", # Speaker labels in the stored transcript
152
+ agent_name="Assistant",
153
+ enable_session_recall=True, # Recall known facts when the agent starts
154
+ session_recall_query="Who is this user? ...",
155
+ enable_recall=True, # Recall before each reply (pipelines)
156
+ enable_retain=True, # Store the conversation
157
+ )
158
+ ```
159
+
160
+ ### Global configuration
161
+
162
+ ```python
163
+ from hindsight_livekit import configure
164
+
165
+ configure(
166
+ hindsight_api_url="https://api.hindsight.vectorize.io",
167
+ api_key="hsk_...",
168
+ recall_budget="low",
169
+ )
170
+
171
+ memory = HindsightMemory(bank_id="user-123")
172
+ ```
173
+
174
+ ### Using your own `Agent` or `llm_node`
175
+
176
+ `memory.attach(session)` works with any `Agent`. To add recall to an agent that already overrides `llm_node` or `on_enter`, call the memory directly instead of subclassing `HindsightAgent`:
177
+
178
+ ```python
179
+ from livekit.agents import Agent
180
+
181
+ class MyAgent(Agent):
182
+ def __init__(self, memory: HindsightMemory) -> None:
183
+ super().__init__(instructions="...")
184
+ self.memory = memory
185
+
186
+ async def on_enter(self):
187
+ if memories := await self.memory.recall_session_context():
188
+ await self.update_instructions(f"{self.instructions}\n\n{memories}")
189
+
190
+ async def llm_node(self, chat_ctx, tools, model_settings):
191
+ chat_ctx = await self.memory.inject_memories(chat_ctx)
192
+ async for chunk in Agent.default.llm_node(self, chat_ctx, tools, model_settings):
193
+ yield chunk
194
+ ```
195
+
196
+ ## Prerequisites
197
+
198
+ - `livekit-agents` 1.2 or later
199
+ - A running Hindsight instance, self-hosted or [Hindsight Cloud](https://ui.hindsight.vectorize.io)
200
+
201
+ ## Running Tests
202
+
203
+ ```bash
204
+ uv sync
205
+ uv run pytest tests -v
206
+ ```
207
+
208
+ The unit tests run a real LiveKit `AgentSession` with a scripted LLM and a mocked Hindsight client. The live tests in `tests/test_live_integration.py` run against a real Hindsight server when `HINDSIGHT_API_URL` is set, and also against real pipeline and realtime models through LiveKit Inference when LiveKit Cloud credentials are set:
209
+
210
+ ```bash
211
+ docker run --rm -p 8888:8888 -e HINDSIGHT_API_LLM_PROVIDER=none ghcr.io/vectorize-io/hindsight:latest
212
+ HINDSIGHT_API_URL=http://localhost:8888 uv run pytest tests -v
213
+
214
+ # optional: real models via LiveKit Inference
215
+ LIVEKIT_URL=wss://... LIVEKIT_API_KEY=... LIVEKIT_API_SECRET=... HINDSIGHT_API_URL=http://localhost:8888 uv run pytest tests -v
216
+ ```
@@ -0,0 +1,74 @@
1
+ """Realtime (speech-to-speech) voice agent with Hindsight memory.
2
+
3
+ Realtime models answer straight from audio, so there is no step before each reply
4
+ where memories can be added. Instead this agent:
5
+
6
+ * recalls what is known about the user when the call starts (HindsightAgent adds it
7
+ to the instructions), and
8
+ * gets ``hindsight_recall`` / ``hindsight_retain`` tools to look things up or save
9
+ them mid-call.
10
+
11
+ The conversation is retained as it happens, as with the STT-LLM-TTS example.
12
+
13
+ Setup is the same as voice_agent.py::
14
+
15
+ lk agent console examples/realtime_agent.py
16
+ lk agent dev examples/realtime_agent.py
17
+ lk token create --identity me --room hindsight-demo --agent hindsight-realtime-agent --join --open meet
18
+ """
19
+
20
+ import logging
21
+ import os
22
+
23
+ from dotenv import load_dotenv
24
+ from livekit.agents import AgentServer, AgentSession, JobContext, cli, inference
25
+
26
+ from hindsight_livekit import HindsightAgent, HindsightMemory, create_hindsight_tools
27
+
28
+ logger = logging.getLogger("agent")
29
+
30
+ load_dotenv(".env.local")
31
+
32
+
33
+ class Assistant(HindsightAgent):
34
+ def __init__(self, memory: HindsightMemory) -> None:
35
+ super().__init__(
36
+ memory=memory,
37
+ # Any realtime model works: e.g. "google/gemini-live" or "xai/grok-voice".
38
+ llm=inference.RealtimeModel(model="openai/gpt-realtime-mini"),
39
+ # The reflect tool runs an LLM on the Hindsight server; leave it out for snappier calls.
40
+ tools=create_hindsight_tools(bank_id=memory.bank_id, client=memory.client, include_reflect=False),
41
+ instructions=(
42
+ "You are a friendly voice assistant with long-term memory of past calls. "
43
+ "When the user asks about themselves, their past, or their preferences, call "
44
+ "hindsight_recall first. When they share something worth remembering, call "
45
+ "hindsight_retain. Keep replies to one to three short sentences."
46
+ ),
47
+ )
48
+
49
+ async def on_enter(self) -> None:
50
+ # Recall what's known about the user first, so the greeting can use it.
51
+ await super().on_enter()
52
+ self.session.generate_reply(instructions="Greet the user briefly, by name if you know it.")
53
+
54
+
55
+ server = AgentServer()
56
+
57
+
58
+ @server.rtc_session(agent_name="hindsight-realtime-agent")
59
+ async def entrypoint(ctx: JobContext) -> None:
60
+ bank_id = os.environ.get("HINDSIGHT_BANK_ID", "livekit-demo-user")
61
+ logger.info(f"using Hindsight bank {bank_id}")
62
+
63
+ session = AgentSession()
64
+
65
+ memory = HindsightMemory(bank_id=bank_id, document_id=ctx.room.name)
66
+ memory.attach(session)
67
+ ctx.add_shutdown_callback(memory.aclose)
68
+
69
+ await session.start(agent=Assistant(memory), room=ctx.room)
70
+ await ctx.connect()
71
+
72
+
73
+ if __name__ == "__main__":
74
+ cli.run_app(server)