iranti 0.1.0

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.
Files changed (255) hide show
  1. package/.env.example +63 -0
  2. package/LICENSE +12 -0
  3. package/README.md +520 -0
  4. package/bin/iranti.js +24 -0
  5. package/dist/scripts/api-key-create.js +57 -0
  6. package/dist/scripts/api-key-list.js +42 -0
  7. package/dist/scripts/api-key-revoke.js +42 -0
  8. package/dist/scripts/iranti-cli.js +387 -0
  9. package/dist/scripts/seed-codebase.js +138 -0
  10. package/dist/scripts/seed.js +121 -0
  11. package/dist/scripts/setup.js +86 -0
  12. package/dist/src/api/archivistScheduler.d.ts +8 -0
  13. package/dist/src/api/archivistScheduler.d.ts.map +1 -0
  14. package/dist/src/api/archivistScheduler.js +100 -0
  15. package/dist/src/api/archivistScheduler.js.map +1 -0
  16. package/dist/src/api/diag.d.ts +2 -0
  17. package/dist/src/api/diag.d.ts.map +1 -0
  18. package/dist/src/api/diag.js +54 -0
  19. package/dist/src/api/diag.js.map +1 -0
  20. package/dist/src/api/middleware/auth.d.ts +3 -0
  21. package/dist/src/api/middleware/auth.d.ts.map +1 -0
  22. package/dist/src/api/middleware/auth.js +36 -0
  23. package/dist/src/api/middleware/auth.js.map +1 -0
  24. package/dist/src/api/middleware/authorization.d.ts +4 -0
  25. package/dist/src/api/middleware/authorization.d.ts.map +1 -0
  26. package/dist/src/api/middleware/authorization.js +54 -0
  27. package/dist/src/api/middleware/authorization.js.map +1 -0
  28. package/dist/src/api/middleware/rateLimit.d.ts +23 -0
  29. package/dist/src/api/middleware/rateLimit.d.ts.map +1 -0
  30. package/dist/src/api/middleware/rateLimit.js +70 -0
  31. package/dist/src/api/middleware/rateLimit.js.map +1 -0
  32. package/dist/src/api/middleware/validation.d.ts +128 -0
  33. package/dist/src/api/middleware/validation.d.ts.map +1 -0
  34. package/dist/src/api/middleware/validation.js +137 -0
  35. package/dist/src/api/middleware/validation.js.map +1 -0
  36. package/dist/src/api/repro.d.ts +2 -0
  37. package/dist/src/api/repro.d.ts.map +1 -0
  38. package/dist/src/api/repro.js +25 -0
  39. package/dist/src/api/repro.js.map +1 -0
  40. package/dist/src/api/routes/agents.d.ts +4 -0
  41. package/dist/src/api/routes/agents.d.ts.map +1 -0
  42. package/dist/src/api/routes/agents.js +56 -0
  43. package/dist/src/api/routes/agents.js.map +1 -0
  44. package/dist/src/api/routes/batch.d.ts +2 -0
  45. package/dist/src/api/routes/batch.d.ts.map +1 -0
  46. package/dist/src/api/routes/batch.js +63 -0
  47. package/dist/src/api/routes/batch.js.map +1 -0
  48. package/dist/src/api/routes/dev.d.ts +2 -0
  49. package/dist/src/api/routes/dev.d.ts.map +1 -0
  50. package/dist/src/api/routes/dev.js +29 -0
  51. package/dist/src/api/routes/dev.js.map +1 -0
  52. package/dist/src/api/routes/knowledge.d.ts +4 -0
  53. package/dist/src/api/routes/knowledge.d.ts.map +1 -0
  54. package/dist/src/api/routes/knowledge.js +184 -0
  55. package/dist/src/api/routes/knowledge.js.map +1 -0
  56. package/dist/src/api/routes/memory.d.ts +4 -0
  57. package/dist/src/api/routes/memory.d.ts.map +1 -0
  58. package/dist/src/api/routes/memory.js +150 -0
  59. package/dist/src/api/routes/memory.js.map +1 -0
  60. package/dist/src/api/server.d.ts +2 -0
  61. package/dist/src/api/server.d.ts.map +1 -0
  62. package/dist/src/api/server.js +191 -0
  63. package/dist/src/api/server.js.map +1 -0
  64. package/dist/src/archivist/index.d.ts +10 -0
  65. package/dist/src/archivist/index.d.ts.map +1 -0
  66. package/dist/src/archivist/index.js +232 -0
  67. package/dist/src/archivist/index.js.map +1 -0
  68. package/dist/src/attendant/AttendantInstance.d.ts +96 -0
  69. package/dist/src/attendant/AttendantInstance.d.ts.map +1 -0
  70. package/dist/src/attendant/AttendantInstance.js +808 -0
  71. package/dist/src/attendant/AttendantInstance.js.map +1 -0
  72. package/dist/src/attendant/index.d.ts +12 -0
  73. package/dist/src/attendant/index.d.ts.map +1 -0
  74. package/dist/src/attendant/index.js +39 -0
  75. package/dist/src/attendant/index.js.map +1 -0
  76. package/dist/src/attendant/registry.d.ts +6 -0
  77. package/dist/src/attendant/registry.d.ts.map +1 -0
  78. package/dist/src/attendant/registry.js +27 -0
  79. package/dist/src/attendant/registry.js.map +1 -0
  80. package/dist/src/generated/prisma/browser.d.ts +35 -0
  81. package/dist/src/generated/prisma/browser.d.ts.map +1 -0
  82. package/dist/src/generated/prisma/browser.js +57 -0
  83. package/dist/src/generated/prisma/browser.js.map +1 -0
  84. package/dist/src/generated/prisma/client.d.ts +54 -0
  85. package/dist/src/generated/prisma/client.d.ts.map +1 -0
  86. package/dist/src/generated/prisma/client.js +71 -0
  87. package/dist/src/generated/prisma/client.js.map +1 -0
  88. package/dist/src/generated/prisma/commonInputTypes.d.ts +415 -0
  89. package/dist/src/generated/prisma/commonInputTypes.d.ts.map +1 -0
  90. package/dist/src/generated/prisma/commonInputTypes.js +12 -0
  91. package/dist/src/generated/prisma/commonInputTypes.js.map +1 -0
  92. package/dist/src/generated/prisma/enums.d.ts +2 -0
  93. package/dist/src/generated/prisma/enums.d.ts.map +1 -0
  94. package/dist/src/generated/prisma/enums.js +12 -0
  95. package/dist/src/generated/prisma/enums.js.map +1 -0
  96. package/dist/src/generated/prisma/internal/class.d.ts +186 -0
  97. package/dist/src/generated/prisma/internal/class.d.ts.map +1 -0
  98. package/dist/src/generated/prisma/internal/class.js +86 -0
  99. package/dist/src/generated/prisma/internal/class.js.map +1 -0
  100. package/dist/src/generated/prisma/internal/prismaNamespace.d.ts +1015 -0
  101. package/dist/src/generated/prisma/internal/prismaNamespace.d.ts.map +1 -0
  102. package/dist/src/generated/prisma/internal/prismaNamespace.js +220 -0
  103. package/dist/src/generated/prisma/internal/prismaNamespace.js.map +1 -0
  104. package/dist/src/generated/prisma/internal/prismaNamespaceBrowser.d.ts +152 -0
  105. package/dist/src/generated/prisma/internal/prismaNamespaceBrowser.d.ts.map +1 -0
  106. package/dist/src/generated/prisma/internal/prismaNamespaceBrowser.js +191 -0
  107. package/dist/src/generated/prisma/internal/prismaNamespaceBrowser.js.map +1 -0
  108. package/dist/src/generated/prisma/models/Archive.d.ts +1425 -0
  109. package/dist/src/generated/prisma/models/Archive.d.ts.map +1 -0
  110. package/dist/src/generated/prisma/models/Archive.js +3 -0
  111. package/dist/src/generated/prisma/models/Archive.js.map +1 -0
  112. package/dist/src/generated/prisma/models/Entity.d.ts +1129 -0
  113. package/dist/src/generated/prisma/models/Entity.d.ts.map +1 -0
  114. package/dist/src/generated/prisma/models/Entity.js +3 -0
  115. package/dist/src/generated/prisma/models/Entity.js.map +1 -0
  116. package/dist/src/generated/prisma/models/EntityAlias.d.ts +1347 -0
  117. package/dist/src/generated/prisma/models/EntityAlias.d.ts.map +1 -0
  118. package/dist/src/generated/prisma/models/EntityAlias.js +3 -0
  119. package/dist/src/generated/prisma/models/EntityAlias.js.map +1 -0
  120. package/dist/src/generated/prisma/models/EntityRelationship.d.ts +1143 -0
  121. package/dist/src/generated/prisma/models/EntityRelationship.d.ts.map +1 -0
  122. package/dist/src/generated/prisma/models/EntityRelationship.js +3 -0
  123. package/dist/src/generated/prisma/models/EntityRelationship.js.map +1 -0
  124. package/dist/src/generated/prisma/models/KnowledgeEntry.d.ts +1322 -0
  125. package/dist/src/generated/prisma/models/KnowledgeEntry.d.ts.map +1 -0
  126. package/dist/src/generated/prisma/models/KnowledgeEntry.js +3 -0
  127. package/dist/src/generated/prisma/models/KnowledgeEntry.js.map +1 -0
  128. package/dist/src/generated/prisma/models/WriteReceipt.d.ts +1147 -0
  129. package/dist/src/generated/prisma/models/WriteReceipt.d.ts.map +1 -0
  130. package/dist/src/generated/prisma/models/WriteReceipt.js +3 -0
  131. package/dist/src/generated/prisma/models/WriteReceipt.js.map +1 -0
  132. package/dist/src/generated/prisma/models.d.ts +8 -0
  133. package/dist/src/generated/prisma/models.d.ts.map +1 -0
  134. package/dist/src/generated/prisma/models.js +3 -0
  135. package/dist/src/generated/prisma/models.js.map +1 -0
  136. package/dist/src/lib/escalationPaths.d.ts +9 -0
  137. package/dist/src/lib/escalationPaths.d.ts.map +1 -0
  138. package/dist/src/lib/escalationPaths.js +38 -0
  139. package/dist/src/lib/escalationPaths.js.map +1 -0
  140. package/dist/src/lib/llm.d.ts +32 -0
  141. package/dist/src/lib/llm.d.ts.map +1 -0
  142. package/dist/src/lib/llm.js +161 -0
  143. package/dist/src/lib/llm.js.map +1 -0
  144. package/dist/src/lib/metrics.d.ts +21 -0
  145. package/dist/src/lib/metrics.d.ts.map +1 -0
  146. package/dist/src/lib/metrics.js +46 -0
  147. package/dist/src/lib/metrics.js.map +1 -0
  148. package/dist/src/lib/providers/claude.d.ts +7 -0
  149. package/dist/src/lib/providers/claude.d.ts.map +1 -0
  150. package/dist/src/lib/providers/claude.js +9 -0
  151. package/dist/src/lib/providers/claude.js.map +1 -0
  152. package/dist/src/lib/providers/gemini.d.ts +10 -0
  153. package/dist/src/lib/providers/gemini.d.ts.map +1 -0
  154. package/dist/src/lib/providers/gemini.js +40 -0
  155. package/dist/src/lib/providers/gemini.js.map +1 -0
  156. package/dist/src/lib/providers/groq.d.ts +10 -0
  157. package/dist/src/lib/providers/groq.d.ts.map +1 -0
  158. package/dist/src/lib/providers/groq.js +39 -0
  159. package/dist/src/lib/providers/groq.js.map +1 -0
  160. package/dist/src/lib/providers/mistral.d.ts +10 -0
  161. package/dist/src/lib/providers/mistral.d.ts.map +1 -0
  162. package/dist/src/lib/providers/mistral.js +39 -0
  163. package/dist/src/lib/providers/mistral.js.map +1 -0
  164. package/dist/src/lib/providers/mock.d.ts +24 -0
  165. package/dist/src/lib/providers/mock.d.ts.map +1 -0
  166. package/dist/src/lib/providers/mock.js +129 -0
  167. package/dist/src/lib/providers/mock.js.map +1 -0
  168. package/dist/src/lib/providers/ollama.d.ts +10 -0
  169. package/dist/src/lib/providers/ollama.d.ts.map +1 -0
  170. package/dist/src/lib/providers/ollama.js +39 -0
  171. package/dist/src/lib/providers/ollama.js.map +1 -0
  172. package/dist/src/lib/providers/openai.d.ts +11 -0
  173. package/dist/src/lib/providers/openai.d.ts.map +1 -0
  174. package/dist/src/lib/providers/openai.js +38 -0
  175. package/dist/src/lib/providers/openai.js.map +1 -0
  176. package/dist/src/lib/requestContext.d.ts +8 -0
  177. package/dist/src/lib/requestContext.d.ts.map +1 -0
  178. package/dist/src/lib/requestContext.js +10 -0
  179. package/dist/src/lib/requestContext.js.map +1 -0
  180. package/dist/src/lib/router.d.ts +16 -0
  181. package/dist/src/lib/router.d.ts.map +1 -0
  182. package/dist/src/lib/router.js +63 -0
  183. package/dist/src/lib/router.js.map +1 -0
  184. package/dist/src/librarian/chunker.d.ts +16 -0
  185. package/dist/src/librarian/chunker.d.ts.map +1 -0
  186. package/dist/src/librarian/chunker.js +67 -0
  187. package/dist/src/librarian/chunker.js.map +1 -0
  188. package/dist/src/librarian/getPolicy.d.ts +3 -0
  189. package/dist/src/librarian/getPolicy.d.ts.map +1 -0
  190. package/dist/src/librarian/getPolicy.js +22 -0
  191. package/dist/src/librarian/getPolicy.js.map +1 -0
  192. package/dist/src/librarian/guards.d.ts +9 -0
  193. package/dist/src/librarian/guards.d.ts.map +1 -0
  194. package/dist/src/librarian/guards.js +52 -0
  195. package/dist/src/librarian/guards.js.map +1 -0
  196. package/dist/src/librarian/index.d.ts +20 -0
  197. package/dist/src/librarian/index.d.ts.map +1 -0
  198. package/dist/src/librarian/index.js +512 -0
  199. package/dist/src/librarian/index.js.map +1 -0
  200. package/dist/src/librarian/policy.d.ts +13 -0
  201. package/dist/src/librarian/policy.d.ts.map +1 -0
  202. package/dist/src/librarian/policy.js +20 -0
  203. package/dist/src/librarian/policy.js.map +1 -0
  204. package/dist/src/librarian/scoring.d.ts +8 -0
  205. package/dist/src/librarian/scoring.d.ts.map +1 -0
  206. package/dist/src/librarian/scoring.js +10 -0
  207. package/dist/src/librarian/scoring.js.map +1 -0
  208. package/dist/src/librarian/source-reliability.d.ts +8 -0
  209. package/dist/src/librarian/source-reliability.d.ts.map +1 -0
  210. package/dist/src/librarian/source-reliability.js +105 -0
  211. package/dist/src/librarian/source-reliability.js.map +1 -0
  212. package/dist/src/library/agent-registry.d.ts +31 -0
  213. package/dist/src/library/agent-registry.d.ts.map +1 -0
  214. package/dist/src/library/agent-registry.js +197 -0
  215. package/dist/src/library/agent-registry.js.map +1 -0
  216. package/dist/src/library/client.d.ts +5 -0
  217. package/dist/src/library/client.d.ts.map +1 -0
  218. package/dist/src/library/client.js +39 -0
  219. package/dist/src/library/client.js.map +1 -0
  220. package/dist/src/library/entity-resolution.d.ts +47 -0
  221. package/dist/src/library/entity-resolution.d.ts.map +1 -0
  222. package/dist/src/library/entity-resolution.js +344 -0
  223. package/dist/src/library/entity-resolution.js.map +1 -0
  224. package/dist/src/library/locks.d.ts +9 -0
  225. package/dist/src/library/locks.d.ts.map +1 -0
  226. package/dist/src/library/locks.js +38 -0
  227. package/dist/src/library/locks.js.map +1 -0
  228. package/dist/src/library/queries.d.ts +66 -0
  229. package/dist/src/library/queries.d.ts.map +1 -0
  230. package/dist/src/library/queries.js +169 -0
  231. package/dist/src/library/queries.js.map +1 -0
  232. package/dist/src/library/relationships.d.ts +30 -0
  233. package/dist/src/library/relationships.d.ts.map +1 -0
  234. package/dist/src/library/relationships.js +97 -0
  235. package/dist/src/library/relationships.js.map +1 -0
  236. package/dist/src/sdk/index.d.ts +108 -0
  237. package/dist/src/sdk/index.d.ts.map +1 -0
  238. package/dist/src/sdk/index.js +323 -0
  239. package/dist/src/sdk/index.js.map +1 -0
  240. package/dist/src/security/apiKeys.d.ts +48 -0
  241. package/dist/src/security/apiKeys.d.ts.map +1 -0
  242. package/dist/src/security/apiKeys.js +279 -0
  243. package/dist/src/security/apiKeys.js.map +1 -0
  244. package/dist/src/types.d.ts +54 -0
  245. package/dist/src/types.d.ts.map +1 -0
  246. package/dist/src/types.js +4 -0
  247. package/dist/src/types.js.map +1 -0
  248. package/package.json +86 -0
  249. package/prisma/migrations/20260228090200_init/migration.sql +49 -0
  250. package/prisma/migrations/20260228121746_add_properties_and_relationships/migration.sql +29 -0
  251. package/prisma/migrations/20260301223834_add_superseded_by_pointer/migration.sql +4 -0
  252. package/prisma/migrations/20260301225152_add_write_receipts/migration.sql +20 -0
  253. package/prisma/migrations/20260302135650_entity_resolution/migration.sql +33 -0
  254. package/prisma/migrations/migration_lock.toml +3 -0
  255. package/prisma/schema.prisma +118 -0
