iranti 0.2.20 → 0.2.22

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.
package/README.md CHANGED
@@ -9,10 +9,10 @@
9
9
 
10
10
  Iranti gives agents persistent, identity-based memory. Facts written by one agent are retrievable by any other agent through exact entity+key lookup. Iranti also supports hybrid search (lexical + vector) when exact keys are unknown. Memory persists across sessions and survives context window limits.
11
11
 
12
- **Latest release:** [`v0.2.20`](https://github.com/nfemmanuel/iranti/releases/tag/v0.2.20)
12
+ **Latest release:** [`v0.2.21`](https://github.com/nfemmanuel/iranti/releases/tag/v0.2.21)
13
13
  Published packages:
14
- - `iranti@0.2.20`
15
- - `@iranti/sdk@0.2.20`
14
+ - `iranti@0.2.21`
15
+ - `@iranti/sdk@0.2.21`
16
16
 
17
17
  ---
18
18
 
@@ -52,27 +52,26 @@ Vector databases answer "what's similar to X?" Iranti answers "what do we know a
52
52
 
53
53
  ## Benchmark Summary
54
54
 
55
- Iranti has now been rerun against a broader benchmark program covering 13 capability tracks. The current picture is stronger and narrower than the early validation story: exact, durable, shared memory is benchmark-backed; broad semantic-memory claims still need tighter boundaries.
55
+ Iranti has now been rerun against a broader benchmark program covering 11 active capability tracks in `v0.2.21`. The current picture is stronger and narrower than the early validation story: exact, durable, shared memory is benchmark-backed; broad semantic-memory and autonomous-memory claims still need tighter boundaries.
56
56
 
57
57
  ### Confirmed Strengths
58
58
 
59
- - **Exact lookup (`iranti_query`)**: O(1) retrieval confirmed up to 276k-token haystacks, with a positive differential over baseline at large context size.
60
- - **Persistence**: Facts survive across sessions and across version upgrades with no observed data loss.
59
+ - **Exact lookup (`iranti_query`)**: Retrieval remains exact and durable across genuine session and process breaks. At tested scale (`N=1938`, about `107k` tokens), the measured advantage is efficiency, not accuracy: Iranti answered `10/10` with zero haystack tokens while the baseline also answered `10/10` after reading the full document.
60
+ - **Persistence across sessions**: Facts survive context-window loss and genuine process boundaries. `iranti_query` remained `8/8` across isolated session breaks in the rerun.
61
61
  - **Conflict handling**: Reliable when confidence differentials are large and explicit.
62
- - **Multi-agent coordination**: Agents can share memory with correct separation and attribution.
63
- - **Provenance**: `agentId` attribution is preserved correctly.
64
- - **Relationships**: Relationship writes, reads, and depth traversal are working.
65
- - **Ingest**: Prose extraction is working and benchmark-confirmed in `v0.2.16`.
66
- - **Search**: Effective for structured attribute-value retrieval.
67
- - **Observe / Attend**: Automatic detection and injection behavior improved materially and now works in realistic benchmark conditions.
62
+ - **Multi-agent coordination**: Agents can share memory across genuine subprocess boundaries with zero shared conversational context.
63
+ - **Provenance on writes**: Write-side attribution through stored source metadata is working and benchmark-confirmed.
64
+ - **Ingest**: Prose extraction is accurate on clean entities in `v0.2.21`. Reliability under conflict-heavy transactional conditions should still be treated as a separate, narrower claim.
65
+ - **Observe with hints**: `iranti_observe` recovers facts reliably when given the right entity hint, with higher-confidence facts returned first.
68
66
  - **Session recovery**: Interrupted-session recovery now performs substantially better than baseline.
69
- - **Upgrade safety**: Memory durability was preserved across three version upgrades.
70
67
 
71
68
  ### Current Limits
72
69
 
73
- - **Search is not yet full semantic paraphrase retrieval.**
74
- - **Observe still performs better on confidence ranking than on broad progress-fact discovery.**
75
- - **Structured search is operational, but not yet broad semantic paraphrase retrieval.**
70
+ - **Search is lexical-first today, not semantic multi-hop retrieval.** In the current rerun, hop-value discovery was `0/4`; bare entity-token lookup worked, but `vectorScore` stayed `0` across results.
71
+ - **`iranti_attend` is not yet a reliable autonomous classifier.** Natural-language attend classification still falls back to `classification_parse_failed_default_false`; `forceInject` works as an operator bypass, not as proof of autonomous injection.
72
+ - **Observe performs better with explicit entity hints than with cold-start discovery.**
73
+ - **Upgrade durability should be scoped carefully.** The `v0.2.21` upgrade procedure reinitialized the instance under test; do not assume KB data survives upgrades without an explicit preservation or migration path.
74
+ - **Relationship and provenance reflection surfaces remain partially permission-gated in benchmark sessions.** The rerun did not prove `iranti_relate`, `iranti_related`, `iranti_related_deep`, or `iranti_who_knows` end-to-end under the benchmark session policy.
76
75
 
77
76
  ### Practical Position
78
77
 
@@ -82,9 +81,8 @@ Iranti is strongest today as **structured memory infrastructure for multi-agent
82
81
  - provenance-aware writes
83
82
  - conflict-aware storage
84
83
  - session-aware recovery
85
- - upgrade-safe persistence
86
84
 
87
- It should not yet be described as a fully general semantic-memory or autonomous-extraction system.
85
+ It should not yet be described as a fully general semantic-memory, semantic-search, or autonomous-memory-injection system.
88
86
 
89
87
  Historical benchmark material remains available here:
90
88
  - [`docs/internal/validation_results.md`](docs/internal/validation_results.md)
@@ -106,7 +104,7 @@ The current competitive case for Iranti is strongest when a team needs memory th
106
104
  - Explicit per-fact confidence scores
107
105
  - Per-agent memory injection through the Attendant
108
106
  - Temporal exact lookup with `asOf` and ordered `history()`
109
- - Relationship primitives through `relate()`, `getRelated()`, and `getRelatedDeep()`
107
+ - Relationship primitives through `relate()`, `getRelated()`, and `getRelatedDeep()` at the product surface, with benchmark confirmation for those MCP-accessible paths still pending
110
108
  - Hybrid retrieval when exact keys are unknown
111
109
  - Local install + project binding flow for Claude Code and Codex
112
110
  - Published npm / PyPI surfaces with machine-level CLI setup
@@ -163,11 +161,12 @@ The current landscape splits into three buckets:
163
161
  Iranti is strongest today as infrastructure for developers building multi-agent systems who need shared, structured, queryable memory rather than pure semantic recall. The current benchmark base now supports a more concrete product claim:
164
162
 
165
163
  - exact cross-agent fact transfer works at meaningful context scales
166
- - facts survive session loss and version upgrades
164
+ - facts survive session loss and genuine process breaks
167
165
  - same-key conflicting writes are serialized and observable
168
- - relationship traversal, prose ingest, and attended recovery are usable surfaces
166
+ - prose ingest is accurate on clean entities
167
+ - attended recovery works with explicit hints, while autonomous attend classification remains a known defect
169
168
 
170
- That is still not a claim that multi-agent memory is solved. It is a claim that Iranti now has broader evidence for durable, structured, attribution-aware memory with recovery and upgrade safety.
169
+ That is still not a claim that multi-agent memory is solved. It is a claim that Iranti now has broader evidence for durable, structured, attribution-aware memory with exact retrieval and bounded recovery behavior.
171
170
 
172
171
  The next leverage is still product simplicity: setup, operations, and day-to-day inspection need to be simple enough that real users keep Iranti in the loop.
173
172
 
@@ -316,7 +315,7 @@ iranti codex-setup
316
315
  codex -C /path/to/your/project
317
316
  ```
318
317
 
319
- When `iranti codex-setup` is run from a project directory, it automatically captures that project's `.env.iranti` as `IRANTI_PROJECT_ENV` so Codex resolves the correct Iranti instance consistently.
318
+ By default, `iranti codex-setup` does not pin a project binding globally. `iranti mcp` resolves `.env.iranti` from the active project/workspace at runtime. Use `--project-env` only if you deliberately want to pin Codex globally to one project binding.
320
319
 
321
320
  Alias:
322
321
 
@@ -71,7 +71,8 @@ function printHelp() {
71
71
  'Notes:',
72
72
  ' - Registers a global Codex MCP entry using `codex mcp add`.',
73
73
  ' - Prefers the installed CLI path: `iranti mcp`.',
74
- ' - Auto-detects .env.iranti from the current working directory and stores it as IRANTI_PROJECT_ENV.',
74
+ ' - By default does not pin IRANTI_PROJECT_ENV, so Codex can resolve .env.iranti from the active project/workspace at runtime.',
75
+ ' - Use --project-env only when you deliberately want to pin Codex globally to one project binding.',
75
76
  ' - Use --local-script only if you need to point Codex at this repo build directly.',
76
77
  ' - Does not store DATABASE_URL in Codex config; iranti-mcp loads project/instance env at runtime.',
77
78
  ' - Replaces any existing MCP entry with the same name.',
@@ -141,8 +142,7 @@ function resolveProjectEnv(options) {
141
142
  }
142
143
  return resolved;
143
144
  }
144
- const candidate = node_path_1.default.resolve(process.cwd(), '.env.iranti');
145
- return node_fs_1.default.existsSync(candidate) ? candidate : undefined;
145
+ return undefined;
146
146
  }
147
147
  function canUseInstalledIranti(repoRoot) {
148
148
  try {
@@ -204,7 +204,10 @@ function main() {
204
204
  if (useInstalled) {
205
205
  console.log('Registration target: installed CLI (`iranti mcp`)');
206
206
  if (projectEnv) {
207
- console.log(`Project binding: ${projectEnv}`);
207
+ console.log(`Pinned project binding: ${projectEnv}`);
208
+ }
209
+ else {
210
+ console.log('Project binding: not pinned; `iranti mcp` will resolve `.env.iranti` from the active project/workspace at runtime.');
208
211
  }
209
212
  console.log('Launch Codex in the project you want to bind to Iranti, for example:');
210
213
  console.log(' codex -C C:\\path\\to\\your\\project');
@@ -212,7 +215,10 @@ function main() {
212
215
  else {
213
216
  console.log(`Registration target: repo build (${mcpScript})`);
214
217
  if (projectEnv) {
215
- console.log(`Project binding: ${projectEnv}`);
218
+ console.log(`Pinned project binding: ${projectEnv}`);
219
+ }
220
+ else {
221
+ console.log('Project binding: not pinned; `iranti mcp` will resolve `.env.iranti` from the active project/workspace at runtime.');
216
222
  }
217
223
  console.log(`Launch with: codex -C "${repoRoot}"`);
218
224
  }
@@ -16,6 +16,7 @@ const net_1 = __importDefault(require("net"));
16
16
  const client_1 = require("../src/library/client");
17
17
  const apiKeys_1 = require("../src/security/apiKeys");
18
18
  const escalationPaths_1 = require("../src/lib/escalationPaths");
19
+ const dockerCliParsing_1 = require("../src/lib/dockerCliParsing");
19
20
  const runtimeEnv_1 = require("../src/lib/runtimeEnv");
20
21
  const resolutionist_1 = require("../src/resolutionist");
21
22
  const chat_1 = require("../src/chat");
@@ -676,8 +677,9 @@ async function withPromptSession(run) {
676
677
  secret: async (prompt, currentValue) => {
677
678
  const placeholder = currentValue ? `${redactSecret(currentValue)} (enter new value to replace)` : 'leave blank to skip';
678
679
  const suffix = placeholder ? ` [${placeholder}]` : '';
680
+ process.stdout.write(`${prompt}${suffix}: `);
679
681
  muted = true;
680
- const answer = (await rl.question(`${prompt}${suffix}: `)).trim();
682
+ const answer = (await rl.question('')).trim();
681
683
  muted = false;
682
684
  process.stdout.write('\n');
683
685
  if (!answer || answer === placeholder)
@@ -689,8 +691,9 @@ async function withPromptSession(run) {
689
691
  secretRequired: async (prompt, currentValue) => {
690
692
  const placeholder = currentValue ? `${redactSecret(currentValue)} (enter new value to replace)` : 'required';
691
693
  const suffix = placeholder ? ` [${placeholder}]` : '';
694
+ process.stdout.write(`${prompt}${suffix}: `);
692
695
  muted = true;
693
- const answer = (await rl.question(`${prompt}${suffix}: `)).trim();
696
+ const answer = (await rl.question('')).trim();
694
697
  muted = false;
695
698
  process.stdout.write('\n');
696
699
  if (!answer || answer === placeholder)
@@ -840,6 +843,18 @@ async function promptRequiredSecret(session, prompt, currentValue) {
840
843
  console.log(`${warnLabel()} ${prompt} is required.`);
841
844
  }
842
845
  }
846
+ async function promptSecretWithDefault(session, prompt, defaultValue) {
847
+ while (true) {
848
+ const value = (await session.secret(`${prompt} (blank uses local-dev default)`, undefined) ?? '').trim();
849
+ if (!value) {
850
+ console.log(`${infoLabel()} Using the local development default for ${prompt}.`);
851
+ return defaultValue;
852
+ }
853
+ if (!detectPlaceholder(value))
854
+ return value;
855
+ console.log(`${warnLabel()} ${prompt} still looks like a placeholder. Enter a real value or leave it blank to use the local-dev default.`);
856
+ }
857
+ }
843
858
  function makeLegacyInstanceApiKey(instanceName) {
844
859
  const keyId = sanitizeIdentifier(`${instanceName}_${os_1.default.userInfo().username}`, 'iranti');
845
860
  return (0, apiKeys_1.formatApiKeyToken)(keyId, (0, apiKeys_1.generateApiKeySecret)());
@@ -990,6 +1005,76 @@ function resolveAttendMessage(args) {
990
1005
  return fromPositionals;
991
1006
  throw new Error('Missing latest message. Usage: iranti attend [message] [--message <text>] [--context <text>] [--json]');
992
1007
  }
1008
+ function parseDelimitedList(raw) {
1009
+ if (!raw?.trim())
1010
+ return [];
1011
+ const delimiter = raw.includes('||') ? '||' : ',';
1012
+ return raw
1013
+ .split(delimiter)
1014
+ .map((item) => item.trim())
1015
+ .filter(Boolean);
1016
+ }
1017
+ function resolveTaskEntity(args) {
1018
+ const entity = (args.subcommand ?? args.positionals[0] ?? getFlag(args, 'entity') ?? '').trim();
1019
+ if (!entity) {
1020
+ throw new Error('Missing task entity. Usage: iranti handoff task/<task_id> --next-step <text> [--json]');
1021
+ }
1022
+ if (!entity.includes('/')) {
1023
+ throw new Error('task entity must use entityType/entityId format.');
1024
+ }
1025
+ return entity;
1026
+ }
1027
+ function buildHandoffSummary(key, value) {
1028
+ switch (key) {
1029
+ case 'status': {
1030
+ const state = typeof value === 'object' && value && 'state' in value ? String(value.state) : 'updated';
1031
+ return `Shared task status is ${state}.`;
1032
+ }
1033
+ case 'current_owner': {
1034
+ const agentId = typeof value === 'object' && value && 'agentId' in value ? String(value.agentId) : 'unassigned';
1035
+ return `Current owner is ${agentId}.`;
1036
+ }
1037
+ case 'next_step': {
1038
+ const instruction = typeof value === 'object' && value && 'instruction' in value ? String(value.instruction) : 'Next step updated.';
1039
+ return truncateText(`Next step: ${instruction}`, 140);
1040
+ }
1041
+ case 'blockers': {
1042
+ const count = typeof value === 'object' && value && 'items' in value && Array.isArray(value.items)
1043
+ ? value.items.length
1044
+ : 0;
1045
+ return count === 0 ? 'No blockers recorded.' : `${count} blocker${count === 1 ? '' : 's'} recorded for the shared task.`;
1046
+ }
1047
+ case 'artifacts': {
1048
+ const count = typeof value === 'object' && value && 'files' in value && Array.isArray(value.files)
1049
+ ? value.files.length
1050
+ : 0;
1051
+ return count === 0 ? 'No artifacts recorded.' : `${count} artifact${count === 1 ? '' : 's'} recorded for the shared task.`;
1052
+ }
1053
+ case 'notes': {
1054
+ const text = typeof value === 'object' && value && 'text' in value ? String(value.text) : 'Shared handoff notes updated.';
1055
+ return truncateText(`Notes: ${text}`, 140);
1056
+ }
1057
+ case 'active_handoff_task': {
1058
+ const taskEntity = typeof value === 'object' && value && 'taskEntity' in value ? String(value.taskEntity) : 'task';
1059
+ return `Project now points to active handoff ${taskEntity}.`;
1060
+ }
1061
+ default:
1062
+ return 'Shared handoff state updated.';
1063
+ }
1064
+ }
1065
+ function printHandoffResult(target, taskEntity, writes) {
1066
+ console.log(bold('Iranti handoff'));
1067
+ console.log(` agent ${target.agentId}`);
1068
+ console.log(` env source ${target.envSource}`);
1069
+ if (target.envFile)
1070
+ console.log(` env file ${target.envFile}`);
1071
+ console.log(` task entity ${taskEntity}`);
1072
+ console.log(` writes ${writes.length}`);
1073
+ console.log('');
1074
+ for (const write of writes) {
1075
+ console.log(`- ${write.entity} :: ${write.key} | ${write.summary}`);
1076
+ }
1077
+ }
993
1078
  function printHandshakeResult(target, task, result) {
994
1079
  console.log(bold('Iranti handshake'));
995
1080
  console.log(` agent ${target.agentId}`);
@@ -1220,7 +1305,7 @@ function inspectDockerAvailability() {
1220
1305
  detail: `Docker CLI is installed, but the daemon is not reachable. ${reason}`,
1221
1306
  };
1222
1307
  }
1223
- async function isPortAvailable(port, host = '127.0.0.1') {
1308
+ async function isPortAvailable(port, host = '0.0.0.0') {
1224
1309
  return await new Promise((resolve) => {
1225
1310
  const server = net_1.default.createServer();
1226
1311
  server.unref();
@@ -1230,18 +1315,33 @@ async function isPortAvailable(port, host = '127.0.0.1') {
1230
1315
  });
1231
1316
  });
1232
1317
  }
1233
- async function findNextAvailablePort(start, host = '127.0.0.1', maxSteps = 50) {
1318
+ function listPublishedDockerHostPorts() {
1319
+ const docker = inspectDockerAvailability();
1320
+ if (!docker.daemonReachable)
1321
+ return new Set();
1322
+ const inspect = runCommandCapture('docker', ['ps', '--format', '{{.Ports}}']);
1323
+ if (inspect.status !== 0)
1324
+ return new Set();
1325
+ return (0, dockerCliParsing_1.parsePublishedDockerHostPorts)(inspect.stdout ?? '');
1326
+ }
1327
+ async function isPortUsable(port, host = '0.0.0.0', dockerPublishedPorts = new Set()) {
1328
+ if (dockerPublishedPorts.has(port))
1329
+ return false;
1330
+ return isPortAvailable(port, host);
1331
+ }
1332
+ async function findNextAvailablePort(start, host = '0.0.0.0', maxSteps = 50, dockerPublishedPorts = new Set()) {
1234
1333
  for (let port = start; port < start + maxSteps; port += 1) {
1235
- if (await isPortAvailable(port, host)) {
1334
+ if (await isPortUsable(port, host, dockerPublishedPorts)) {
1236
1335
  return port;
1237
1336
  }
1238
1337
  }
1239
1338
  throw new Error(`No available port found in range ${start}-${start + maxSteps - 1}.`);
1240
1339
  }
1241
1340
  async function chooseAvailablePort(session, promptText, preferredPort, allowOccupiedCurrent = false) {
1341
+ const dockerPublishedPorts = listPublishedDockerHostPorts();
1242
1342
  let suggested = preferredPort;
1243
- if (!allowOccupiedCurrent && !(await isPortAvailable(preferredPort))) {
1244
- suggested = await findNextAvailablePort(preferredPort + 1);
1343
+ if (!allowOccupiedCurrent && !(await isPortUsable(preferredPort, '0.0.0.0', dockerPublishedPorts))) {
1344
+ suggested = await findNextAvailablePort(preferredPort + 1, '0.0.0.0', 50, dockerPublishedPorts);
1245
1345
  console.log(`${warnLabel()} Port ${preferredPort} is already in use. A good next option is ${suggested}.`);
1246
1346
  }
1247
1347
  while (true) {
@@ -1254,10 +1354,10 @@ async function chooseAvailablePort(session, promptText, preferredPort, allowOccu
1254
1354
  if (allowOccupiedCurrent && parsed === preferredPort) {
1255
1355
  return parsed;
1256
1356
  }
1257
- if (await isPortAvailable(parsed)) {
1357
+ if (await isPortUsable(parsed, '0.0.0.0', dockerPublishedPorts)) {
1258
1358
  return parsed;
1259
1359
  }
1260
- const next = await findNextAvailablePort(parsed + 1);
1360
+ const next = await findNextAvailablePort(parsed + 1, '0.0.0.0', 50, dockerPublishedPorts);
1261
1361
  console.log(`${warnLabel()} Port ${parsed} is already in use. Try ${next} instead.`);
1262
1362
  suggested = next;
1263
1363
  }
@@ -1290,16 +1390,14 @@ async function runDockerPostgresContainer(options) {
1290
1390
  if (!docker.daemonReachable) {
1291
1391
  throw new Error(`Docker daemon is not reachable. Start Docker Desktop or Docker Engine, then retry. ${docker.detail}`);
1292
1392
  }
1293
- const inspect = process.platform === 'win32'
1294
- ? (0, child_process_1.spawnSync)(process.env.ComSpec ?? 'cmd.exe', ['/d', '/c', `docker ps -a --format "{{.Names}}"`], { encoding: 'utf8' })
1295
- : (0, child_process_1.spawnSync)('docker', ['ps', '-a', '--format', '{{.Names}}'], { encoding: 'utf8' });
1393
+ const inspect = runCommandCapture('docker', ['ps', '-a', '--format', '{{.Names}}']);
1296
1394
  if (inspect.status !== 0) {
1297
1395
  throw new Error(`Failed to inspect Docker containers. ${(inspect.stderr ?? inspect.stdout ?? '').trim() || 'docker ps returned a non-zero exit code.'}`);
1298
1396
  }
1299
- const names = (inspect.stdout ?? '').split(/\r?\n/).map((value) => value.trim()).filter(Boolean);
1397
+ const names = (0, dockerCliParsing_1.parseDockerContainerNames)(inspect.stdout ?? '');
1300
1398
  if (names.includes(options.containerName)) {
1301
1399
  const start = process.platform === 'win32'
1302
- ? (0, child_process_1.spawnSync)(process.env.ComSpec ?? 'cmd.exe', ['/d', '/c', `docker start ${options.containerName}`], { stdio: 'inherit' })
1400
+ ? (0, child_process_1.spawnSync)(process.env.ComSpec ?? 'cmd.exe', ['/d', '/c', ['docker', 'start', options.containerName].map(quoteForCmd).join(' ')], { stdio: 'inherit' })
1303
1401
  : (0, child_process_1.spawnSync)('docker', ['start', options.containerName], { stdio: 'inherit' });
1304
1402
  if (start.status !== 0) {
1305
1403
  throw new Error(`Failed to start existing Docker container '${options.containerName}'.`);
@@ -1344,10 +1442,12 @@ async function executeSetupPlan(plan) {
1344
1442
  });
1345
1443
  if (plan.bootstrapDatabase) {
1346
1444
  try {
1347
- if (plan.databaseMode === 'docker') {
1445
+ if (plan.databaseMode === 'docker' && !plan.databaseProvisioned) {
1348
1446
  const parsed = parsePostgresConnectionString(plan.databaseUrl);
1349
1447
  await runDockerPostgresContainer({
1350
- containerName: sanitizeIdentifier(`iranti_${plan.instanceName}_db`, `iranti_${plan.instanceName}_db`),
1448
+ containerName: plan.dockerContainerName
1449
+ ? sanitizeIdentifier(plan.dockerContainerName, `iranti_${plan.instanceName}_db`)
1450
+ : sanitizeIdentifier(`iranti_${plan.instanceName}_db`, `iranti_${plan.instanceName}_db`),
1351
1451
  hostPort: Number.parseInt(parsed.port || '5432', 10),
1352
1452
  password: decodeURIComponent(parsed.password || 'postgres'),
1353
1453
  database: postgresDatabaseName(plan.databaseUrl),
@@ -3326,6 +3426,8 @@ async function setupCommand(args) {
3326
3426
  const psqlAvailable = hasCommandInstalled('psql');
3327
3427
  let dbUrl = '';
3328
3428
  let bootstrapDatabase = false;
3429
+ let databaseProvisioned = false;
3430
+ let dockerContainerName;
3329
3431
  let databaseMode = recommendedDatabaseMode;
3330
3432
  while (true) {
3331
3433
  const defaultMode = recommendedDatabaseMode;
@@ -3360,8 +3462,9 @@ async function setupCommand(args) {
3360
3462
  }
3361
3463
  const dbHostPort = await chooseAvailablePort(prompt, 'Docker PostgreSQL host port', 5432, false);
3362
3464
  const dbName = sanitizeIdentifier(await promptNonEmpty(prompt, 'Docker PostgreSQL database name', `iranti_${instanceName}`), `iranti_${instanceName}`);
3363
- const dbPassword = await promptRequiredSecret(prompt, 'Docker PostgreSQL password');
3465
+ const dbPassword = await promptSecretWithDefault(prompt, 'Docker PostgreSQL password', 'postgres');
3364
3466
  const containerName = sanitizeIdentifier(await promptNonEmpty(prompt, 'Docker container name', `iranti_${instanceName}_db`), `iranti_${instanceName}_db`);
3467
+ dockerContainerName = containerName;
3365
3468
  dbUrl = `postgresql://postgres:${dbPassword}@localhost:${dbHostPort}/${dbName}`;
3366
3469
  console.log(`${infoLabel()} Docker will be used only for PostgreSQL. Iranti itself does not require Docker once a PostgreSQL database is available.`);
3367
3470
  if (await promptYesNo(prompt, `Start or reuse Docker container '${containerName}' now?`, true)) {
@@ -3372,11 +3475,9 @@ async function setupCommand(args) {
3372
3475
  database: dbName,
3373
3476
  });
3374
3477
  console.log(`${okLabel()} Docker PostgreSQL ready at localhost:${dbHostPort}`);
3375
- bootstrapDatabase = true;
3376
- }
3377
- else {
3378
- bootstrapDatabase = await promptYesNo(prompt, 'Will you start PostgreSQL separately before first run?', false);
3478
+ databaseProvisioned = true;
3379
3479
  }
3480
+ bootstrapDatabase = await promptYesNo(prompt, 'Run migrations and seed the database now?', true);
3380
3481
  break;
3381
3482
  }
