synomem 0.6.10 → 0.7.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 (53) hide show
  1. package/dist/backend.d.ts.map +1 -1
  2. package/dist/backend.js +1 -0
  3. package/dist/backend.js.map +1 -1
  4. package/dist/cli.d.ts.map +1 -1
  5. package/dist/cli.js +139 -43
  6. package/dist/cli.js.map +1 -1
  7. package/dist/client.d.ts +22 -1
  8. package/dist/client.d.ts.map +1 -1
  9. package/dist/client.js +164 -3
  10. package/dist/client.js.map +1 -1
  11. package/dist/errors.d.ts +1 -1
  12. package/dist/errors.d.ts.map +1 -1
  13. package/dist/errors.js +2 -0
  14. package/dist/errors.js.map +1 -1
  15. package/dist/import.d.ts +83 -0
  16. package/dist/import.d.ts.map +1 -1
  17. package/dist/mcp/index.d.ts.map +1 -1
  18. package/dist/mcp/index.js +110 -2
  19. package/dist/mcp/index.js.map +1 -1
  20. package/dist/ports/repository.d.ts +10 -1
  21. package/dist/ports/repository.d.ts.map +1 -1
  22. package/dist/projections.d.ts.map +1 -1
  23. package/dist/projections.js +6 -0
  24. package/dist/projections.js.map +1 -1
  25. package/dist/remote.d.ts +22 -1
  26. package/dist/remote.d.ts.map +1 -1
  27. package/dist/remote.js +18 -4
  28. package/dist/remote.js.map +1 -1
  29. package/dist/schemas.d.ts +130 -0
  30. package/dist/schemas.d.ts.map +1 -1
  31. package/dist/schemas.js +77 -0
  32. package/dist/schemas.js.map +1 -1
  33. package/dist/service.d.ts +14 -1
  34. package/dist/service.d.ts.map +1 -1
  35. package/dist/storage.d.ts +12 -1
  36. package/dist/storage.d.ts.map +1 -1
  37. package/dist/storage.js +136 -19
  38. package/dist/storage.js.map +1 -1
  39. package/dist/types.d.ts +80 -1
  40. package/dist/types.d.ts.map +1 -1
  41. package/package.json +1 -1
  42. package/src/backend.ts +1 -0
  43. package/src/cli.ts +305 -91
  44. package/src/client.ts +184 -2
  45. package/src/errors.ts +2 -0
  46. package/src/mcp/index.ts +140 -1
  47. package/src/ports/repository.ts +8 -0
  48. package/src/projections.ts +6 -0
  49. package/src/remote.ts +65 -4
  50. package/src/schemas.ts +85 -0
  51. package/src/service.ts +18 -0
  52. package/src/storage.ts +167 -13
  53. package/src/types.ts +90 -2
package/src/cli.ts CHANGED
@@ -228,6 +228,7 @@ function itemListInput(options: Record<string, string | string[]>): ItemListInpu
228
228
  ...(typeof options.actor === 'string' ? { actorId: options.actor } : {}),
229
229
  ...(typeof options.status === 'string' ? { status: options.status } : {}),
230
230
  ...(typeof options.tag === 'string' ? { tag: options.tag } : {}),
231
+ ...(typeof options.topic === 'string' ? { topicId: options.topic } : {}),
231
232
  ...(typeof options.visibility === 'string'
232
233
  ? { visibility: options.visibility as ItemListInput['visibility'] }
233
234
  : {}),