package/.env.example ADDED
@@ -0,0 +1,63 @@
1
+ # Database
2
+ DATABASE_URL=postgresql://postgres:yourpassword@localhost:5432/iranti
3
+ POSTGRES_PASSWORD=yourpassword
4
+
5
+ # LLM Provider (mock | gemini | claude | openai | groq | mistral | ollama)
6
+ LLM_PROVIDER=mock
7
+
8
+ # Fallback chain (comma-separated, tried in order if primary fails)
9
+ # Always falls back to mock as final safety net
10
+ LLM_PROVIDER_FALLBACK=openai,groq,mistral,mock
11
+
12
+ # Gemini (when LLM_PROVIDER=gemini)
13
+ GEMINI_API_KEY=
14
+ GEMINI_MODEL=gemini-2.0-flash-001
15
+
16
+ # Model routing overrides (optional)
17
+ CLASSIFICATION_MODEL=gemini-2.0-flash-001
18
+ RELEVANCE_MODEL=gemini-2.0-flash-001
19
+ CONFLICT_MODEL=gemini-2.5-pro
20
+ SUMMARIZATION_MODEL=gemini-2.0-flash-001
21
+ TASK_INFERENCE_MODEL=gemini-2.0-flash-001
22
+ EXTRACTION_MODEL=gemini-2.0-flash-001
23
+
24
+ # Anthropic (when LLM_PROVIDER=claude)
25
+ ANTHROPIC_API_KEY=
26
+
27
+ # OpenAI (when LLM_PROVIDER=openai)
28
+ OPENAI_API_KEY=
29
+ OPENAI_MODEL=gpt-4o-mini
30
+ OPENAI_BASE_URL=https://api.openai.com/v1
31
+
32
+ # Groq (when LLM_PROVIDER=groq)
33
+ GROQ_API_KEY=
34
+ GROQ_MODEL=llama-3.3-70b-versatile
35
+
36
+ # Mistral (when LLM_PROVIDER=mistral)
37
+ MISTRAL_API_KEY=
38
+ MISTRAL_MODEL=mistral-small-latest
39
+
40
+ # Ollama (when LLM_PROVIDER=ollama, local, no API key needed)
41
+ OLLAMA_MODEL=llama3.2
42
+ OLLAMA_BASE_URL=http://localhost:11434
43
+
44
+ # API Server
45
+ IRANTI_PORT=3001
46
+ IRANTI_API_KEY=your_secret_api_key_here
47
+ # Optional additional legacy keys (comma-separated plaintext)
48
+ IRANTI_API_KEYS=
49
+ # Optional pepper used when hashing registry key secrets
50
+ IRANTI_API_KEY_PEPPER=
51
+
52
+ # Escalation storage and Archivist automation
53
+ # Default escalation root (if unset): ~/.iranti/escalation
54
+ IRANTI_ESCALATION_DIR=
55
+ # Watch escalation/active for changes and run maintenance after debounce
56
+ IRANTI_ARCHIVIST_WATCH=true
57
+ # Delay after the last escalation file change before maintenance runs
58
+ IRANTI_ARCHIVIST_DEBOUNCE_MS=60000
59
+ # Periodic maintenance interval; set >0 to enable (e.g. 21600000 = 6h)
60
+ IRANTI_ARCHIVIST_INTERVAL_MS=0
61
+
62
+ # Node environment
63
+ NODE_ENV=development
package/LICENSE ADDED
@@ -0,0 +1,12 @@
1
+ GNU AFFERO GENERAL PUBLIC LICENSE
2
+ Version 3, 19 November 2007
3
+
4
+ Copyright (C) 2007 Free Software Foundation, Inc. <https://fsf.org/>
5
+
6
+ This project is licensed under the GNU Affero General Public License v3.0
7
+ or (at your option) any later version.
8
+
9
+ You may obtain a copy of the full license text at:
10
+ https://www.gnu.org/licenses/agpl-3.0.txt
11
+
12
+ SPDX-License-Identifier: AGPL-3.0-or-later
package/README.md ADDED
@@ -0,0 +1,520 @@
1
+ # Iranti
2
+
3
+ [![License: AGPL-3.0](https://img.shields.io/badge/License-AGPL--3.0-blue.svg)](https://www.gnu.org/licenses/agpl-3.0.en.html)
4
+ [![Python](https://img.shields.io/badge/python-3.8+-blue.svg)](https://www.python.org/downloads/)
5
+ [![TypeScript](https://img.shields.io/badge/typescript-5.0+-blue.svg)](https://www.typescriptlang.org/)
6
+ [![CrewAI Compatible](https://img.shields.io/badge/CrewAI-compatible-green.svg)](https://www.crewai.com/)
7
+
8
+ **Memory infrastructure for multi-agent AI systems.**
9
+
10
+ Iranti gives agents persistent, identity-based memory. Facts written by one agent are retrievable by any other agent through exact entity+key lookup, not similarity search. Memory persists across sessions and survives context window limits.
11
+
12
+ ---
13
+
14
+ ## What is Iranti?
15
+
16
+ Iranti is a knowledge base for multi-agent systems. Unlike vector databases that retrieve by semantic similarity, Iranti retrieves by identity — this specific entity (`project/nexus_prime`), this specific key (`deadline`), with confidence attached. When Agent A writes a fact, Agent B can retrieve it by exact lookup without being told it exists. Facts persist in PostgreSQL and survive context window boundaries through the `observe()` API.
17
+
18
+ ---
19
+
20
+ ## Runtime Roles
21
+
22
+ - **User**: Person who interacts with an app or chatbot built on Iranti.
23
+ - **Agent**: External AI worker that writes/reads facts through Iranti APIs.
24
+ - **Attendant**: Per-agent memory manager that decides what to inject for each turn.
25
+ - **Librarian**: Conflict-aware writer that owns all KB writes.
26
+ - **Library**: Active truth store (`knowledge_base`) in PostgreSQL.
27
+ - **Archive**: Historical/superseded truth store (`archive`) in PostgreSQL.
28
+ - **Archivist**: Maintenance worker that archives stale/low-confidence facts and processes resolved escalations.
29
+
30
+ ---
31
+
32
+ ## Why Not a Vector Database?
33
+
34
+ | Feature | Vector DB | Iranti |
35
+ |---|---|---|
36
+ | **Retrieval** | Similarity (nearest neighbor) | Identity (entity+key) |
37
+ | **Storage** | Embeddings in vector space | Structured facts with keys |
38
+ | **Persistence** | Stateless between calls | Persistent across sessions |
39
+ | **Confidence** | No confidence tracking | Per-fact confidence scores |
40
+ | **Conflicts** | No conflict resolution | Automatic resolution + escalation |
41
+ | **Context** | No context awareness | `observe()` injects missing facts |
42
+
43
+ Vector databases answer "what's similar to X?" Iranti answers "what do we know about X?"
44
+
45
+ ---
46
+
47
+ ## Validated Results
48
+
49
+ All five goals validated with fictional entities and invented facts that GPT-4o-mini cannot know from training data.
50
+
51
+ | Goal | Experiment | Score | Status |
52
+ |---|---|---|---|
53
+ | **1. Easy Integration** | Raw HTTP (9 lines) | 3/3 facts | ✓ PASSED |
54
+ | **2. Context Persistence** | observe() API | 6/6 injected | ✓ PASSED |
55
+ | **3. Working Retrieval** | Cross-agent query | 5/5 facts | ✓ PASSED |
56
+ | **4. Per-Agent Persistence** | Cross-process | 5/5 facts | ✓ PASSED |
57
+ | **5. Response Quality** | Memory injection | 0/2 → 2/2 | ✓ PASSED |
58
+
59
+ ### Framework Compatibility
60
+
61
+ Validated with multiple agent frameworks:
62
+
63
+ | Framework | Entity | Facts | Score | Time |
64
+ |---|---|---|---|---|
65
+ | **Raw OpenAI API** | project/void_runner | 5 | 5/5 ✓ | 14.0s |
66
+ | **LangChain** | project/stellar_drift | 5 | 5/5 ✓ | 2.9s |
67
+ | **CrewAI** | project/nexus_prime | 6 | 6/6 ✓ | 60s |
68
+
69
+ **Total: 16/16 facts transferred (100%)**
70
+
71
+ Full validation report: [`docs/internal/validation_results.md`](docs/internal/validation_results.md) | Multi-framework details: [`docs/internal/MULTI_FRAMEWORK_VALIDATION.md`](docs/internal/MULTI_FRAMEWORK_VALIDATION.md)
72
+
73
+ ### Goal 1: Easy Integration
74
+
75
+ - **Entity**: `project/quantum_bridge`
76
+ - **Test**: Integrate Iranti with raw HTTP in under 20 lines of Python
77
+ - **Result**: 9 lines of code, 3/3 facts written and retrieved
78
+ - **Conclusion**: No SDK or framework dependencies required, just standard `requests` library
79
+
80
+ ### Goal 2: Context Persistence
81
+
82
+ - **Entity**: `project/nexus_prime`
83
+ - **Control**: Facts already in context → `observe()` returns 0 to inject (correct, avoids duplication)
84
+ - **Treatment**: Facts missing from context → `observe()` returns 6/6 facts for injection
85
+ - **Result**: 100% recovery rate when facts fall out of context window
86
+
87
+ ### Goal 3: Working Retrieval
88
+
89
+ - **Entity**: `project/photon_cascade`
90
+ - **Test**: Agent 2 retrieves facts written by Agent 1 with zero shared context
91
+ - **Result**: 5/5 facts retrieved via identity-based lookup (entity+key)
92
+ - **Conclusion**: Facts accessible across agents with no context window dependency
93
+
94
+ ### Goal 4: Per-Agent Knowledge Persistence
95
+
96
+ - **Entity**: `project/resonance_field`
97
+ - **Test**: Process 1 writes facts and exits, Process 2 reads in new process
98
+ - **Result**: 5/5 facts retrieved with no shared state between processes
99
+ - **Conclusion**: PostgreSQL storage validated, facts survive across sessions
100
+
101
+ ### Goal 5: Response Quality
102
+
103
+ - **Entity**: `project/meridian_core`
104
+ - **Test**: Ask LLM question requiring facts from earlier in long conversation
105
+ - **Control**: Without Iranti → 0/2 facts correct (hallucinated answers)
106
+ - **Treatment**: With Iranti memory injection → 2/2 facts correct (accurate answers)
107
+ - **Conclusion**: Memory injection eliminates hallucination, improves response accuracy
108
+
109
+ Full validation report: [`docs/internal/validation_results.md`](docs/internal/validation_results.md)
110
+
111
+ ## Quickstart
112
+
113
+ **Requirements**: Node.js 18+, Docker, Python 3.8+
114
+
115
+ ```bash
116
+ # 1. Clone and configure
117
+ git clone https://github.com/nfemmanuel/iranti
118
+ cd iranti
119
+ cp .env.example .env # Set DATABASE_URL and IRANTI_API_KEY
120
+
121
+ # Optional runtime hygiene
122
+ # IRANTI_ESCALATION_DIR=C:/Users/<you>/.iranti/escalation
123
+ # IRANTI_ARCHIVIST_WATCH=true
124
+ # IRANTI_ARCHIVIST_DEBOUNCE_MS=60000
125
+ # IRANTI_ARCHIVIST_INTERVAL_MS=21600000
126
+
127
+ # 2. Start PostgreSQL
128
+ docker-compose up -d
129
+
130
+ # 3. Install and initialize
131
+ npm install
132
+ npm run setup # Runs migrations
133
+
134
+ # 4. Start API server
135
+ npm run api # Runs on port 3001
136
+
137
+ # 5. Install Python client
138
+ pip install iranti
139
+ ```
140
+
141
+ ### Archivist Scheduling Knobs
142
+
143
+ - `IRANTI_ARCHIVIST_WATCH=true` enables file-change watching on escalation `active/`.
144
+ - `IRANTI_ARCHIVIST_DEBOUNCE_MS=60000` runs maintenance 60s after the latest file change.
145
+ - `IRANTI_ARCHIVIST_INTERVAL_MS=21600000` runs maintenance every 6 hours (set `0` to disable).
146
+ - `IRANTI_ESCALATION_DIR` sets escalation storage root. Default is `~/.iranti/escalation`, keeping escalation files out of the repo by default.
147
+
148
+ ### Per-User API Keys (Recommended)
149
+
150
+ ```bash
151
+ # Create a key for one user/app (prints token once)
152
+ npm run api-key:create -- --key-id chatbot_alice --owner "Alice chatbot" --scopes "kb:read,kb:write,memory:read,memory:write,agents:read,agents:write"
153
+
154
+ # List keys
155
+ npm run api-key:list
156
+
157
+ # Revoke a key
158
+ npm run api-key:revoke -- --key-id chatbot_alice
159
+ ```
160
+
161
+ Use the printed token (`keyId.secret`) as `X-Iranti-Key`.
162
+ Scopes use `resource:action` format (for example `kb:read`, `memory:write`, `metrics:read`, `proxy:chat`).
163
+
164
+ ---
165
+
166
+ ## Install Strategy (Double Layer)
167
+
168
+ Iranti now supports a two-layer install flow:
169
+
170
+ 1. **Machine/runtime layer**: one local runtime root with one or more named Iranti instances.
171
+ 2. **Project layer**: each chatbot/app binds to one instance with a local `.env.iranti`.
172
+
173
+ ### 1) Install CLI
174
+
175
+ ```bash
176
+ # If published package is available
177
+ npm install -g iranti
178
+
179
+ # Or from this repo (local simulation)
180
+ npm install -g .
181
+ ```
182
+
183
+ ### 2) Initialize machine runtime root
184
+
185
+ ```bash
186
+ iranti install --scope user
187
+ ```
188
+
189
+ Defaults:
190
+ - Windows user scope: `%USERPROFILE%\\.iranti`
191
+ - Windows system scope: `%ProgramData%\\Iranti`
192
+ - Linux system scope: `/var/lib/iranti`
193
+ - macOS system scope: `/Library/Application Support/Iranti`
194
+
195
+ ### 3) Create a named instance
196
+
197
+ ```bash
198
+ iranti instance create local --port 3001 --db-url "postgresql://postgres:yourpassword@localhost:5432/iranti_local"
199
+ iranti instance show local
200
+ ```
201
+
202
+ Then edit the printed instance `.env` file and set:
203
+ - `DATABASE_URL` (real value)
204
+ - `IRANTI_API_KEY` (real token)
205
+
206
+ ### 4) Run Iranti from that instance
207
+
208
+ ```bash
209
+ iranti run --instance local
210
+ ```
211
+
212
+ ### 5) Bind any chatbot/app project to that instance
213
+
214
+ ```bash
215
+ cd /path/to/your/chatbot
216
+ iranti project init . --instance local --agent-id chatbot_main
217
+ ```
218
+
219
+ This writes `.env.iranti` in the project with the correct `IRANTI_URL`, `IRANTI_API_KEY`, and default agent identity.
220
+
221
+ For multi-agent systems, bind once per project and set unique agent IDs per worker (for example `planner_agent`, `research_agent`, `critic_agent`).
222
+
223
+ ---
224
+
225
+ ## Core API
226
+
227
+ ### Write a Fact
228
+
229
+ ```python
230
+ from clients.python.iranti import IrantiClient
231
+
232
+ client = IrantiClient(
233
+ base_url="http://localhost:3001",
234
+ api_key="your_api_key_here"
235
+ )
236
+
237
+ result = client.write(
238
+ entity="researcher/jane_smith", # Format: entityType/entityId
239
+ key="affiliation",
240
+ value={"institution": "MIT", "department": "CSAIL"},
241
+ summary="Affiliated with MIT CSAIL", # Compressed for working memory
242
+ confidence=85, # 0-100
243
+ source="OpenAlex",
244
+ agent="research_agent_001"
245
+ )
246
+
247
+ print(result.action) # 'created', 'updated', 'escalated', or 'rejected'
248
+ ```
249
+
250
+ ### Query a Fact
251
+
252
+ ```python
253
+ result = client.query("researcher/jane_smith", "affiliation")
254
+
255
+ if result.found:
256
+ print(result.value) # {"institution": "MIT", "department": "CSAIL"}
257
+ print(result.confidence) # 85
258
+ print(result.source) # "OpenAlex"
259
+ ```
260
+
261
+ ### Query All Facts for an Entity
262
+
263
+ ```python
264
+ facts = client.query_all("researcher/jane_smith")
265
+
266
+ for fact in facts:
267
+ print(f"[{fact['key']}] {fact['summary']} (confidence: {fact['confidence']})")
268
+ ```
269
+
270
+ ### Context Persistence (attend)
271
+
272
+ ```python
273
+ # Before each LLM call, let Attendant decide if memory is needed
274
+ result = client.attend(
275
+ agent_id="research_agent_001",
276
+ latest_message="What's Jane Smith's current affiliation?",
277
+ current_context="User: What's Jane Smith's current affiliation?\nAssistant: Let me check...",
278
+ max_facts=5
279
+ )
280
+
281
+ if result["shouldInject"]:
282
+ for fact in result['facts']:
283
+ print(f"Inject: [{fact['entityKey']}] {fact['summary']}")
284
+ ```
285
+
286
+ ### Working Memory (handshake)
287
+
288
+ ```python
289
+ # At session start, get personalized brief for agent's current task
290
+ brief = client.handshake(
291
+ agent="research_agent_001",
292
+ task="Research publication history for Dr. Jane Smith",
293
+ recent_messages=["Starting literature review..."]
294
+ )
295
+
296
+ print(brief.operating_rules) # Staff namespace rules for this agent
297
+ print(brief.inferred_task_type) # e.g. "research", "verification"
298
+
299
+ for entry in brief.working_memory:
300
+ print(f"{entry.entity_key}: {entry.summary}")
301
+ ```
302
+
303
+ ---
304
+
305
+ ## CrewAI Integration
306
+
307
+ Minimal working example based on validated experiments:
308
+
309
+ ```python
310
+ from crewai import Agent, Task, Crew, LLM
311
+ from crewai.tools import tool
312
+ from clients.python.iranti import IrantiClient
313
+
314
+ iranti = IrantiClient(base_url="http://localhost:3001", api_key="your_key")
315
+ ENTITY = "project/my_project"
316
+
317
+ @tool("Write finding to shared memory")
318
+ def write_finding(key: str, value: str, summary: str, confidence: int) -> str:
319
+ """Write a fact to Iranti so other agents can access it."""
320
+ result = iranti.write(
321
+ entity=ENTITY,
322
+ key=key,
323
+ value={"data": value},
324
+ summary=summary,
325
+ confidence=confidence,
326
+ source="briefing_doc",
327
+ agent="researcher_agent"
328
+ )
329
+ return f"Saved '{key}': {result.action}"
330
+
331
+ @tool("Get all findings")
332
+ def get_all_findings() -> str:
333
+ """Load all facts from Iranti."""
334
+ facts = iranti.query_all(ENTITY)
335
+ if not facts:
336
+ return "No findings in shared memory."
337
+ lines = [f"[{f['key']}] {f['summary']} (confidence: {f['confidence']})" for f in facts]
338
+ return "\n".join(lines)
339
+
340
+ # Researcher agent: writes to Iranti
341
+ researcher = Agent(
342
+ role="Research Analyst",
343
+ goal="Extract facts from documents and save to shared memory",
344
+ tools=[write_finding],
345
+ llm=LLM(model="gpt-4o-mini")
346
+ )
347
+
348
+ # Analyst agent: reads from Iranti
349
+ analyst = Agent(
350
+ role="Project Analyst",
351
+ goal="Summarize projects using shared memory",
352
+ tools=[get_all_findings],
353
+ llm=LLM(model="gpt-4o-mini")
354
+ )
355
+
356
+ # Researcher extracts facts, analyst loads them — no direct communication needed
357
+ crew = Crew(agents=[researcher, analyst], tasks=[...])
358
+ crew.kickoff()
359
+ ```
360
+
361
+ **Result**: Analyst successfully loads all facts written by researcher (validated 6/6 transfer rate).
362
+
363
+ ---
364
+
365
+ ## Middleware for Any LLM
366
+
367
+ Add Iranti memory to Claude, ChatGPT, or any LLM via API wrapper:
368
+
369
+ ```python
370
+ from clients.middleware.iranti_middleware import IrantiMiddleware
371
+
372
+ middleware = IrantiMiddleware(
373
+ agent_id="my_agent",
374
+ iranti_url="http://localhost:3001"
375
+ )
376
+
377
+ # Before sending to LLM
378
+ augmented = middleware.before_send(
379
+ user_message="What was the blocker?",
380
+ conversation_history=[...]
381
+ )
382
+
383
+ # After receiving response
384
+ middleware.after_receive(
385
+ response="The blocker is...",
386
+ conversation_history=[...]
387
+ )
388
+ ```
389
+
390
+ **How it works**:
391
+ 1. `before_send()` calls `attend()` with conversation context
392
+ 2. Forgotten facts are prepended as `[MEMORY: ...]`
393
+ 3. `after_receive()` extracts new facts and saves them (best-effort)
394
+
395
+ **Note**: Browser extensions are blocked by ChatGPT and Claude's Content Security Policy. Use API-based middleware instead.
396
+
397
+ **Examples**: [`clients/middleware/claude_example.py`](clients/middleware/claude_example.py)
398
+
399
+ ---
400
+
401
+ ## Architecture
402
+
403
+ Iranti has four internal components:
404
+
405
+ | Component | Role |
406
+ |---|---|
407
+ | **Library** | PostgreSQL knowledge base. Active truth (soft-deleted entries marked as archived). Full provenance in Archive table. |
408
+ | **Librarian** | Manages all writes. Detects conflicts, reasons about resolution, escalates when uncertain. |
409
+ | **Attendant** | Per-agent working memory manager. Implements `attend()`, `observe()`, and `handshake()` APIs. |
410
+ | **Archivist** | Periodic cleanup. Archives expired and low-confidence entries. Processes human-resolved conflicts. |
411
+
412
+ ### REST API
413
+
414
+ Express server on port 3001 with endpoints:
415
+
416
+ - `POST /kb/write` - Write atomic fact
417
+ - `POST /kb/ingest` - Ingest raw text, auto-chunk into facts
418
+ - `GET /kb/query/:entityType/:entityId/:key` - Query specific fact
419
+ - `GET /kb/query/:entityType/:entityId` - Query all facts for entity
420
+ - `POST /memory/attend` - Decide whether to inject memory for this turn
421
+ - `POST /memory/observe` - Context persistence (inject missing facts)
422
+ - `POST /memory/handshake` - Working memory brief for agent session
423
+ - `POST /kb/relate` - Create entity relationship
424
+ - `GET /kb/related/:entityType/:entityId` - Get related entities
425
+ - `POST /agents/register` - Register agent in registry
426
+
427
+ All endpoints require `X-Iranti-Key` header for authentication.
428
+
429
+ ---
430
+
431
+ ## Schema
432
+
433
+ Three PostgreSQL tables:
434
+
435
+ ```
436
+ knowledge_base — active truth (archived entries soft-deleted with confidence=0)
437
+ archive — full provenance history, never deleted, includes supersededBy links
438
+ entity_relationships — directional graph: MEMBER_OF, PART_OF, AUTHORED, etc.
439
+ ```
440
+
441
+ Every table has a `properties` JSON column for caller-defined metadata. New entity types, relationship types, and fact keys never require migrations — they are just strings you define.
442
+
443
+ **Archive semantics**: When an entry is archived, it remains in knowledge_base with confidence set to 0 and summary marked as `[ARCHIVED]`. A full copy is written to the archive table with supersededBy linking for traceability. Nothing is ever truly deleted.
444
+
445
+ ---
446
+
447
+ ## Running Tests
448
+
449
+ ```bash
450
+ npm run test:integration # Full end-to-end
451
+ npm run test:librarian # Conflict resolution
452
+ npm run test:attendant # Working memory
453
+ npm run test:reliability # Source scoring
454
+
455
+ # Python validation experiments
456
+ cd clients/experiments
457
+ python validate_nexus_observe.py # Context persistence
458
+ python validate_nexus_treatment.py # Cross-agent transfer
459
+ ```
460
+
461
+ ---
462
+
463
+ ## Contributing
464
+
465
+ Contributions welcome! Please:
466
+
467
+ 1. Fork the repository
468
+ 2. Create a feature branch (`git checkout -b feature/amazing-feature`)
469
+ 3. Commit your changes (`git commit -m 'Add amazing feature'`)
470
+ 4. Push to the branch (`git push origin feature/amazing-feature`)
471
+ 5. Open a Pull Request
472
+
473
+ ---
474
+
475
+ ## License
476
+
477
+ GNU Affero General Public License v3.0 (AGPL-3.0) - see [LICENSE](LICENSE) file for details.
478
+
479
+ Free to use, modify, and distribute under AGPL terms. If you offer Iranti as a hosted service and modify it, AGPL requires publishing those modifications.
480
+
481
+ ---
482
+
483
+ ## Name
484
+
485
+ Iranti is the Yoruba word for memory and remembrance.
486
+
487
+ ---
488
+
489
+ ## Project Structure
490
+
491
+ ```
492
+ src/
493
+ ├── library/ — DB client, queries, relationships, agent registry
494
+ ├── librarian/ — Write logic, conflict resolution, reliability
495
+ ├── attendant/ — Per-agent working memory, observe() implementation
496
+ ├── archivist/ — Periodic cleanup, escalation processing
497
+ ├── lib/ — LLM abstraction, model router, providers
498
+ ├── sdk/ — Public TypeScript API
499
+ └── api/ — REST API server
500
+
501
+ clients/
502
+ ├── python/ — Python client (IrantiClient)
503
+ ├── middleware/ — LLM conversation wrappers (Claude, ChatGPT, etc.)
504
+ └── experiments/ — Validated experiments with real results
505
+
506
+ docs/
507
+ └── internal/validation_results.md — Full experiment outputs and analysis
508
+ ```
509
+
510
+ ---
511
+
512
+ ## Support
513
+
514
+ - **Issues**: [GitHub Issues](https://github.com/nfemmanuel/iranti/issues)
515
+ - **Discussions**: [GitHub Discussions](https://github.com/nfemmanuel/iranti/discussions)
516
+ - **Email**: oluwaniifemi.emmanuel@uni.minerva.edu
517
+
518
+ ---
519
+
520
+ **Built with ❤️ for the multi-agent AI community.**
package/bin/iranti.js ADDED
@@ -0,0 +1,24 @@
1
+ #!/usr/bin/env node
2
+ 'use strict';
3
+
4
+ const fs = require('fs');
5
+ const path = require('path');
6
+
7
+ const root = path.resolve(__dirname, '..');
8
+ const distCli = path.join(root, 'dist', 'scripts', 'iranti-cli.js');
9
+ const srcCli = path.join(root, 'scripts', 'iranti-cli.ts');
10
+
11
+ if (fs.existsSync(distCli)) {
12
+ require(distCli);
13
+ return;
14
+ }
15
+
16
+ try {
17
+ require('ts-node/register/transpile-only');
18
+ require(srcCli);
19
+ } catch (err) {
20
+ const message = err instanceof Error ? err.message : String(err);
21
+ console.error('Iranti CLI is not built. Run "npm run build" first.');
22
+ console.error(message);
23
+ process.exit(1);
24
+ }
@@ -0,0 +1,57 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ require("dotenv/config");
4
+ const client_1 = require("../src/library/client");
5
+ const apiKeys_1 = require("../src/security/apiKeys");
6
+ function parseArg(flag) {
7
+ const idx = process.argv.indexOf(flag);
8
+ if (idx === -1)
9
+ return undefined;
10
+ return process.argv[idx + 1];
11
+ }
12
+ async function main() {
13
+ const keyId = parseArg('--key-id') ?? parseArg('-k');
14
+ const owner = parseArg('--owner') ?? parseArg('-o');
15
+ const scopesRaw = parseArg('--scopes') ?? '';
16
+ const description = parseArg('--description');
17
+ const scopes = scopesRaw
18
+ .split(',')
19
+ .map((s) => s.trim())
20
+ .filter(Boolean);
21
+ if (!keyId || !owner) {
22
+ console.error('Usage: npm run api-key:create -- --key-id <id> --owner <owner> [--scopes read,write] [--description "text"]');
23
+ process.exit(1);
24
+ }
25
+ const dbUrl = process.env.DATABASE_URL;
26
+ if (!dbUrl) {
27
+ console.error('DATABASE_URL is required.');
28
+ process.exit(1);
29
+ }
30
+ (0, client_1.initDb)(dbUrl);
31
+ const created = await (0, apiKeys_1.createOrRotateApiKey)({
32
+ keyId,
33
+ owner,
34
+ scopes,
35
+ description,
36
+ });
37
+ console.log('\nAPI key created (or rotated):');
38
+ console.log(` keyId: ${created.record.keyId}`);
39
+ console.log(` owner: ${created.record.owner}`);
40
+ console.log(` scopes: ${created.record.scopes.join(',') || '(none)'}`);
41
+ console.log(` active: ${created.record.isActive}`);
42
+ console.log('\nCopy this token now (it will not be shown again):');
43
+ console.log(created.token);
44
+ console.log('\nUse it as: X-Iranti-Key: <token>\n');
45
+ await (0, client_1.getDb)().$disconnect();
46
+ }
47
+ main().catch(async (err) => {
48
+ console.error('Failed to create API key:', err instanceof Error ? err.message : String(err));
49
+ try {
50
+ await (0, client_1.getDb)().$disconnect();
51
+ }
52
+ catch {
53
+ // ignore
54
+ }
55
+ process.exit(1);
56
+ });
57
+ //# sourceMappingURL=api-key-create.js.map