3382
3483
  console.log(`${warnLabel()} Choose one of: local, managed, docker.`);
@@ -3478,6 +3579,8 @@ async function setupCommand(args) {
3478
3579
  codex,
3479
3580
  codexAgent: projects[0]?.agentId,
3480
3581
  bootstrapDatabase,
3582
+ dockerContainerName,
3583
+ databaseProvisioned,
3481
3584
  });
3482
3585
  });
3483
3586
  if (!result) {
@@ -3519,6 +3622,7 @@ async function doctorCommand(args) {
3519
3622
  const version = getPackageVersion();
3520
3623
  const pushEnvironmentChecks = async (env, prefix = '') => {
3521
3624
  const databaseUrl = env.DATABASE_URL;
3625
+ let databaseInitializedForDoctor = false;
3522
3626
  checks.push(detectPlaceholder(databaseUrl)
3523
3627
  ? {
3524
3628
  name: `${prefix}database configuration`,
@@ -3542,6 +3646,10 @@ async function doctorCommand(args) {
3542
3646
  name: `${prefix}${providerKeyCheck.name}`,
3543
3647
  });
3544
3648
  try {
3649
+ if (!detectPlaceholder(databaseUrl)) {
3650
+ (0, client_1.initDb)(databaseUrl);
3651
+ databaseInitializedForDoctor = true;
3652
+ }
3545
3653
  const backendName = (0, backends_1.resolveVectorBackendName)({
3546
3654
  vectorBackend: env.IRANTI_VECTOR_BACKEND,
3547
3655
  qdrantUrl: env.IRANTI_QDRANT_URL,
@@ -3581,6 +3689,11 @@ async function doctorCommand(args) {
3581
3689
  detail: error instanceof Error ? error.message : String(error),
3582
3690
  });
3583
3691
  }
3692
+ finally {
3693
+ if (databaseInitializedForDoctor) {
3694
+ await (0, client_1.disconnectDb)();
3695
+ }
3696
+ }
3584
3697
  };
3585
3698
  checks.push({
3586
3699
  name: 'node version',
@@ -4623,6 +4736,113 @@ async function attendCommand(args) {
4623
4736
  console.log('');
4624
4737
  console.log(`${infoLabel()} This is a manual Attendant inspection tool. Claude Code should still use hooks + MCP in normal operation.`);
4625
4738
  }
4739
+ async function handoffCommand(args) {
4740
+ const json = hasFlag(args, 'json');
4741
+ const target = await resolveAttendantCliTarget(args);
4742
+ const taskEntity = resolveTaskEntity(args);
4743
+ const projectEntity = getFlag(args, 'project-entity')?.trim();
4744
+ if (projectEntity && !projectEntity.includes('/')) {
4745
+ throw new Error('project-entity must use entityType/entityId format.');
4746
+ }
4747
+ const nextStep = getFlag(args, 'next-step')?.trim();
4748
+ if (!nextStep) {
4749
+ throw new Error('Missing --next-step. A standardized handoff must record the receiver action.');
4750
+ }
4751
+ const status = getFlag(args, 'status')?.trim() || 'ready_for_handoff';
4752
+ const owner = getFlag(args, 'owner')?.trim();
4753
+ const blockers = parseDelimitedList(getFlag(args, 'blockers'));
4754
+ const artifacts = parseDelimitedList(getFlag(args, 'artifacts'));
4755
+ const notes = getFlag(args, 'notes')?.trim();
4756
+ const source = getFlag(args, 'source')?.trim() || 'CLIHandoff';
4757
+ const confidence = parsePositiveInteger(getFlag(args, 'confidence'), 'confidence') ?? 95;
4758
+ if (confidence > 100) {
4759
+ throw new Error('confidence must be <= 100.');
4760
+ }
4761
+ const writes = [];
4762
+ writes.push({
4763
+ entity: taskEntity,
4764
+ key: 'status',
4765
+ value: { state: status },
4766
+ summary: buildHandoffSummary('status', { state: status }),
4767
+ });
4768
+ writes.push({
4769
+ entity: taskEntity,
4770
+ key: 'next_step',
4771
+ value: { instruction: nextStep },
4772
+ summary: buildHandoffSummary('next_step', { instruction: nextStep }),
4773
+ });
4774
+ if (owner) {
4775
+ writes.push({
4776
+ entity: taskEntity,
4777
+ key: 'current_owner',
4778
+ value: { agentId: owner },
4779
+ summary: buildHandoffSummary('current_owner', { agentId: owner }),
4780
+ });
4781
+ }
4782
+ if (blockers.length > 0) {
4783
+ writes.push({
4784
+ entity: taskEntity,
4785
+ key: 'blockers',
4786
+ value: { items: blockers },
4787
+ summary: buildHandoffSummary('blockers', { items: blockers }),
4788
+ });
4789
+ }
4790
+ if (artifacts.length > 0) {
4791
+ writes.push({
4792
+ entity: taskEntity,
4793
+ key: 'artifacts',
4794
+ value: { files: artifacts },
4795
+ summary: buildHandoffSummary('artifacts', { files: artifacts }),
4796
+ });
4797
+ }
4798
+ if (notes) {
4799
+ writes.push({
4800
+ entity: taskEntity,
4801
+ key: 'notes',
4802
+ value: { text: notes },
4803
+ summary: buildHandoffSummary('notes', { text: notes }),
4804
+ });
4805
+ }
4806
+ if (projectEntity) {
4807
+ writes.push({
4808
+ entity: projectEntity,
4809
+ key: 'active_handoff_task',
4810
+ value: {
4811
+ taskEntity,
4812
+ owner: owner ?? null,
4813
+ status,
4814
+ updatedBy: target.agentId,
4815
+ },
4816
+ summary: buildHandoffSummary('active_handoff_task', { taskEntity }),
4817
+ });
4818
+ }
4819
+ for (const write of writes) {
4820
+ await target.iranti.write({
4821
+ entity: write.entity,
4822
+ key: write.key,
4823
+ value: write.value,
4824
+ summary: write.summary,
4825
+ confidence,
4826
+ source,
4827
+ agent: target.agentId,
4828
+ });
4829
+ }
4830
+ if (json) {
4831
+ console.log(JSON.stringify({
4832
+ agent: target.agentId,
4833
+ envSource: target.envSource,
4834
+ envFile: target.envFile,
4835
+ source,
4836
+ confidence,
4837
+ writes,
4838
+ }, null, 2));
4839
+ process.exit(0);
4840
+ }
4841
+ printHandoffResult(target, taskEntity, writes);
4842
+ console.log('');
4843
+ console.log(`${infoLabel()} Handoffs are shared-memory facts. Pair this with checkpoint() if the sender also needs agent-local recovery.`);
4844
+ process.exit(0);
4845
+ }
4626
4846
  function printClaudeSetupHelp() {
4627
4847
  console.log([
4628
4848
  'Scaffold Claude Code MCP and hook files for the current project.',
@@ -4921,6 +5141,7 @@ function printHelp() {
4921
5141
  ['iranti uninstall [--dry-run] [--yes] [--all] [--keep-data] [--keep-project-bindings] [--scan-root <dir[,dir2]>] [--json]', 'Remove Iranti packages and, with --all, runtime data and project integrations.'],
4922
5142
  ['iranti handshake [--instance <name> | --project-env <file>] [--agent <id>] [--task <text>] [--recent <msg1||msg2>] [--recent-file <path>] [--json]', 'Manually inspect Attendant handshake output.'],
4923
5143
  ['iranti attend [message] [--instance <name> | --project-env <file>] [--agent <id>] [--context <text> | --context-file <path>] [--entity-hint <entity>] [--force] [--max-facts <n>] [--json]', 'Manually inspect turn-level memory injection decisions.'],
5144
+ ['iranti handoff task/<task_id> [--instance <name> | --project-env <file>] [--agent <id>] --next-step <text> [--status <state>] [--owner <agent-id>] [--blockers <a||b>] [--artifacts <path1||path2>] [--project-entity <entity>] [--notes <text>] [--source <label>] [--confidence <n>] [--json]', 'Write a standardized shared-memory handoff for Claude/Codex collaboration.'],
4924
5145
  ['iranti chat [--agent <agent-id>] [--provider <provider>] [--model <model>]', 'Open the local interactive chat shell.'],
4925
5146
  ['iranti resolve [--dir <escalation-dir>]', 'Walk through pending escalation files.'],
4926
5147
  ]);
@@ -5147,6 +5368,10 @@ async function main() {
5147
5368
  await attendCommand(args);
5148
5369
  return;
5149
5370
  }
5371
+ if (args.command === 'handoff') {
5372
+ await handoffCommand(args);
5373
+ return;
5374
+ }
5150
5375
  if (args.command === 'chat') {
5151
5376
  await chatCommand(args);
5152
5377
  return;
@@ -144,7 +144,7 @@ async function main() {
144
144
  await ensureDefaultAgent(iranti);
145
145
  const server = new mcp_js_1.McpServer({
146
146
  name: 'iranti-mcp',
147
- version: '0.2.20',
147
+ version: '0.2.22',
148
148
  });
149
149
  server.registerTool('iranti_handshake', {
150
150
  description: `Initialize or refresh an agent's working-memory brief for the current task.
@@ -39,7 +39,7 @@ const INSTANCE_DIR = process.env.IRANTI_INSTANCE_DIR?.trim()
39
39
  const INSTANCE_RUNTIME_FILE = process.env.IRANTI_INSTANCE_RUNTIME_FILE?.trim()
40
40
  || (INSTANCE_DIR ? (0, runtimeLifecycle_1.runtimeFileForInstance)(INSTANCE_DIR) : null);
41
41
  const INSTANCE_NAME = process.env.IRANTI_INSTANCE_NAME?.trim() || (INSTANCE_DIR ? path_1.default.basename(INSTANCE_DIR) : 'adhoc');
42
- const VERSION = '0.2.20';
42
+ const VERSION = '0.2.22';
43
43
  try {
44
44
  fs_1.default.mkdirSync(path_1.default.dirname(REQUEST_LOG_FILE), { recursive: true });
45
45
  }
@@ -0,0 +1,3 @@
1
+ export declare function parsePublishedDockerHostPorts(output: string): Set<number>;
2
+ export declare function parseDockerContainerNames(output: string): string[];
3
+ //# sourceMappingURL=dockerCliParsing.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"dockerCliParsing.d.ts","sourceRoot":"","sources":["../../../src/lib/dockerCliParsing.ts"],"names":[],"mappings":"AAAA,wBAAgB,6BAA6B,CAAC,MAAM,EAAE,MAAM,GAAG,GAAG,CAAC,MAAM,CAAC,CAWzE;AAED,wBAAgB,yBAAyB,CAAC,MAAM,EAAE,MAAM,GAAG,MAAM,EAAE,CAKlE"}
@@ -0,0 +1,23 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.parsePublishedDockerHostPorts = parsePublishedDockerHostPorts;
4
+ exports.parseDockerContainerNames = parseDockerContainerNames;
5
+ function parsePublishedDockerHostPorts(output) {
6
+ const ports = new Set();
7
+ for (const line of output.split(/\r?\n/)) {
8
+ for (const match of line.matchAll(/(?:^|,\s*)(?:0\.0\.0\.0|127\.0\.0\.1|\[::\]|localhost):(\d+)->/g)) {
9
+ const parsed = Number.parseInt(match[1] ?? '', 10);
10
+ if (Number.isFinite(parsed) && parsed > 0) {
11
+ ports.add(parsed);
12
+ }
13
+ }
14
+ }
15
+ return ports;
16
+ }
17
+ function parseDockerContainerNames(output) {
18
+ return output
19
+ .split(/\r?\n/)
20
+ .map((value) => value.trim())
21
+ .filter(Boolean);
22
+ }
23
+ //# sourceMappingURL=dockerCliParsing.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"dockerCliParsing.js","sourceRoot":"","sources":["../../../src/lib/dockerCliParsing.ts"],"names":[],"mappings":";;AAAA,sEAWC;AAED,8DAKC;AAlBD,SAAgB,6BAA6B,CAAC,MAAc;IACxD,MAAM,KAAK,GAAG,IAAI,GAAG,EAAU,CAAC;IAChC,KAAK,MAAM,IAAI,IAAI,MAAM,CAAC,KAAK,CAAC,OAAO,CAAC,EAAE,CAAC;QACvC,KAAK,MAAM,KAAK,IAAI,IAAI,CAAC,QAAQ,CAAC,iEAAiE,CAAC,EAAE,CAAC;YACnG,MAAM,MAAM,GAAG,MAAM,CAAC,QAAQ,CAAC,KAAK,CAAC,CAAC,CAAC,IAAI,EAAE,EAAE,EAAE,CAAC,CAAC;YACnD,IAAI,MAAM,CAAC,QAAQ,CAAC,MAAM,CAAC,IAAI,MAAM,GAAG,CAAC,EAAE,CAAC;gBACxC,KAAK,CAAC,GAAG,CAAC,MAAM,CAAC,CAAC;YACtB,CAAC;QACL,CAAC;IACL,CAAC;IACD,OAAO,KAAK,CAAC;AACjB,CAAC;AAED,SAAgB,yBAAyB,CAAC,MAAc;IACpD,OAAO,MAAM;SACR,KAAK,CAAC,OAAO,CAAC;SACd,GAAG,CAAC,CAAC,KAAK,EAAE,EAAE,CAAC,KAAK,CAAC,IAAI,EAAE,CAAC;SAC5B,MAAM,CAAC,OAAO,CAAC,CAAC;AACzB,CAAC"}
@@ -1 +1 @@
1
- {"version":3,"file":"runtimeEnv.d.ts","sourceRoot":"","sources":["../../../src/lib/runtimeEnv.ts"],"names":[],"mappings":"AAIA,KAAK,iBAAiB,GAAG;IACrB,GAAG,CAAC,EAAE,MAAM,CAAC;IACb,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB,eAAe,CAAC,EAAE,MAAM,CAAC;IACzB,eAAe,CAAC,EAAE,MAAM,CAAC;CAC5B,CAAC;AAEF,KAAK,oBAAoB,GAAG;IACxB,WAAW,EAAE,MAAM,EAAE,CAAC;IACtB,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB,eAAe,CAAC,EAAE,MAAM,CAAC;CAC5B,CAAC;AAuDF,wBAAgB,cAAc,CAAC,OAAO,GAAE,iBAAsB,GAAG,oBAAoB,CAsCpF"}
1
+ {"version":3,"file":"runtimeEnv.d.ts","sourceRoot":"","sources":["../../../src/lib/runtimeEnv.ts"],"names":[],"mappings":"AAIA,KAAK,iBAAiB,GAAG;IACrB,GAAG,CAAC,EAAE,MAAM,CAAC;IACb,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB,eAAe,CAAC,EAAE,MAAM,CAAC;IACzB,eAAe,CAAC,EAAE,MAAM,CAAC;CAC5B,CAAC;AAEF,KAAK,oBAAoB,GAAG;IACxB,WAAW,EAAE,MAAM,EAAE,CAAC;IACtB,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB,eAAe,CAAC,EAAE,MAAM,CAAC;CAC5B,CAAC;AA4DF,wBAAgB,cAAc,CAAC,OAAO,GAAE,iBAAsB,GAAG,oBAAoB,CAuCpF"}
@@ -11,9 +11,10 @@ function parseEnvFile(filePath) {
11
11
  const raw = fs_1.default.readFileSync(filePath, 'utf-8');
12
12
  return dotenv_1.default.parse(raw);
13
13
  }
14
- function applyEnvVars(vars, initialEnvKeys) {
14
+ function applyEnvVars(vars, initialEnvKeys, options = {}) {
15
+ const { overrideExisting = false } = options;
15
16
  for (const [key, value] of Object.entries(vars)) {
16
- if (initialEnvKeys.has(key))
17
+ if (!overrideExisting && initialEnvKeys.has(key))
17
18
  continue;
18
19
  process.env[key] = value;
19
20
  }
@@ -61,7 +62,8 @@ function loadRuntimeEnv(options = {}) {
61
62
  const initialEnvKeys = new Set(Object.keys(process.env));
62
63
  const fallbackEnvFile = findFallbackEnvFile(options);
63
64
  if (fallbackEnvFile) {
64
- applyEnvVars(parseEnvFile(fallbackEnvFile), initialEnvKeys);
65
+ const explicitFallback = Boolean(options.explicitEnvFile?.trim() || process.env.IRANTI_ENV_FILE?.trim());
66
+ applyEnvVars(parseEnvFile(fallbackEnvFile), initialEnvKeys, { overrideExisting: explicitFallback });
65
67
  loadedFiles.push(fallbackEnvFile);
66
68
  }
67
69
  const projectEnvFile = findProjectEnvFile(options);
@@ -75,11 +77,11 @@ function loadRuntimeEnv(options = {}) {
75
77
  ? path_1.default.resolve(instanceEnvFile)
76
78
  : undefined;
77
79
  if (resolvedInstanceEnvFile) {
78
- applyEnvVars(parseEnvFile(resolvedInstanceEnvFile), initialEnvKeys);
80
+ applyEnvVars(parseEnvFile(resolvedInstanceEnvFile), initialEnvKeys, { overrideExisting: true });
79
81
  loadedFiles.push(resolvedInstanceEnvFile);
80
82
  }
81
83
  if (projectEnvFile && projectEnv) {
82
- applyEnvVars(projectEnv, initialEnvKeys);
84
+ applyEnvVars(projectEnv, initialEnvKeys, { overrideExisting: true });
83
85
  loadedFiles.push(projectEnvFile);
84
86
  }
85
87
  return {
@@ -1 +1 @@
1
- {"version":3,"file":"runtimeEnv.js","sourceRoot":"","sources":["../../../src/lib/runtimeEnv.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"runtimeEnv.js","sourceRoot":"","sources":["../../../src/lib/runtimeEnv.ts"],"names":[],"mappings":"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"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "iranti",
3
- "version": "0.2.20",
3
+ "version": "0.2.22",
4
4
  "description": "Memory infrastructure for multi-agent AI systems",
5
5
  "main": "dist/src/sdk/index.js",
6
6
  "files": [
@@ -71,6 +71,7 @@
71
71
  "test:vector-backends": "ts-node tests/vector-backends/run_vector_backend_tests.ts",
72
72
  "test:ts-client-smoke": "ts-node --project tests/typescript_client/tsconfig.json tests/typescript_client/smoke_test.ts",
73
73
  "test:consistency": "ts-node tests/consistency/run_consistency_tests.ts",
74
+ "test:cross-tool-handoff": "ts-node tests/cross-tool/run_cross_tool_handoff_tests.ts",
74
75
  "release:check": "ts-node scripts/check-release-version.ts",
75
76
  "release:bump": "ts-node scripts/bump-version.ts",
76
77
  "seed:policy": "ts-node scripts/seed_policy.ts",