@@ -293,8 +294,13 @@ async function withService<T>(
293
294
  home: string | undefined,
294
295
  configuredActor: ActorIdentity,
295
296
  operation: (client: SynomemService) => Promise<T>,
297
+ assertActor?: boolean,
296
298
  ): Promise<T> {
297
- const client = serviceFactory({ ...(home ? { home } : {}), actor: configuredActor });
299
+ const client = serviceFactory({
300
+ ...(home ? { home } : {}),
301
+ actor: configuredActor,
302
+ ...(assertActor !== undefined ? { assertActor } : {}),
303
+ });
298
304
  await client.init();
299
305
  try {
300
306
  return await operation(client);
@@ -309,6 +315,7 @@ function addListOptions(command: Command): Command {
309
315
  .option('--actor <id>')
310
316
  .option('--actor-kind <kind>', 'human, agent, or system')
311
317
  .option('--tag <tag>')
318
+ .option('--topic <id>', 'only records carrying this topic')
312
319
  .option('--status <status>', 'acknowledged or unacknowledged')
313
320
  .option('--visibility <visibility>', 'private, local, or public')
314
321
  .addOption(
@@ -327,6 +334,7 @@ function listInput(options: Record<string, string>): KudosListInput {
327
334
  ...(options.actor ? { actorId: options.actor } : {}),
328
335
  ...(options.actorKind ? { actorKind: options.actorKind as KudosListInput['actorKind'] } : {}),
329
336
  ...(options.tag ? { tag: options.tag } : {}),
337
+ ...(options.topic ? { topicId: options.topic } : {}),
330
338
  ...(options.status ? { status: options.status as KudosListInput['status'] } : {}),
331
339
  ...(options.visibility
332
340
  ? { visibility: options.visibility as KudosListInput['visibility'] }
@@ -452,7 +460,8 @@ export function createCli(
452
460
  home: string | undefined,
453
461
  configuredActor: ActorIdentity,
454
462
  operation: (client: SynomemService) => Promise<T>,
455
- ) => withService(serviceFactory, home, configuredActor, operation);
463
+ assertActor?: boolean,
464
+ ) => withService(serviceFactory, home, configuredActor, operation, assertActor);
456
465
  cliExitCodes.set(program, 0);
457
466
  program
458
467
  .name('synomem')
@@ -559,7 +568,11 @@ export function createCli(
559
568
  writeSynomemBackend({ kind: 'local' }, home);
560
569
  // Opening it once creates the database, so `list` does not report a
561
570
  // workspace that exists in name only.
562
- await withClient(home, defaultActor(env, 'system', 'cli'), async () => undefined);
571
+ await withClient(
572
+ home,
573
+ defaultActor(env, 'system', 'cli', global.actor),
574
+ async () => undefined,
575
+ );
563
576
  output(
564
577
  io,
565
578
  global.json,
@@ -698,18 +711,22 @@ export function createCli(
698
711
  if (persisted?.backend.kind === 'remote') {
699
712
  throw new SynomemError('INVALID_INPUT', 'The init command requires a local backend.');
700
713
  }
701
- await withClient(options.home, defaultActor(env, 'system', 'cli'), async (client) => {
702
- const info = await client.info();
703
- if (info.backend !== 'local') {
704
- throw new SynomemError('INVALID_INPUT', 'The init command requires a local backend.');
705
- }
706
- output(
707
- io,
708
- options.json,
709
- { home: info.home, database: info.databasePath },
710
- `Initialized Synomem at ${info.home}`,
711
- );
712
- });
714
+ await withClient(
715
+ options.home,
716
+ defaultActor(env, 'system', 'cli', options.actor),
717
+ async (client) => {
718
+ const info = await client.info();
719
+ if (info.backend !== 'local') {
720
+ throw new SynomemError('INVALID_INPUT', 'The init command requires a local backend.');
721
+ }
722
+ output(
723
+ io,
724
+ options.json,
725
+ { home: info.home, database: info.databasePath },
726
+ `Initialized Synomem at ${info.home}`,
727
+ );
728
+ },
729
+ );
713
730
  });
714
731
 
715
732
  /*
@@ -1160,7 +1177,7 @@ export function createCli(
1160
1177
  }
1161
1178
  const result = await withClient(
1162
1179
  global.home,
1163
- defaultActor(env, 'system', 'cli'),
1180
+ defaultActor(env, 'system', 'cli', global.actor),
1164
1181
  async (client) => ({
1165
1182
  info: await client.info(),
1166
1183
  capabilities: await client.capabilities(),
@@ -1198,15 +1215,19 @@ export function createCli(
1198
1215
  .description('Report whether the generated files match the canonical events')
1199
1216
  .action(async (_options, command: Command) => {
1200
1217
  const global = globals(command);
1201
- const status = await withClient(global.home, defaultActor(env, 'system', 'cli'), (client) => {
1202
- if (!client.projectionStatus) {
1203
- throw new SynomemError(
1204
- 'INVALID_INPUT',
1205
- 'The remote backend keeps no filesystem projections, so there is nothing to report.',
1206
- );
1207
- }
1208
- return client.projectionStatus();
1209
- });
1218
+ const status = await withClient(
1219
+ global.home,
1220
+ defaultActor(env, 'system', 'cli', global.actor),
1221
+ (client) => {
1222
+ if (!client.projectionStatus) {
1223
+ throw new SynomemError(
1224
+ 'INVALID_INPUT',
1225
+ 'The remote backend keeps no filesystem projections, so there is nothing to report.',
1226
+ );
1227
+ }
1228
+ return client.projectionStatus();
1229
+ },
1230
+ );
1210
1231
  const enabled = Object.entries(status.settings)
1211
1232
  .filter(([, on]) => on)
1212
1233
  .map(([name]) => name);
@@ -1373,7 +1394,7 @@ export function createCli(
1373
1394
  const global = globals(command);
1374
1395
  const profile = await withClient(
1375
1396
  global.home,
1376
- defaultActor(env, 'system', 'cli'),
1397
+ defaultActor(env, 'system', 'cli', global.actor),
1377
1398
  (client) =>
1378
1399
  client.agents.create({
1379
1400
  handle,
@@ -1402,8 +1423,10 @@ export function createCli(
1402
1423
  .description('Add aliases, keeping the ones already there')
1403
1424
  .action(async (agent: string, aliases: string[], _options, command: Command) => {
1404
1425
  const global = globals(command);
1405
- const profile = await withClient(global.home, defaultActor(env, 'system', 'cli'), (client) =>
1406
- client.agents.addAliases(agent, aliases),
1426
+ const profile = await withClient(
1427
+ global.home,
1428
+ defaultActor(env, 'system', 'cli', global.actor),
1429
+ (client) => client.agents.addAliases(agent, aliases),
1407
1430
  );
1408
1431
  output(io, global.json, profile, `Aliases: ${(profile.aliases ?? []).join(', ') || 'none'}`);
1409
1432
  });
@@ -1413,8 +1436,10 @@ export function createCli(
1413
1436
  .description('Remove aliases, keeping the rest')
1414
1437
  .action(async (agent: string, aliases: string[], _options, command: Command) => {
1415
1438
  const global = globals(command);
1416
- const profile = await withClient(global.home, defaultActor(env, 'system', 'cli'), (client) =>
1417
- client.agents.removeAliases(agent, aliases),
1439
+ const profile = await withClient(
1440
+ global.home,
1441
+ defaultActor(env, 'system', 'cli', global.actor),
1442
+ (client) => client.agents.removeAliases(agent, aliases),
1418
1443
  );
1419
1444
  output(io, global.json, profile, `Aliases: ${(profile.aliases ?? []).join(', ') || 'none'}`);
1420
1445
  });
@@ -1424,8 +1449,10 @@ export function createCli(
1424
1449
  .description('Change an agent handle. Its canonical ID never changes.')
1425
1450
  .action(async (agent: string, handle: string, _options, command: Command) => {
1426
1451
  const global = globals(command);
1427
- const profile = await withClient(global.home, defaultActor(env, 'system', 'cli'), (client) =>
1428
- client.agents.update(agent, { handle }),
1452
+ const profile = await withClient(
1453
+ global.home,
1454
+ defaultActor(env, 'system', 'cli', global.actor),
1455
+ (client) => client.agents.update(agent, { handle }),
1429
1456
  );
1430
1457
  output(
1431
1458
  io,
@@ -1440,8 +1467,10 @@ export function createCli(
1440
1467
  .description('Stop an agent acting, keeping its records and history')
1441
1468
  .action(async (agent: string, _options, command: Command) => {
1442
1469
  const global = globals(command);
1443
- const profile = await withClient(global.home, defaultActor(env, 'system', 'cli'), (client) =>
1444
- client.agents.archive(agent),
1470
+ const profile = await withClient(
1471
+ global.home,
1472
+ defaultActor(env, 'system', 'cli', global.actor),
1473
+ (client) => client.agents.archive(agent),
1445
1474
  );
1446
1475
  output(io, global.json, profile, `Archived ${profile.handle} (${profile.id})`);
1447
1476
  });
@@ -1451,12 +1480,145 @@ export function createCli(
1451
1480
  .description('Let an archived agent act again')
1452
1481
  .action(async (agent: string, _options, command: Command) => {
1453
1482
  const global = globals(command);
1454
- const profile = await withClient(global.home, defaultActor(env, 'system', 'cli'), (client) =>
1455
- client.agents.restore(agent),
1483
+ const profile = await withClient(
1484
+ global.home,
1485
+ defaultActor(env, 'system', 'cli', global.actor),
1486
+ (client) => client.agents.restore(agent),
1456
1487
  );
1457
1488
  output(io, global.json, profile, `Restored ${profile.handle} (${profile.id})`);
1458
1489
  });
1459
1490
 
1491
+ const topicCommand = program
1492
+ .command('topic')
1493
+ .description('Create and manage topics — a stable, reusable subject any record can carry');
1494
+
1495
+ topicCommand
1496
+ .command('create <display-name>')
1497
+ .description('Create a topic. Any actor may create one.')
1498
+ .option('--alias <name>', 'alias (repeatable)', collect, [])
1499
+ .action(async (displayName: string, options: { alias: string[] }, command: Command) => {
1500
+ const global = globals(command);
1501
+ const topic = await withClient(
1502
+ global.home,
1503
+ defaultActor(env, 'system', 'cli', global.actor),
1504
+ (client) =>
1505
+ client.topics.create({
1506
+ displayName,
1507
+ ...(options.alias.length ? { aliases: options.alias } : {}),
1508
+ }),
1509
+ );
1510
+ output(io, global.json, topic, `Created ${topic.displayName}\n\nTopic ID: ${topic.id}`);
1511
+ });
1512
+
1513
+ topicCommand
1514
+ .command('list')
1515
+ .description('List known topics')
1516
+ .option('--status <status>', 'active or archived')
1517
+ .action(async (options: { status?: string }, command: Command) => {
1518
+ const global = globals(command);
1519
+ const topics = await withClient(
1520
+ global.home,
1521
+ defaultActor(env, 'system', 'cli', global.actor),
1522
+ (client) =>
1523
+ client.topics.list(
1524
+ options.status ? { status: options.status as 'active' | 'archived' } : {},
1525
+ ),
1526
+ );
1527
+ const human = topics.length
1528
+ ? topics
1529
+ .map(
1530
+ (topic) =>
1531
+ `${topic.displayName}${topic.status === 'archived' ? ' [archived]' : ''}${
1532
+ topic.aliases?.length ? ` aliases: ${topic.aliases.join(', ')}` : ''
1533
+ }\n ${topic.id}`,
1534
+ )
1535
+ .join('\n')
1536
+ : 'No topics yet.';
1537
+ output(io, global.json, { topics }, human);
1538
+ });
1539
+
1540
+ topicCommand
1541
+ .command('show <id>')
1542
+ .description('Show one topic, resolving aliases')
1543
+ .action(async (id: string, _options, command: Command) => {
1544
+ const global = globals(command);
1545
+ const topic = await withClient(
1546
+ global.home,
1547
+ defaultActor(env, 'system', 'cli', global.actor),
1548
+ (client) => client.topics.get(id),
1549
+ );
1550
+ output(
1551
+ io,
1552
+ global.json,
1553
+ topic,
1554
+ `${topic.displayName}\n\nTopic ID: ${topic.id}\nStatus: ${topic.status}\nAliases: ${topic.aliases?.join(', ') ?? 'none'}`,
1555
+ );
1556
+ });
1557
+
1558
+ topicCommand
1559
+ .command('resolve <name>')
1560
+ .description('Resolve a name or alias to one topic, or list the candidates')
1561
+ .action(async (name: string, _options, command: Command) => {
1562
+ const global = globals(command);
1563
+ const resolution = await withClient(
1564
+ global.home,
1565
+ defaultActor(env, 'system', 'cli', global.actor),
1566
+ (client) => client.topics.resolve(name),
1567
+ );
1568
+ const human = resolution.match
1569
+ ? `${resolution.match.displayName} (${resolution.match.id})`
1570
+ : resolution.candidates.length
1571
+ ? `"${resolution.query}" is ambiguous. Candidates:\n${resolution.candidates
1572
+ .map((topic) => ` ${topic.id} ${topic.displayName}`)
1573
+ .join('\n')}`
1574
+ : `No topic answers to "${resolution.query}".`;
1575
+ output(io, global.json, resolution, human);
1576
+ });
1577
+
1578
+ topicCommand
1579
+ .command('rename <id> <display-name>')
1580
+ .description("Change a topic's display name. Its ID never changes.")
1581
+ .action(async (id: string, displayName: string, _options, command: Command) => {
1582
+ const global = globals(command);
1583
+ const topic = await withClient(
1584
+ global.home,
1585
+ defaultActor(env, 'system', 'cli', global.actor),
1586
+ (client) => client.topics.update(id, { displayName }),
1587
+ );
1588
+ output(
1589
+ io,
1590
+ global.json,
1591
+ topic,
1592
+ `Renamed to ${topic.displayName}\nTopic ID: ${topic.id} (unchanged)`,
1593
+ );
1594
+ });
1595
+
1596
+ topicCommand
1597
+ .command('archive <id>')
1598
+ .description('Stop a topic being attached to new records, keeping the ones it already has')
1599
+ .action(async (id: string, _options, command: Command) => {
1600
+ const global = globals(command);
1601
+ const topic = await withClient(
1602
+ global.home,
1603
+ defaultActor(env, 'system', 'cli', global.actor),
1604
+ (client) => client.topics.archive(id),
1605
+ );
1606
+ output(io, global.json, topic, `Archived ${topic.displayName} (${topic.id})`);
1607
+ });
1608
+
1609
+ topicCommand
1610
+ .command('restore <id>')
1611
+ .description('Let an archived topic be attached to new records again')
1612
+ .action(async (id: string, _options, command: Command) => {
1613
+ const global = globals(command);
1614
+ const topic = await withClient(
1615
+ global.home,
1616
+ defaultActor(env, 'system', 'cli', global.actor),
1617
+ (client) => client.topics.restore(id),
1618
+ );
1619
+ output(io, global.json, topic, `Restored ${topic.displayName} (${topic.id})`);
1620
+ });
1621
+
1460
1622
  const skillCommand = program
1461
1623
  .command('skill')
1462
1624
  .description('Install and maintain the packaged agent skill');
@@ -1493,7 +1655,7 @@ export function createCli(
1493
1655
  if (options.agent) {
1494
1656
  agentId = await withClient(
1495
1657
  global.home,
1496
- defaultActor(env, 'system', 'cli'),
1658
+ defaultActor(env, 'system', 'cli', global.actor),
1497
1659
  async (client) => {
1498
1660
  const resolution = await client.agents.resolve(options.agent!);
1499
1661
  if (!resolution.match) {
@@ -1582,8 +1744,10 @@ export function createCli(
1582
1744
  .description('List known agent identities')
1583
1745
  .action(async (_options, command: Command) => {
1584
1746
  const global = globals(command);
1585
- const agents = await withClient(global.home, defaultActor(env, 'system', 'cli'), (client) =>
1586
- client.agents.list(),
1747
+ const agents = await withClient(
1748
+ global.home,
1749
+ defaultActor(env, 'system', 'cli', global.actor),
1750
+ (client) => client.agents.list(),
1587
1751
  );
1588
1752
  const human = agents.length
1589
1753
  ? agents
@@ -1605,8 +1769,10 @@ export function createCli(
1605
1769
  .description('Show one agent profile, resolving aliases')
1606
1770
  .action(async (id: string, _options, command: Command) => {
1607
1771
  const global = globals(command);
1608
- const profile = await withClient(global.home, defaultActor(env, 'system', 'cli'), (client) =>
1609
- client.agents.get(id),
1772
+ const profile = await withClient(
1773
+ global.home,
1774
+ defaultActor(env, 'system', 'cli', global.actor),
1775
+ (client) => client.agents.get(id),
1610
1776
  );
1611
1777
  output(
1612
1778
  io,
@@ -1633,7 +1799,7 @@ export function createCli(
1633
1799
  const hasAliases = options.clearAliases || options.alias.length > 0;
1634
1800
  const profile = await withClient(
1635
1801
  global.home,
1636
- defaultActor(env, 'system', 'cli'),
1802
+ defaultActor(env, 'system', 'cli', global.actor),
1637
1803
  (client) =>
1638
1804
  client.agents.update(id, {
1639
1805
  ...(options.name ? { displayName: options.name } : {}),
@@ -1652,7 +1818,7 @@ export function createCli(
1652
1818
  const global = globals(command);
1653
1819
  const resolution = await withClient(
1654
1820
  global.home,
1655
- defaultActor(env, 'system', 'cli'),
1821
+ defaultActor(env, 'system', 'cli', global.actor),
1656
1822
  (client) => client.agents.resolve(name),
1657
1823
  );
1658
1824
  // An ambiguous name is a question, not a failure: exit zero and show the
@@ -1672,8 +1838,10 @@ export function createCli(
1672
1838
  .description('List agents with their runtime bindings')
1673
1839
  .action(async (_options, command: Command) => {
1674
1840
  const global = globals(command);
1675
- const entries = await withClient(global.home, defaultActor(env, 'system', 'cli'), (client) =>
1676
- client.agents.directory(),
1841
+ const entries = await withClient(
1842
+ global.home,
1843
+ defaultActor(env, 'system', 'cli', global.actor),
1844
+ (client) => client.agents.directory(),
1677
1845
  );
1678
1846
  const human = entries.length
1679
1847
  ? entries
@@ -1713,7 +1881,7 @@ export function createCli(
1713
1881
  const global = globals(command);
1714
1882
  const binding = await withClient(
1715
1883
  global.home,
1716
- defaultActor(env, 'system', 'cli'),
1884
+ defaultActor(env, 'system', 'cli', global.actor),
1717
1885
  (client) =>
1718
1886
  client.agents.bindRuntime({
1719
1887
  agentId: agent,
@@ -1744,7 +1912,7 @@ export function createCli(
1744
1912
  const global = globals(command);
1745
1913
  const result = await withClient(
1746
1914
  global.home,
1747
- defaultActor(env, 'system', 'cli'),
1915
+ defaultActor(env, 'system', 'cli', global.actor),
1748
1916
  async (client) => {
1749
1917
  if (agent) {
1750
1918
  const profile = await client.agents.get(agent);
@@ -1777,8 +1945,10 @@ export function createCli(
1777
1945
  .description('Remove a runtime binding')
1778
1946
  .action(async (bindingId: string, _options, command: Command) => {
1779
1947
  const global = globals(command);
1780
- const removed = await withClient(global.home, defaultActor(env, 'system', 'cli'), (client) =>
1781
- client.agents.unbindRuntime(bindingId),
1948
+ const removed = await withClient(
1949
+ global.home,
1950
+ defaultActor(env, 'system', 'cli', global.actor),
1951
+ (client) => client.agents.unbindRuntime(bindingId),
1782
1952
  );
1783
1953
  output(
1784
1954
  io,
@@ -1798,6 +1968,7 @@ export function createCli(
1798
1968
  .requiredOption('--title <title>')
1799
1969
  .requiredOption('--body <body>')
1800
1970
  .option('--tag <tag>', 'repeatable', collect, [])
1971
+ .option('--topic <id>', 'topic ID (repeatable)', collect, [])
1801
1972
  .option('--reply-to <post-id>')
1802
1973
  .action(
1803
1974
  async (
@@ -1807,6 +1978,7 @@ export function createCli(
1807
1978
  title: string;
1808
1979
  body: string;
1809
1980
  tag: string[];
1981
+ topic: string[];
1810
1982
  replyTo?: string;
1811
1983
  },
1812
1984
  command: Command,
@@ -1820,6 +1992,7 @@ export function createCli(
1820
1992
  title: options.title,
1821
1993
  body: options.body,
1822
1994
  ...(options.tag.length ? { tags: options.tag } : {}),
1995
+ ...(options.topic.length ? { topicIds: options.topic } : {}),
1823
1996
  ...(options.replyTo ? { replyTo: options.replyTo } : {}),
1824
1997
  }),
1825
1998
  );
@@ -1969,6 +2142,7 @@ export function createCli(
1969
2142
  .requiredOption('--title <title>')
1970
2143
  .requiredOption('--reason <reason>')
1971
2144
  .option('--tag <tag>', 'tag (repeatable)', collect, [])
2145
+ .option('--topic <id>', 'topic ID (repeatable)', collect, [])
1972
2146
  .option('--evidence <kind:value>', 'sanitized evidence (repeatable)', collect, [])
1973
2147
  .option('--visibility <visibility>', 'private, local, or public', 'workspace')
1974
2148
  .option('--idempotency-key <key>')
@@ -1982,6 +2156,7 @@ export function createCli(
1982
2156
  title: string;
1983
2157
  reason: string;
1984
2158
  tag: string[];
2159
+ topic: string[];
1985
2160
  evidence: string[];
1986
2161
  visibility: 'private' | 'workspace' | 'public';
1987
2162
  idempotencyKey?: string;
@@ -1999,6 +2174,7 @@ export function createCli(
1999
2174
  reason: options.reason,
2000
2175
  visibility: options.visibility,
2001
2176
  ...(options.tag.length ? { tags: options.tag } : {}),
2177
+ ...(options.topic.length ? { topicIds: options.topic } : {}),
2002
2178
  ...(options.evidence.length ? { evidence: options.evidence.map(parseEvidence) } : {}),
2003
2179
  ...(options.idempotencyKey ? { idempotencyKey: options.idempotencyKey } : {}),
2004
2180
  }),
@@ -2055,8 +2231,9 @@ export function createCli(
2055
2231
  const global = globals(command);
2056
2232
  const page = await withClient(
2057
2233
  global.home,
2058
- defaultActor(env, 'human', 'local-cli'),
2234
+ defaultActor(env, 'human', 'local-cli', global.actor),
2059
2235
  (client) => client.kudos.list(listInput(options)),
2236
+ Boolean(global.actor),
2060
2237
  );
2061
2238
  output(
2062
2239
  io,
@@ -2076,6 +2253,7 @@ export function createCli(
2076
2253
  .option('--participant <agent>')
2077
2254
  .option('--actor <id>')
2078
2255
  .option('--tag <tag>')
2256
+ .option('--topic <id>', 'only records carrying this topic')
2079
2257
  .option('--status <status>')
2080
2258
  .option('--visibility <visibility>', 'private, workspace, or public')
2081
2259
  .option('--limit <number>', 'maximum results (default 10, maximum 50)', '10')
@@ -2085,8 +2263,9 @@ export function createCli(
2085
2263
  const global = globals(command);
2086
2264
  const page = await withClient(
2087
2265
  global.home,
2088
- defaultActor(env, 'human', 'local-cli'),
2266
+ defaultActor(env, 'human', 'local-cli', global.actor),
2089
2267
  (client) => client.items.list(itemListInput(options)),
2268
+ Boolean(global.actor),
2090
2269
  );
2091
2270
  output(
2092
2271
  io,
@@ -2109,13 +2288,14 @@ export function createCli(
2109
2288
  const global = globals(command);
2110
2289
  const page = await withClient(
2111
2290
  global.home,
2112
- defaultActor(env, 'human', 'local-cli'),
2291
+ defaultActor(env, 'human', 'local-cli', global.actor),
2113
2292
  (client) =>
2114
2293
  client.items.changes({
2115
2294
  limit: Number(options.limit),
2116
2295
  ...(options.kind.length ? { kinds: options.kind as ItemListInput['kinds'] } : {}),
2117
2296
  ...(options.after ? { after: options.after } : {}),
2118
2297
  }),
2298
+ Boolean(global.actor),
2119
2299
  );
2120
2300
  const human = page.items.length
2121
2301
  ? `${page.items
@@ -2138,6 +2318,7 @@ export function createCli(
2138
2318
  .requiredOption('--subject <subject>')
2139
2319
  .requiredOption('--body <body>')
2140
2320
  .option('--tag <tag>', 'tag (repeatable)', collect, [])
2321
+ .option('--topic <id>', 'topic ID (repeatable)', collect, [])
2141
2322
  .option('--visibility <visibility>', 'private, workspace, or public', 'workspace')
2142
2323
  .option('--idempotency-key <key>')
2143
2324
  .action(
@@ -2150,6 +2331,7 @@ export function createCli(
2150
2331
  subject: string;
2151
2332
  body: string;
2152
2333
  tag: string[];
2334
+ topic: string[];
2153
2335
  visibility: 'private' | 'workspace' | 'public';
2154
2336
  idempotencyKey?: string;
2155
2337
  },
@@ -2166,6 +2348,7 @@ export function createCli(
2166
2348
  body: options.body,
2167
2349
  visibility: options.visibility,
2168
2350
  ...(options.tag.length ? { tags: options.tag } : {}),
2351
+ ...(options.topic.length ? { topicIds: options.topic } : {}),
2169
2352
  ...(options.idempotencyKey ? { idempotencyKey: options.idempotencyKey } : {}),
2170
2353
  }),
2171
2354
  );
@@ -2191,7 +2374,7 @@ export function createCli(
2191
2374
  const global = globals(command);
2192
2375
  const page = await withClient(
2193
2376
  global.home,
2194
- defaultActor(env, 'human', 'local-cli'),
2377
+ defaultActor(env, 'human', 'local-cli', global.actor),
2195
2378
  (client) =>
2196
2379
  client.memos.list({
2197
2380
  ...(options.participant ? { participantAgentId: options.participant } : {}),
@@ -2199,6 +2382,7 @@ export function createCli(
2199
2382
  limit: Number(options.limit),
2200
2383
  ...(options.cursor ? { cursor: options.cursor } : {}),
2201
2384
  }),
2385
+ Boolean(global.actor),
2202
2386
  );
2203
2387
  output(io, global.json, page, page.items.map(lineForItem).join('\n') || 'No memos found.');
2204
2388
  },
@@ -2207,8 +2391,9 @@ export function createCli(
2207
2391
  const global = globals(command);
2208
2392
  const record = await withClient(
2209
2393
  global.home,
2210
- defaultActor(env, 'human', 'local-cli'),
2394
+ defaultActor(env, 'human', 'local-cli', global.actor),
2211
2395
  (client) => client.memos.get(id),
2396
+ Boolean(global.actor),
2212
2397
  );
2213
2398
  output(
2214
2399
  io,
@@ -2255,6 +2440,7 @@ export function createCli(
2255
2440
  .requiredOption('--title <title>')
2256
2441
  .requiredOption('--body <body>')
2257
2442
  .option('--tag <tag>', 'tag (repeatable)', collect, [])
2443
+ .option('--topic <id>', 'topic ID (repeatable)', collect, [])
2258
2444
  .option('--idempotency-key <key>')
2259
2445
  .action(
2260
2446
  async (
@@ -2265,6 +2451,7 @@ export function createCli(
2265
2451
  title: string;
2266
2452
  body: string;
2267
2453
  tag: string[];
2454
+ topic: string[];
2268
2455
  idempotencyKey?: string;
2269
2456
  },
2270
2457
  command: Command,
@@ -2279,6 +2466,7 @@ export function createCli(
2279
2466
  title: options.title,
2280
2467
  body: options.body,
2281
2468
  ...(options.tag.length ? { tags: options.tag } : {}),
2469
+ ...(options.topic.length ? { topicIds: options.topic } : {}),
2282
2470
  ...(options.idempotencyKey ? { idempotencyKey: options.idempotencyKey } : {}),
2283
2471
  }),
2284
2472
  );
@@ -2300,13 +2488,14 @@ export function createCli(
2300
2488
  const global = globals(command);
2301
2489
  const page = await withClient(
2302
2490
  global.home,
2303
- defaultActor(env, 'human', 'local-cli'),
2491
+ defaultActor(env, 'human', 'local-cli', global.actor),
2304
2492
  (client) =>
2305
2493
  client.notes.list({
2306
2494
  ...(options.owner ? { participantAgentId: options.owner } : {}),
2307
2495
  ...(options.status ? { status: options.status } : {}),
2308
2496
  limit: Number(options.limit),
2309
2497
  }),
2498
+ Boolean(global.actor),
2310
2499
  );
2311
2500
  output(io, global.json, page, page.items.map(lineForItem).join('\n') || 'No notes found.');
2312
2501
  },
@@ -2315,8 +2504,9 @@ export function createCli(
2315
2504
  const global = globals(command);
2316
2505
  const record = await withClient(
2317
2506
  global.home,
2318
- defaultActor(env, 'human', 'local-cli'),
2507
+ defaultActor(env, 'human', 'local-cli', global.actor),
2319
2508
  (client) => client.notes.get(id),
2509
+ Boolean(global.actor),
2320
2510
  );
2321
2511
  output(
2322
2512
  io,
@@ -2404,6 +2594,7 @@ export function createCli(
2404
2594
  .option('--due-at <datetime>')
2405
2595
  .option('--time-zone <iana-zone>')
2406
2596
  .option('--tag <tag>', 'tag (repeatable)', collect, [])
2597
+ .option('--topic <id>', 'topic ID (repeatable)', collect, [])
2407
2598
  .option('--idempotency-key <key>')
2408
2599
  .action(
2409
2600
  async (
@@ -2417,6 +2608,7 @@ export function createCli(
2417
2608
  dueAt?: string;
2418
2609
  timeZone?: string;
2419
2610
  tag: string[];
2611
+ topic: string[];
2420
2612
  idempotencyKey?: string;
2421
2613
  },
2422
2614
  command: Command,
@@ -2432,6 +2624,7 @@ export function createCli(
2432
2624
  priority: Number(options.priority) as 1 | 2 | 3 | 4,
2433
2625
  ...((due) => (due ? { due } : {}))(taskDue(options)),
2434
2626
  ...(options.tag.length ? { tags: options.tag } : {}),
2627
+ ...(options.topic.length ? { topicIds: options.topic } : {}),
2435
2628
  ...(options.idempotencyKey ? { idempotencyKey: options.idempotencyKey } : {}),
2436
2629
  }),
2437
2630
  );
@@ -2557,6 +2750,7 @@ export function createCli(
2557
2750
  .option('--due-at <datetime>')
2558
2751
  .option('--time-zone <iana-zone>')
2559
2752
  .option('--tag <tag>', 'tag (repeatable)', collect, [])
2753
+ .option('--topic <id>', 'topic ID (repeatable)', collect, [])
2560
2754
  .option('--visibility <visibility>', 'private, workspace, or public', 'workspace')
2561
2755
  .option('--idempotency-key <key>')
2562
2756
  .action(
@@ -2572,6 +2766,7 @@ export function createCli(
2572
2766
  dueAt?: string;
2573
2767
  timeZone?: string;
2574
2768
  tag: string[];
2769
+ topic: string[];
2575
2770
  visibility: 'private' | 'workspace' | 'public';
2576
2771
  idempotencyKey?: string;
2577
2772
  },
@@ -2589,6 +2784,7 @@ export function createCli(
2589
2784
  priority: Number(options.priority) as 1 | 2 | 3 | 4,
2590
2785
  ...((due) => (due ? { due } : {}))(taskDue(options)),
2591
2786
  ...(options.tag.length ? { tags: options.tag } : {}),
2787
+ ...(options.topic.length ? { topicIds: options.topic } : {}),
2592
2788
  visibility: options.visibility,
2593
2789
  ...(options.idempotencyKey ? { idempotencyKey: options.idempotencyKey } : {}),
2594
2790
  }),
@@ -2611,13 +2807,14 @@ export function createCli(
2611
2807
  const global = globals(command);
2612
2808
  const page = await withClient(
2613
2809
  global.home,
2614
- defaultActor(env, 'human', 'local-cli'),
2810
+ defaultActor(env, 'human', 'local-cli', global.actor),
2615
2811
  (client) =>
2616
2812
  client.tasks.list({
2617
2813
  ...(options.assignee ? { participantAgentId: options.assignee } : {}),
2618
2814
  ...(options.status ? { status: options.status } : {}),
2619
2815
  limit: Number(options.limit),
2620
2816
  }),
2817
+ Boolean(global.actor),
2621
2818
  );
2622
2819
  output(io, global.json, page, page.items.map(lineForItem).join('\n') || 'No tasks found.');
2623
2820
  },
@@ -2626,8 +2823,9 @@ export function createCli(
2626
2823
  const global = globals(command);
2627
2824
  const record = await withClient(
2628
2825
  global.home,
2629
- defaultActor(env, 'human', 'local-cli'),
2826
+ defaultActor(env, 'human', 'local-cli', global.actor),
2630
2827
  (client) => client.tasks.get(id),
2828
+ Boolean(global.actor),
2631
2829
  );
2632
2830
  output(
2633
2831
  io,
@@ -2765,8 +2963,10 @@ export function createCli(
2765
2963
  .description('Show one kudos item and its current state')
2766
2964
  .action(async (id: string, _options, command: Command) => {
2767
2965
  const global = globals(command);
2768
- const record = await withClient(global.home, defaultActor(env, 'system', 'cli'), (client) =>
2769
- client.kudos.get(id),
2966
+ const record = await withClient(
2967
+ global.home,
2968
+ defaultActor(env, 'system', 'cli', global.actor),
2969
+ (client) => client.kudos.get(id),
2770
2970
  );
2771
2971
  output(io, global.json, record, showRecord(record));
2772
2972
  });
@@ -2841,7 +3041,7 @@ export function createCli(
2841
3041
  if (!agentId) throw new SynomemError('INVALID_INPUT', 'Specify an agent.');
2842
3042
  const details = await withClient(
2843
3043
  global.home,
2844
- defaultActor(env, 'system', 'cli'),
3044
+ defaultActor(env, 'system', 'cli', global.actor),
2845
3045
  async (client) => {
2846
3046
  const profile = await client.agents.get(agentId);
2847
3047
  const info = await client.info();
@@ -2885,8 +3085,10 @@ export function createCli(
2885
3085
  kudosCommand.command('stats').description('Show aggregate kudos statistics'),
2886
3086
  ).action(async (options: Record<string, string>, command: Command) => {
2887
3087
  const global = globals(command);
2888
- const stats = await withClient(global.home, defaultActor(env, 'system', 'cli'), (client) =>
2889
- client.stats(listInput(options)),
3088
+ const stats = await withClient(
3089
+ global.home,
3090
+ defaultActor(env, 'system', 'cli', global.actor),
3091
+ (client) => client.stats(listInput(options)),
2890
3092
  );
2891
3093
  output(
2892
3094
  io,
@@ -2901,8 +3103,10 @@ export function createCli(
2901
3103
  .description('Regenerate current-state and filesystem projections from canonical events')
2902
3104
  .action(async (_options, command: Command) => {
2903
3105
  const global = globals(command);
2904
- const result = await withClient(global.home, defaultActor(env, 'system', 'cli'), (client) =>
2905
- client.rebuild(),
3106
+ const result = await withClient(
3107
+ global.home,
3108
+ defaultActor(env, 'system', 'cli', global.actor),
3109
+ (client) => client.rebuild(),
2906
3110
  );
2907
3111
  output(
2908
3112
  io,
@@ -2917,15 +3121,19 @@ export function createCli(
2917
3121
  .description('Create a transactionally consistent SQLite backup')
2918
3122
  .action(async (destination: string, _options, command: Command) => {
2919
3123
  const global = globals(command);
2920
- const path = await withClient(global.home, defaultActor(env, 'system', 'cli'), (client) => {
2921
- if (!client.backup) {
2922
- throw new SynomemError(
2923
- 'INVALID_INPUT',
2924
- 'Filesystem backup is available only with the local backend.',
2925
- );
2926
- }
2927
- return client.backup(destination);
2928
- });
3124
+ const path = await withClient(
3125
+ global.home,
3126
+ defaultActor(env, 'system', 'cli', global.actor),
3127
+ (client) => {
3128
+ if (!client.backup) {
3129
+ throw new SynomemError(
3130
+ 'INVALID_INPUT',
3131
+ 'Filesystem backup is available only with the local backend.',
3132
+ );
3133
+ }
3134
+ return client.backup(destination);
3135
+ },
3136
+ );
2929
3137
  output(io, global.json, { path }, `Created backup at ${path}`);
2930
3138
  });
2931
3139
 
@@ -2944,7 +3152,7 @@ export function createCli(
2944
3152
  const global = globals(command);
2945
3153
  const content = await withClient(
2946
3154
  global.home,
2947
- defaultActor(env, 'system', 'cli'),
3155
+ defaultActor(env, 'system', 'cli', global.actor),
2948
3156
  (client) => client.export(options.format),
2949
3157
  );
2950
3158
  if (options.output) {
@@ -2967,8 +3175,10 @@ export function createCli(
2967
3175
  .description('Run safe diagnostics')
2968
3176
  .action(async (_options, command: Command) => {
2969
3177
  const global = globals(command);
2970
- const result = await withClient(global.home, defaultActor(env, 'system', 'cli'), (client) =>
2971
- client.doctor(),
3178
+ const result = await withClient(
3179
+ global.home,
3180
+ defaultActor(env, 'system', 'cli', global.actor),
3181
+ (client) => client.doctor(),
2972
3182
  );
2973
3183
  const human = result.diagnostics
2974
3184
  .map((item) => `${item.level.toUpperCase().padEnd(7)} ${item.code}: ${item.message}`)
@@ -2994,20 +3204,24 @@ export function createCli(
2994
3204
  // the name is written into every event the session appends, and a
2995
3205
  // harness must not be able to sign another agent's name to work.
2996
3206
  const bound = options.agentId
2997
- ? await withClient(global.home, defaultActor(env, 'system', 'cli'), async (client) => {
2998
- const resolution = await client.agents.resolve(options.agentId!);
2999
- if (!resolution.match) {
3000
- throw new SynomemError(
3001
- 'AGENT_NOT_FOUND',
3002
- resolution.candidates.length
3003
- ? `"${options.agentId}" matches ${resolution.candidates.length} agents: ${resolution.candidates
3004
- .map((candidate) => candidate.id)
3005
- .join(', ')}. Name one of them.`
3006
- : `Unknown agent: ${options.agentId}`,
3007
- );
3008
- }
3009
- return actor('agent', resolution.match.id, resolution.match.displayName);
3010
- })
3207
+ ? await withClient(
3208
+ global.home,
3209
+ defaultActor(env, 'system', 'cli', global.actor),
3210
+ async (client) => {
3211
+ const resolution = await client.agents.resolve(options.agentId!);
3212
+ if (!resolution.match) {
3213
+ throw new SynomemError(
3214
+ 'AGENT_NOT_FOUND',
3215
+ resolution.candidates.length
3216
+ ? `"${options.agentId}" matches ${resolution.candidates.length} agents: ${resolution.candidates
3217
+ .map((candidate) => candidate.id)
3218
+ .join(', ')}. Name one of them.`
3219
+ : `Unknown agent: ${options.agentId}`,
3220
+ );
3221
+ }
3222
+ return actor('agent', resolution.match.id, resolution.match.displayName);
3223
+ },
3224
+ )
3011
3225
  : undefined;
3012
3226
  if (!bound && !(options.actorId && options.actorKind)) {
3013
3227
  throw new SynomemError(