crewai-pastdotdev 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,7 @@
1
+ __pycache__/
2
+ *.egg-info/
3
+ .venv/
4
+ .pytest_cache/
5
+ dist/
6
+ build/
7
+ .crewai/
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 past.dev
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,225 @@
1
+ Metadata-Version: 2.5
2
+ Name: crewai-pastdotdev
3
+ Version: 0.1.0
4
+ Summary: past dev for CrewAI: Ingest and Recall as crew memory and as agent tools.
5
+ Project-URL: Homepage, https://past.dev
6
+ Project-URL: Documentation, https://past.dev/docs/memory-api/quickstart
7
+ Project-URL: API reference, https://past.dev/docs/memory-api/api-reference
8
+ Project-URL: Source, https://github.com/pastdotdev/crewai-pastdotdev
9
+ Author-email: "past.dev" <louis@past.dev>
10
+ Maintainer-email: "past.dev" <louis@past.dev>
11
+ License-Expression: MIT
12
+ License-File: LICENSE
13
+ Keywords: agents,crewai,llm,memory,past dev,recall
14
+ Classifier: Development Status :: 4 - Beta
15
+ Classifier: Intended Audience :: Developers
16
+ Classifier: License :: OSI Approved :: MIT License
17
+ Classifier: Programming Language :: Python :: 3
18
+ Classifier: Programming Language :: Python :: 3.10
19
+ Classifier: Programming Language :: Python :: 3.11
20
+ Classifier: Programming Language :: Python :: 3.12
21
+ Classifier: Programming Language :: Python :: 3.13
22
+ Classifier: Topic :: Scientific/Engineering :: Artificial Intelligence
23
+ Classifier: Typing :: Typed
24
+ Requires-Python: <3.14,>=3.10
25
+ Requires-Dist: crewai<2,>=1.14.6
26
+ Requires-Dist: httpx<1,>=0.27
27
+ Provides-Extra: test
28
+ Requires-Dist: pytest>=8; extra == 'test'
29
+ Description-Content-Type: text/markdown
30
+
31
+ # crewai-pastdotdev
32
+
33
+ past dev is a memory API for AI agents. This package connects it to
34
+ [CrewAI](https://www.crewai.com): your crews keep what they learn in past dev
35
+ and recall it in later runs, with the date and the source of every memory.
36
+
37
+ It gives you two ways to use past dev:
38
+
39
+ - **`PastDevMemory`**: crew memory. Pass it as `memory=` to a `Crew` or an
40
+ `Agent`. CrewAI recalls before each task and saves each task result. A save
41
+ is a past dev **Ingest** and a search is a past dev **Recall**.
42
+ - **`PastDevRecallTool`** and **`PastDevIngestTool`**: tools that an agent
43
+ calls when it decides to. The model gives only the query or the content.
44
+
45
+ [Website](https://past.dev) ·
46
+ [Quickstart](https://past.dev/docs/memory-api/quickstart) ·
47
+ [API reference](https://past.dev/docs/memory-api/api-reference)
48
+
49
+ ## Install
50
+
51
+ ```bash
52
+ pip install crewai-pastdotdev
53
+ ```
54
+
55
+ The package needs Python 3.10 to 3.13 and CrewAI 1.14.6 or later.
56
+
57
+ ## Set the API key
58
+
59
+ Make a project API key in the past dev console. It starts with `past_sk_`.
60
+
61
+ ```bash
62
+ export PAST_API_KEY="past_sk_..."
63
+ ```
64
+
65
+ If you run past dev in your own cloud, also set the API host. The default is
66
+ `https://api.past.dev`.
67
+
68
+ ```bash
69
+ export PAST_API_HOST="https://past.example.com"
70
+ ```
71
+
72
+ You can also give `api_key=` and `api_host=` to each class.
73
+
74
+ ## The identity
75
+
76
+ Every memory in past dev belongs to an **identity**: the id of the person or
77
+ account the memory is about, for example the user id in your application.
78
+ Recall requires an identity. Ingest makes the identity the first time it sees
79
+ it.
80
+
81
+ **Your code sets the identity, never the model.** Take it from the user
82
+ record or the trigger of the run, and give it to the constructor. The tools
83
+ have no identity argument, so an agent cannot read or write the memory of
84
+ another person. Make one memory or one tool for each user.
85
+
86
+ ## Use past dev as crew memory
87
+
88
+ ```python
89
+ from crewai import Agent, Crew, Task
90
+ from crewai_pastdotdev import PastDevMemory
91
+
92
+ memory = PastDevMemory(identity=current_user.id)
93
+
94
+ writer = Agent(
95
+ role="Account manager",
96
+ goal="Write the weekly report the way the customer wants it",
97
+ backstory="You know the customer well.",
98
+ )
99
+ report = Task(
100
+ description="Write this week's report for the customer.",
101
+ expected_output="A report in the format the customer prefers.",
102
+ agent=writer,
103
+ )
104
+
105
+ crew = Crew(agents=[writer], tasks=[report], memory=memory)
106
+ crew.kickoff()
107
+ ```
108
+
109
+ What happens in a run:
110
+
111
+ 1. Before each task, CrewAI recalls with the task description. The recalled
112
+ memories go into the agent's prompt.
113
+ 2. After each task, CrewAI saves the task and its result. `PastDevMemory`
114
+ ingests them as one data point. past dev extracts the memories on its own
115
+ side, so no extra LLM call runs in your crew.
116
+ 3. CrewAI also gives the agents its own "Search memory" and "Save to memory"
117
+ tools. They go through past dev too.
118
+
119
+ You can also call the memory directly:
120
+
121
+ ```python
122
+ record = memory.remember("The customer wants the report on Mondays.")
123
+ for match in memory.recall("When does the customer want the report?"):
124
+ print(match.record.content, match.record.metadata["occurred_at"])
125
+ memory.forget(record_ids=[record.id]) # Delete data point
126
+ ```
127
+
128
+ ### Settings
129
+
130
+ | Argument | Default | What it does |
131
+ | :- | :- | :- |
132
+ | `identity` | required | The identity for every Ingest and Recall. |
133
+ | `api_key` | `PAST_API_KEY` | The project API key. |
134
+ | `api_host` | `PAST_API_HOST`, else `https://api.past.dev` | The API host. |
135
+ | `audience` | none | An audience slug for ingested memories. Without it, a memory is visible to the whole project. |
136
+ | `label` | none | A short title stored with each data point. |
137
+ | `recall_level` | `low` | The evidence budget of a recall: `low`, `medium`, `high` or `extra-high`. |
138
+ | `recall_max_tokens` | none | An exact evidence budget in tokens, instead of `recall_level`. |
139
+ | `read_only` | `False` | Recall only. Nothing is ingested. |
140
+
141
+ ### How CrewAI concepts map to past dev
142
+
143
+ | CrewAI | past dev |
144
+ | :- | :- |
145
+ | `remember()`, `remember_many()` | Ingest (`POST /api/v1/ingest`). The source id is stable for the same identity and content, so a repeated save is free. |
146
+ | `recall()` | Recall (`POST /api/v1/recall`). `limit` is the number of memories, at most 200. |
147
+ | `forget(record_ids=[...])` | Delete data point (`DELETE /api/v1/data-points/{sourceId}`). |
148
+ | `MemoryMatch.score` | `1 / rank`. past dev ranks the results; it does not return a score. |
149
+ | Scopes, categories, agent role | Kept in the data point's metadata (`crewai_scope`, `crewai_categories`, `crewai_agent_role`). Recall does not filter on them. |
150
+ | `tree()`, `info()`, `list_records()`, `update()`, `reset()` | Not available: past dev keeps memory on its servers. Use the past dev console to browse. To change a fact, remember the new one: past dev marks the earlier statement as superseded. |
151
+
152
+ ## Use past dev as agent tools
153
+
154
+ ```python
155
+ from crewai import Agent
156
+ from crewai_pastdotdev import PastDevIngestTool, PastDevRecallTool
157
+
158
+ agent = Agent(
159
+ role="Support agent",
160
+ goal="Help the customer with what you know about them",
161
+ backstory="You remember every earlier conversation.",
162
+ tools=[
163
+ PastDevRecallTool(identity=current_user.id),
164
+ PastDevIngestTool(identity=current_user.id),
165
+ ],
166
+ )
167
+ ```
168
+
169
+ | Tool | The model gives | Other settings (in the constructor) |
170
+ | :- | :- | :- |
171
+ | `PastDevRecallTool` ("past dev Recall") | `query` | `identity`, `limit` (10), `level`, `api_key`, `api_host` |
172
+ | `PastDevIngestTool` ("past dev Ingest") | `content` | `identity`, `audience`, `label`, `metadata`, `api_key`, `api_host` |
173
+
174
+ The recall tool returns the memories as text: rank, date, content, and the
175
+ quotations from their sources. If a memory was superseded by a later
176
+ statement, the text says so.
177
+
178
+ ## Credits and errors
179
+
180
+ Every Ingest and every Recall spends credits on your past dev plan. The
181
+ connection check (`PastDevClient().check_connection()`, which reads the
182
+ project's audiences) and Delete data point are free.
183
+
184
+ An error from the API raises `PastDevError` with the API's `code` (for
185
+ example `out-of-credits`), the HTTP `status`, the message and the
186
+ `request_id`. Give the request id to past dev support when you ask about a
187
+ call. The tools return the error to the agent as text, and tell it not to
188
+ retry when a retry cannot help.
189
+
190
+ The client sends a request again only when the API answers 429 for the rate
191
+ limit, after the wait the API asks for (two times at most). It never retries
192
+ `402 out-of-credits`, a monthly cap, or a `400`.
193
+
194
+ ## The HTTP client
195
+
196
+ `PastDevClient` is a small client for the calls this package uses. You can
197
+ use it on its own:
198
+
199
+ ```python
200
+ from crewai_pastdotdev import PastDevClient
201
+
202
+ with PastDevClient() as past:
203
+ past.check_connection()
204
+ past.ingest("The customer moved to Lyon.", identity="user-42", id="crm-note-17")
205
+ page = past.recall("Where does the customer live?", identity="user-42", limit=5)
206
+ past.delete_data_point("crm-note-17")
207
+ audiences = past.list_audiences()
208
+ identities = list(past.list_identities())
209
+ ```
210
+
211
+ ## Development
212
+
213
+ ```bash
214
+ python -m venv .venv && . .venv/bin/activate
215
+ pip install -e ".[test]"
216
+ pytest
217
+ ```
218
+
219
+ The tests use `httpx.MockTransport` and a scripted LLM, so they need no key
220
+ and no network. One live test runs a real Ingest, Recall and Delete data
221
+ point when `PAST_API_KEY` is set. It spends a few credits.
222
+
223
+ ## License
224
+
225
+ MIT. See [LICENSE](LICENSE).
@@ -0,0 +1,195 @@
1
+ # crewai-pastdotdev
2
+
3
+ past dev is a memory API for AI agents. This package connects it to
4
+ [CrewAI](https://www.crewai.com): your crews keep what they learn in past dev
5
+ and recall it in later runs, with the date and the source of every memory.
6
+
7
+ It gives you two ways to use past dev:
8
+
9
+ - **`PastDevMemory`**: crew memory. Pass it as `memory=` to a `Crew` or an
10
+ `Agent`. CrewAI recalls before each task and saves each task result. A save
11
+ is a past dev **Ingest** and a search is a past dev **Recall**.
12
+ - **`PastDevRecallTool`** and **`PastDevIngestTool`**: tools that an agent
13
+ calls when it decides to. The model gives only the query or the content.
14
+
15
+ [Website](https://past.dev) ·
16
+ [Quickstart](https://past.dev/docs/memory-api/quickstart) ·
17
+ [API reference](https://past.dev/docs/memory-api/api-reference)
18
+
19
+ ## Install
20
+
21
+ ```bash
22
+ pip install crewai-pastdotdev
23
+ ```
24
+
25
+ The package needs Python 3.10 to 3.13 and CrewAI 1.14.6 or later.
26
+
27
+ ## Set the API key
28
+
29
+ Make a project API key in the past dev console. It starts with `past_sk_`.
30
+
31
+ ```bash
32
+ export PAST_API_KEY="past_sk_..."
33
+ ```
34
+
35
+ If you run past dev in your own cloud, also set the API host. The default is
36
+ `https://api.past.dev`.
37
+
38
+ ```bash
39
+ export PAST_API_HOST="https://past.example.com"
40
+ ```
41
+
42
+ You can also give `api_key=` and `api_host=` to each class.
43
+
44
+ ## The identity
45
+
46
+ Every memory in past dev belongs to an **identity**: the id of the person or
47
+ account the memory is about, for example the user id in your application.
48
+ Recall requires an identity. Ingest makes the identity the first time it sees
49
+ it.
50
+
51
+ **Your code sets the identity, never the model.** Take it from the user
52
+ record or the trigger of the run, and give it to the constructor. The tools
53
+ have no identity argument, so an agent cannot read or write the memory of
54
+ another person. Make one memory or one tool for each user.
55
+
56
+ ## Use past dev as crew memory
57
+
58
+ ```python
59
+ from crewai import Agent, Crew, Task
60
+ from crewai_pastdotdev import PastDevMemory
61
+
62
+ memory = PastDevMemory(identity=current_user.id)
63
+
64
+ writer = Agent(
65
+ role="Account manager",
66
+ goal="Write the weekly report the way the customer wants it",
67
+ backstory="You know the customer well.",
68
+ )
69
+ report = Task(
70
+ description="Write this week's report for the customer.",
71
+ expected_output="A report in the format the customer prefers.",
72
+ agent=writer,
73
+ )
74
+
75
+ crew = Crew(agents=[writer], tasks=[report], memory=memory)
76
+ crew.kickoff()
77
+ ```
78
+
79
+ What happens in a run:
80
+
81
+ 1. Before each task, CrewAI recalls with the task description. The recalled
82
+ memories go into the agent's prompt.
83
+ 2. After each task, CrewAI saves the task and its result. `PastDevMemory`
84
+ ingests them as one data point. past dev extracts the memories on its own
85
+ side, so no extra LLM call runs in your crew.
86
+ 3. CrewAI also gives the agents its own "Search memory" and "Save to memory"
87
+ tools. They go through past dev too.
88
+
89
+ You can also call the memory directly:
90
+
91
+ ```python
92
+ record = memory.remember("The customer wants the report on Mondays.")
93
+ for match in memory.recall("When does the customer want the report?"):
94
+ print(match.record.content, match.record.metadata["occurred_at"])
95
+ memory.forget(record_ids=[record.id]) # Delete data point
96
+ ```
97
+
98
+ ### Settings
99
+
100
+ | Argument | Default | What it does |
101
+ | :- | :- | :- |
102
+ | `identity` | required | The identity for every Ingest and Recall. |
103
+ | `api_key` | `PAST_API_KEY` | The project API key. |
104
+ | `api_host` | `PAST_API_HOST`, else `https://api.past.dev` | The API host. |
105
+ | `audience` | none | An audience slug for ingested memories. Without it, a memory is visible to the whole project. |
106
+ | `label` | none | A short title stored with each data point. |
107
+ | `recall_level` | `low` | The evidence budget of a recall: `low`, `medium`, `high` or `extra-high`. |
108
+ | `recall_max_tokens` | none | An exact evidence budget in tokens, instead of `recall_level`. |
109
+ | `read_only` | `False` | Recall only. Nothing is ingested. |
110
+
111
+ ### How CrewAI concepts map to past dev
112
+
113
+ | CrewAI | past dev |
114
+ | :- | :- |
115
+ | `remember()`, `remember_many()` | Ingest (`POST /api/v1/ingest`). The source id is stable for the same identity and content, so a repeated save is free. |
116
+ | `recall()` | Recall (`POST /api/v1/recall`). `limit` is the number of memories, at most 200. |
117
+ | `forget(record_ids=[...])` | Delete data point (`DELETE /api/v1/data-points/{sourceId}`). |
118
+ | `MemoryMatch.score` | `1 / rank`. past dev ranks the results; it does not return a score. |
119
+ | Scopes, categories, agent role | Kept in the data point's metadata (`crewai_scope`, `crewai_categories`, `crewai_agent_role`). Recall does not filter on them. |
120
+ | `tree()`, `info()`, `list_records()`, `update()`, `reset()` | Not available: past dev keeps memory on its servers. Use the past dev console to browse. To change a fact, remember the new one: past dev marks the earlier statement as superseded. |
121
+
122
+ ## Use past dev as agent tools
123
+
124
+ ```python
125
+ from crewai import Agent
126
+ from crewai_pastdotdev import PastDevIngestTool, PastDevRecallTool
127
+
128
+ agent = Agent(
129
+ role="Support agent",
130
+ goal="Help the customer with what you know about them",
131
+ backstory="You remember every earlier conversation.",
132
+ tools=[
133
+ PastDevRecallTool(identity=current_user.id),
134
+ PastDevIngestTool(identity=current_user.id),
135
+ ],
136
+ )
137
+ ```
138
+
139
+ | Tool | The model gives | Other settings (in the constructor) |
140
+ | :- | :- | :- |
141
+ | `PastDevRecallTool` ("past dev Recall") | `query` | `identity`, `limit` (10), `level`, `api_key`, `api_host` |
142
+ | `PastDevIngestTool` ("past dev Ingest") | `content` | `identity`, `audience`, `label`, `metadata`, `api_key`, `api_host` |
143
+
144
+ The recall tool returns the memories as text: rank, date, content, and the
145
+ quotations from their sources. If a memory was superseded by a later
146
+ statement, the text says so.
147
+
148
+ ## Credits and errors
149
+
150
+ Every Ingest and every Recall spends credits on your past dev plan. The
151
+ connection check (`PastDevClient().check_connection()`, which reads the
152
+ project's audiences) and Delete data point are free.
153
+
154
+ An error from the API raises `PastDevError` with the API's `code` (for
155
+ example `out-of-credits`), the HTTP `status`, the message and the
156
+ `request_id`. Give the request id to past dev support when you ask about a
157
+ call. The tools return the error to the agent as text, and tell it not to
158
+ retry when a retry cannot help.
159
+
160
+ The client sends a request again only when the API answers 429 for the rate
161
+ limit, after the wait the API asks for (two times at most). It never retries
162
+ `402 out-of-credits`, a monthly cap, or a `400`.
163
+
164
+ ## The HTTP client
165
+
166
+ `PastDevClient` is a small client for the calls this package uses. You can
167
+ use it on its own:
168
+
169
+ ```python
170
+ from crewai_pastdotdev import PastDevClient
171
+
172
+ with PastDevClient() as past:
173
+ past.check_connection()
174
+ past.ingest("The customer moved to Lyon.", identity="user-42", id="crm-note-17")
175
+ page = past.recall("Where does the customer live?", identity="user-42", limit=5)
176
+ past.delete_data_point("crm-note-17")
177
+ audiences = past.list_audiences()
178
+ identities = list(past.list_identities())
179
+ ```
180
+
181
+ ## Development
182
+
183
+ ```bash
184
+ python -m venv .venv && . .venv/bin/activate
185
+ pip install -e ".[test]"
186
+ pytest
187
+ ```
188
+
189
+ The tests use `httpx.MockTransport` and a scripted LLM, so they need no key
190
+ and no network. One live test runs a real Ingest, Recall and Delete data
191
+ point when `PAST_API_KEY` is set. It spends a few credits.
192
+
193
+ ## License
194
+
195
+ MIT. See [LICENSE](LICENSE).
@@ -0,0 +1,54 @@
1
+ [build-system]
2
+ requires = ["hatchling>=1.24"]
3
+ build-backend = "hatchling.build"
4
+
5
+ [project]
6
+ name = "crewai-pastdotdev"
7
+ dynamic = ["version"]
8
+ description = "past dev for CrewAI: Ingest and Recall as crew memory and as agent tools."
9
+ readme = "README.md"
10
+ license = "MIT"
11
+ license-files = ["LICENSE"]
12
+ authors = [{ name = "past.dev", email = "louis@past.dev" }]
13
+ maintainers = [{ name = "past.dev", email = "louis@past.dev" }]
14
+ requires-python = ">=3.10,<3.14"
15
+ dependencies = [
16
+ "crewai>=1.14.6,<2",
17
+ "httpx>=0.27,<1",
18
+ ]
19
+ keywords = ["crewai", "memory", "agents", "llm", "past dev", "recall"]
20
+ classifiers = [
21
+ "Development Status :: 4 - Beta",
22
+ "Intended Audience :: Developers",
23
+ "License :: OSI Approved :: MIT License",
24
+ "Programming Language :: Python :: 3",
25
+ "Programming Language :: Python :: 3.10",
26
+ "Programming Language :: Python :: 3.11",
27
+ "Programming Language :: Python :: 3.12",
28
+ "Programming Language :: Python :: 3.13",
29
+ "Topic :: Scientific/Engineering :: Artificial Intelligence",
30
+ "Typing :: Typed",
31
+ ]
32
+
33
+ [project.optional-dependencies]
34
+ test = ["pytest>=8"]
35
+
36
+ [project.urls]
37
+ Homepage = "https://past.dev"
38
+ Documentation = "https://past.dev/docs/memory-api/quickstart"
39
+ "API reference" = "https://past.dev/docs/memory-api/api-reference"
40
+ Source = "https://github.com/pastdotdev/crewai-pastdotdev"
41
+
42
+ [tool.hatch.version]
43
+ path = "src/crewai_pastdotdev/_version.py"
44
+
45
+ [tool.hatch.build.targets.wheel]
46
+ packages = ["src/crewai_pastdotdev"]
47
+
48
+ [tool.hatch.build.targets.sdist]
49
+ include = ["src", "tests", "README.md", "LICENSE", "pyproject.toml"]
50
+ exclude = ["DOCS_PR.md", "PUBLISHING.md"]
51
+
52
+ [tool.pytest.ini_options]
53
+ testpaths = ["tests"]
54
+ markers = ["live: calls the real past dev API (skipped unless PAST_API_KEY is set)"]
@@ -0,0 +1,23 @@
1
+ """past dev for CrewAI: Ingest and Recall as crew memory and as agent tools."""
2
+
3
+ from crewai_pastdotdev._version import __version__
4
+ from crewai_pastdotdev.client import DEFAULT_API_HOST, PastDevClient, PastDevError
5
+ from crewai_pastdotdev.memory import PastDevMemory
6
+ from crewai_pastdotdev.tools import (
7
+ PastDevIngestInput,
8
+ PastDevIngestTool,
9
+ PastDevRecallInput,
10
+ PastDevRecallTool,
11
+ )
12
+
13
+ __all__ = [
14
+ "DEFAULT_API_HOST",
15
+ "PastDevClient",
16
+ "PastDevError",
17
+ "PastDevIngestInput",
18
+ "PastDevIngestTool",
19
+ "PastDevMemory",
20
+ "PastDevRecallInput",
21
+ "PastDevRecallTool",
22
+ "__version__",
23
+ ]
@@ -0,0 +1,85 @@
1
+ """Helpers shared by the memory and the tools."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import hashlib
6
+ import re
7
+ from datetime import datetime, timezone
8
+ from typing import Any
9
+
10
+ from pydantic import SecretStr
11
+
12
+ from crewai_pastdotdev.client import PastDevClient
13
+
14
+ MAX_IDENTITY_LENGTH = 200
15
+ RECALL_LIMIT_MAX = 200
16
+
17
+ _FRACTION = re.compile(r"(\.\d{6})\d+")
18
+
19
+
20
+ def check_identity(identity: str) -> str:
21
+ """Return the identity, or raise ``ValueError`` if past dev would refuse it."""
22
+ if not identity or not identity.strip():
23
+ raise ValueError("past dev needs an identity: the id of the person the memory is about.")
24
+ if len(identity) > MAX_IDENTITY_LENGTH:
25
+ raise ValueError(f"A past dev identity is at most {MAX_IDENTITY_LENGTH} characters.")
26
+ return identity
27
+
28
+
29
+ def make_client(
30
+ client: PastDevClient | None, api_key: SecretStr | None, api_host: str | None
31
+ ) -> PastDevClient:
32
+ if client is not None:
33
+ return client
34
+ return PastDevClient(
35
+ api_key=api_key.get_secret_value() if api_key is not None else None,
36
+ api_host=api_host,
37
+ )
38
+
39
+
40
+ def source_id_for(identity: str, content: str) -> str:
41
+ """A stable source id for one piece of content from one identity.
42
+
43
+ The same content from the same identity gets the same id, so a repeated
44
+ ingest is recognized as unchanged and costs nothing. Two identities that
45
+ send the same words keep two separate data points.
46
+ """
47
+ digest = hashlib.sha256(f"{identity}\x00{content}".encode()).hexdigest()
48
+ return f"crewai-{digest}"
49
+
50
+
51
+ def clamp_limit(limit: int) -> int:
52
+ return max(1, min(int(limit), RECALL_LIMIT_MAX))
53
+
54
+
55
+ def parse_datetime(value: str | None) -> datetime | None:
56
+ """Parse an API date-time (ISO 8601, ``Z`` suffix, up to 7 decimals)."""
57
+ if not value:
58
+ return None
59
+ text = _FRACTION.sub(r"\1", value.replace("Z", "+00:00"))
60
+ try:
61
+ parsed = datetime.fromisoformat(text)
62
+ except ValueError:
63
+ return None
64
+ return parsed if parsed.tzinfo else parsed.replace(tzinfo=timezone.utc)
65
+
66
+
67
+ def format_recall(response: dict[str, Any]) -> str:
68
+ """Render a recall response as text for an agent to read."""
69
+ results = response.get("results") or []
70
+ if not results:
71
+ return "past dev found no relevant memories."
72
+ lines = [f"past dev recalled {len(results)} memories, most relevant first:"]
73
+ for result in results:
74
+ date = (result.get("occurredAt") or "")[:10]
75
+ artifact = result.get("artifact") or {}
76
+ superseded = artifact.get("supersededAt")
77
+ head = f"{result.get('rank', '?')}. [{date}] {result.get('content', '').strip()}"
78
+ if superseded:
79
+ head += f" (superseded on {superseded[:10]})"
80
+ lines.append(head)
81
+ for source in result.get("sources") or []:
82
+ name = source.get("label") or source.get("sourceId")
83
+ for excerpt in source.get("excerpts") or []:
84
+ lines.append(f' - "{excerpt}" ({name}, {(source.get("occurredAt") or "")[:10]})')
85
+ return "\n".join(lines)
@@ -0,0 +1 @@
1
+ __version__ = "0.1.0"