agentirc-cli 9.8.1__tar.gz → 9.10.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (169) hide show
  1. {agentirc_cli-9.8.1 → agentirc_cli-9.10.0}/.claude/skills/cicd/scripts/portability-lint.sh +5 -1
  2. agentirc_cli-9.10.0/.claude/skills/recall/SKILL.md +181 -0
  3. agentirc_cli-9.10.0/.claude/skills/recall/scripts/recall.sh +163 -0
  4. agentirc_cli-9.10.0/.claude/skills/remember/SKILL.md +118 -0
  5. agentirc_cli-9.10.0/.claude/skills/remember/scripts/remember.sh +164 -0
  6. agentirc_cli-9.10.0/.devague/current +1 -0
  7. agentirc_cli-9.10.0/.devague/current_plan +1 -0
  8. agentirc_cli-9.10.0/.devague/frames/agentirc-ships-an-agent-accessibility-release-ai-a.json +414 -0
  9. agentirc_cli-9.10.0/.devague/plans/agentirc-ships-an-agent-accessibility-release-ai-a.json +586 -0
  10. {agentirc_cli-9.8.1 → agentirc_cli-9.10.0}/.gitignore +2 -0
  11. {agentirc_cli-9.8.1 → agentirc_cli-9.10.0}/CHANGELOG.md +58 -0
  12. {agentirc_cli-9.8.1 → agentirc_cli-9.10.0}/CLAUDE.md +37 -0
  13. {agentirc_cli-9.8.1 → agentirc_cli-9.10.0}/PKG-INFO +1 -1
  14. agentirc_cli-9.10.0/docs/plans/2026-07-01-agentirc-ships-an-agent-accessibility-release-ai-a.md +145 -0
  15. agentirc_cli-9.10.0/docs/specs/2026-07-01-agentirc-ships-an-agent-accessibility-release-ai-a.md +76 -0
  16. {agentirc_cli-9.8.1 → agentirc_cli-9.10.0}/pyproject.toml +1 -1
  17. {agentirc_cli-9.8.1 → agentirc_cli-9.10.0}/uv.lock +1 -1
  18. agentirc_cli-9.8.1/.devague/current +0 -1
  19. agentirc_cli-9.8.1/.devague/current_plan +0 -1
  20. {agentirc_cli-9.8.1 → agentirc_cli-9.10.0}/.claude/skills/ask-colleague/SKILL.md +0 -0
  21. {agentirc_cli-9.8.1 → agentirc_cli-9.10.0}/.claude/skills/ask-colleague/prompts/explore.md +0 -0
  22. {agentirc_cli-9.8.1 → agentirc_cli-9.10.0}/.claude/skills/ask-colleague/prompts/review.md +0 -0
  23. {agentirc_cli-9.8.1 → agentirc_cli-9.10.0}/.claude/skills/ask-colleague/prompts/write.md +0 -0
  24. {agentirc_cli-9.8.1 → agentirc_cli-9.10.0}/.claude/skills/ask-colleague/scripts/ask-colleague.sh +0 -0
  25. {agentirc_cli-9.8.1 → agentirc_cli-9.10.0}/.claude/skills/assign-to-workforce/SKILL.md +0 -0
  26. {agentirc_cli-9.8.1 → agentirc_cli-9.10.0}/.claude/skills/assign-to-workforce/scripts/assign-to-workforce.sh +0 -0
  27. {agentirc_cli-9.8.1 → agentirc_cli-9.10.0}/.claude/skills/cicd/SKILL.md +0 -0
  28. {agentirc_cli-9.8.1 → agentirc_cli-9.10.0}/.claude/skills/cicd/scripts/_resolve-nick.sh +0 -0
  29. {agentirc_cli-9.8.1 → agentirc_cli-9.10.0}/.claude/skills/cicd/scripts/pr-reply.sh +0 -0
  30. {agentirc_cli-9.8.1 → agentirc_cli-9.10.0}/.claude/skills/cicd/scripts/pr-status.sh +0 -0
  31. {agentirc_cli-9.8.1 → agentirc_cli-9.10.0}/.claude/skills/cicd/scripts/workflow.sh +0 -0
  32. {agentirc_cli-9.8.1 → agentirc_cli-9.10.0}/.claude/skills/communicate/SKILL.md +0 -0
  33. {agentirc_cli-9.8.1 → agentirc_cli-9.10.0}/.claude/skills/communicate/scripts/fetch-issues.sh +0 -0
  34. {agentirc_cli-9.8.1 → agentirc_cli-9.10.0}/.claude/skills/communicate/scripts/mesh-message.sh +0 -0
  35. {agentirc_cli-9.8.1 → agentirc_cli-9.10.0}/.claude/skills/communicate/scripts/post-comment.sh +0 -0
  36. {agentirc_cli-9.8.1 → agentirc_cli-9.10.0}/.claude/skills/communicate/scripts/post-issue.sh +0 -0
  37. {agentirc_cli-9.8.1 → agentirc_cli-9.10.0}/.claude/skills/spec-to-plan/SKILL.md +0 -0
  38. {agentirc_cli-9.8.1 → agentirc_cli-9.10.0}/.claude/skills/spec-to-plan/scripts/spec-to-plan.sh +0 -0
  39. {agentirc_cli-9.8.1 → agentirc_cli-9.10.0}/.claude/skills/think/SKILL.md +0 -0
  40. {agentirc_cli-9.8.1 → agentirc_cli-9.10.0}/.claude/skills/think/scripts/think.sh +0 -0
  41. {agentirc_cli-9.8.1 → agentirc_cli-9.10.0}/.claude/skills.local.yaml.example +0 -0
  42. {agentirc_cli-9.8.1 → agentirc_cli-9.10.0}/.devague/frames/agentirc-9-7-0-ships-an-embedded-bot-framework-bot.json +0 -0
  43. {agentirc_cli-9.8.1 → agentirc_cli-9.10.0}/.devague/plans/agentirc-9-7-0-ships-an-embedded-bot-framework-bot.json +0 -0
  44. {agentirc_cli-9.8.1 → agentirc_cli-9.10.0}/.github/workflows/publish.yml +0 -0
  45. {agentirc_cli-9.8.1 → agentirc_cli-9.10.0}/.github/workflows/tests.yml +0 -0
  46. {agentirc_cli-9.8.1 → agentirc_cli-9.10.0}/LICENSE +0 -0
  47. {agentirc_cli-9.8.1 → agentirc_cli-9.10.0}/README.md +0 -0
  48. {agentirc_cli-9.8.1 → agentirc_cli-9.10.0}/agentirc/__init__.py +0 -0
  49. {agentirc_cli-9.8.1 → agentirc_cli-9.10.0}/agentirc/__main__.py +0 -0
  50. {agentirc_cli-9.8.1 → agentirc_cli-9.10.0}/agentirc/_internal/__init__.py +0 -0
  51. {agentirc_cli-9.8.1 → agentirc_cli-9.10.0}/agentirc/_internal/aio.py +0 -0
  52. {agentirc_cli-9.8.1 → agentirc_cli-9.10.0}/agentirc/_internal/bots/__init__.py +0 -0
  53. {agentirc_cli-9.8.1 → agentirc_cli-9.10.0}/agentirc/_internal/bots/bot_manager.py +0 -0
  54. {agentirc_cli-9.8.1 → agentirc_cli-9.10.0}/agentirc/_internal/bots/http_listener.py +0 -0
  55. {agentirc_cli-9.8.1 → agentirc_cli-9.10.0}/agentirc/_internal/cli_shared/__init__.py +0 -0
  56. {agentirc_cli-9.8.1 → agentirc_cli-9.10.0}/agentirc/_internal/cli_shared/constants.py +0 -0
  57. {agentirc_cli-9.8.1 → agentirc_cli-9.10.0}/agentirc/_internal/cli_shared/mesh.py +0 -0
  58. {agentirc_cli-9.8.1 → agentirc_cli-9.10.0}/agentirc/_internal/constants.py +0 -0
  59. {agentirc_cli-9.8.1 → agentirc_cli-9.10.0}/agentirc/_internal/event_subscriptions.py +0 -0
  60. {agentirc_cli-9.8.1 → agentirc_cli-9.10.0}/agentirc/_internal/pidfile.py +0 -0
  61. {agentirc_cli-9.8.1 → agentirc_cli-9.10.0}/agentirc/_internal/protocol/__init__.py +0 -0
  62. {agentirc_cli-9.8.1 → agentirc_cli-9.10.0}/agentirc/_internal/protocol/message.py +0 -0
  63. {agentirc_cli-9.8.1 → agentirc_cli-9.10.0}/agentirc/_internal/protocol/replies.py +0 -0
  64. {agentirc_cli-9.8.1 → agentirc_cli-9.10.0}/agentirc/_internal/telemetry/__init__.py +0 -0
  65. {agentirc_cli-9.8.1 → agentirc_cli-9.10.0}/agentirc/_internal/telemetry/audit.py +0 -0
  66. {agentirc_cli-9.8.1 → agentirc_cli-9.10.0}/agentirc/_internal/telemetry/context.py +0 -0
  67. {agentirc_cli-9.8.1 → agentirc_cli-9.10.0}/agentirc/_internal/telemetry/metrics.py +0 -0
  68. {agentirc_cli-9.8.1 → agentirc_cli-9.10.0}/agentirc/_internal/telemetry/tracing.py +0 -0
  69. {agentirc_cli-9.8.1 → agentirc_cli-9.10.0}/agentirc/_internal/virtual_client.py +0 -0
  70. {agentirc_cli-9.8.1 → agentirc_cli-9.10.0}/agentirc/bots/__init__.py +0 -0
  71. {agentirc_cli-9.8.1 → agentirc_cli-9.10.0}/agentirc/bots/bot.py +0 -0
  72. {agentirc_cli-9.8.1 → agentirc_cli-9.10.0}/agentirc/bots/bot_manager.py +0 -0
  73. {agentirc_cli-9.8.1 → agentirc_cli-9.10.0}/agentirc/bots/cli.py +0 -0
  74. {agentirc_cli-9.8.1 → agentirc_cli-9.10.0}/agentirc/bots/config.py +0 -0
  75. {agentirc_cli-9.8.1 → agentirc_cli-9.10.0}/agentirc/bots/filter_dsl.py +0 -0
  76. {agentirc_cli-9.8.1 → agentirc_cli-9.10.0}/agentirc/bots/http_listener.py +0 -0
  77. {agentirc_cli-9.8.1 → agentirc_cli-9.10.0}/agentirc/bots/template_engine.py +0 -0
  78. {agentirc_cli-9.8.1 → agentirc_cli-9.10.0}/agentirc/bots/virtual_client.py +0 -0
  79. {agentirc_cli-9.8.1 → agentirc_cli-9.10.0}/agentirc/channel.py +0 -0
  80. {agentirc_cli-9.8.1 → agentirc_cli-9.10.0}/agentirc/cli.py +0 -0
  81. {agentirc_cli-9.8.1 → agentirc_cli-9.10.0}/agentirc/client.py +0 -0
  82. {agentirc_cli-9.8.1 → agentirc_cli-9.10.0}/agentirc/config.py +0 -0
  83. {agentirc_cli-9.8.1 → agentirc_cli-9.10.0}/agentirc/events.py +0 -0
  84. {agentirc_cli-9.8.1 → agentirc_cli-9.10.0}/agentirc/history_store.py +0 -0
  85. {agentirc_cli-9.8.1 → agentirc_cli-9.10.0}/agentirc/ircd.py +0 -0
  86. {agentirc_cli-9.8.1 → agentirc_cli-9.10.0}/agentirc/protocol.py +0 -0
  87. {agentirc_cli-9.8.1 → agentirc_cli-9.10.0}/agentirc/remote_client.py +0 -0
  88. {agentirc_cli-9.8.1 → agentirc_cli-9.10.0}/agentirc/room_store.py +0 -0
  89. {agentirc_cli-9.8.1 → agentirc_cli-9.10.0}/agentirc/rooms_util.py +0 -0
  90. {agentirc_cli-9.8.1 → agentirc_cli-9.10.0}/agentirc/server_link.py +0 -0
  91. {agentirc_cli-9.8.1 → agentirc_cli-9.10.0}/agentirc/skill.py +0 -0
  92. {agentirc_cli-9.8.1 → agentirc_cli-9.10.0}/agentirc/skills/__init__.py +0 -0
  93. {agentirc_cli-9.8.1 → agentirc_cli-9.10.0}/agentirc/skills/history.py +0 -0
  94. {agentirc_cli-9.8.1 → agentirc_cli-9.10.0}/agentirc/skills/icon.py +0 -0
  95. {agentirc_cli-9.8.1 → agentirc_cli-9.10.0}/agentirc/skills/rooms.py +0 -0
  96. {agentirc_cli-9.8.1 → agentirc_cli-9.10.0}/agentirc/skills/threads.py +0 -0
  97. {agentirc_cli-9.8.1 → agentirc_cli-9.10.0}/agentirc/thread_store.py +0 -0
  98. {agentirc_cli-9.8.1 → agentirc_cli-9.10.0}/agentirc/virtual_client.py +0 -0
  99. {agentirc_cli-9.8.1 → agentirc_cli-9.10.0}/docs/api-stability.md +0 -0
  100. {agentirc_cli-9.8.1 → agentirc_cli-9.10.0}/docs/bots.md +0 -0
  101. {agentirc_cli-9.8.1 → agentirc_cli-9.10.0}/docs/cli.md +0 -0
  102. {agentirc_cli-9.8.1 → agentirc_cli-9.10.0}/docs/deployment.md +0 -0
  103. {agentirc_cli-9.8.1 → agentirc_cli-9.10.0}/docs/extension-api.md +0 -0
  104. {agentirc_cli-9.8.1 → agentirc_cli-9.10.0}/docs/plans/2026-06-12-agentirc-9-7-0-ships-an-embedded-bot-framework-bot.md +0 -0
  105. {agentirc_cli-9.8.1 → agentirc_cli-9.10.0}/docs/specs/2026-06-12-agentirc-9-7-0-ships-an-embedded-bot-framework-bot.md +0 -0
  106. {agentirc_cli-9.8.1 → agentirc_cli-9.10.0}/docs/steward/onboarding.md +0 -0
  107. {agentirc_cli-9.8.1 → agentirc_cli-9.10.0}/docs/superpowers/specs/2026-04-30-bootstrap-design.md +0 -0
  108. {agentirc_cli-9.8.1 → agentirc_cli-9.10.0}/docs/superpowers/specs/2026-05-01-bot-extension-api-design.md +0 -0
  109. {agentirc_cli-9.8.1 → agentirc_cli-9.10.0}/docs/superpowers/specs/2026-05-01-task14-audit.md +0 -0
  110. {agentirc_cli-9.8.1 → agentirc_cli-9.10.0}/tests/__init__.py +0 -0
  111. {agentirc_cli-9.8.1 → agentirc_cli-9.10.0}/tests/_helpers.py +0 -0
  112. {agentirc_cli-9.8.1 → agentirc_cli-9.10.0}/tests/bots/__init__.py +0 -0
  113. {agentirc_cli-9.8.1 → agentirc_cli-9.10.0}/tests/bots/test_bot.py +0 -0
  114. {agentirc_cli-9.8.1 → agentirc_cli-9.10.0}/tests/bots/test_bot_host.py +0 -0
  115. {agentirc_cli-9.8.1 → agentirc_cli-9.10.0}/tests/bots/test_bot_manager.py +0 -0
  116. {agentirc_cli-9.8.1 → agentirc_cli-9.10.0}/tests/bots/test_cli_bot.py +0 -0
  117. {agentirc_cli-9.8.1 → agentirc_cli-9.10.0}/tests/bots/test_config_bots.py +0 -0
  118. {agentirc_cli-9.8.1 → agentirc_cli-9.10.0}/tests/bots/test_filter_dsl.py +0 -0
  119. {agentirc_cli-9.8.1 → agentirc_cli-9.10.0}/tests/bots/test_http_listener.py +0 -0
  120. {agentirc_cli-9.8.1 → agentirc_cli-9.10.0}/tests/bots/test_public_surface.py +0 -0
  121. {agentirc_cli-9.8.1 → agentirc_cli-9.10.0}/tests/bots/test_template_engine.py +0 -0
  122. {agentirc_cli-9.8.1 → agentirc_cli-9.10.0}/tests/conftest.py +0 -0
  123. {agentirc_cli-9.8.1 → agentirc_cli-9.10.0}/tests/telemetry/__init__.py +0 -0
  124. {agentirc_cli-9.8.1 → agentirc_cli-9.10.0}/tests/telemetry/_fakes.py +0 -0
  125. {agentirc_cli-9.8.1 → agentirc_cli-9.10.0}/tests/telemetry/_metrics_helpers.py +0 -0
  126. {agentirc_cli-9.8.1 → agentirc_cli-9.10.0}/tests/telemetry/test_audit_emit.py +0 -0
  127. {agentirc_cli-9.8.1 → agentirc_cli-9.10.0}/tests/telemetry/test_audit_lifecycle.py +0 -0
  128. {agentirc_cli-9.8.1 → agentirc_cli-9.10.0}/tests/telemetry/test_audit_module.py +0 -0
  129. {agentirc_cli-9.8.1 → agentirc_cli-9.10.0}/tests/telemetry/test_audit_parse_error.py +0 -0
  130. {agentirc_cli-9.8.1 → agentirc_cli-9.10.0}/tests/telemetry/test_config.py +0 -0
  131. {agentirc_cli-9.8.1 → agentirc_cli-9.10.0}/tests/telemetry/test_dispatch_span.py +0 -0
  132. {agentirc_cli-9.8.1 → agentirc_cli-9.10.0}/tests/telemetry/test_emit_event_span.py +0 -0
  133. {agentirc_cli-9.8.1 → agentirc_cli-9.10.0}/tests/telemetry/test_metrics_init.py +0 -0
  134. {agentirc_cli-9.8.1 → agentirc_cli-9.10.0}/tests/telemetry/test_metrics_s2s.py +0 -0
  135. {agentirc_cli-9.8.1 → agentirc_cli-9.10.0}/tests/telemetry/test_outbound_inject.py +0 -0
  136. {agentirc_cli-9.8.1 → agentirc_cli-9.10.0}/tests/telemetry/test_parse_error.py +0 -0
  137. {agentirc_cli-9.8.1 → agentirc_cli-9.10.0}/tests/telemetry/test_s2s_relay_span.py +0 -0
  138. {agentirc_cli-9.8.1 → agentirc_cli-9.10.0}/tests/telemetry/test_server_init.py +0 -0
  139. {agentirc_cli-9.8.1 → agentirc_cli-9.10.0}/tests/telemetry/test_server_link_inject.py +0 -0
  140. {agentirc_cli-9.8.1 → agentirc_cli-9.10.0}/tests/telemetry/test_tracing.py +0 -0
  141. {agentirc_cli-9.8.1 → agentirc_cli-9.10.0}/tests/test_api_stability_embedding.py +0 -0
  142. {agentirc_cli-9.8.1 → agentirc_cli-9.10.0}/tests/test_bot_capability.py +0 -0
  143. {agentirc_cli-9.8.1 → agentirc_cli-9.10.0}/tests/test_channel.py +0 -0
  144. {agentirc_cli-9.8.1 → agentirc_cli-9.10.0}/tests/test_cli.py +0 -0
  145. {agentirc_cli-9.8.1 → agentirc_cli-9.10.0}/tests/test_config_loader.py +0 -0
  146. {agentirc_cli-9.8.1 → agentirc_cli-9.10.0}/tests/test_connection.py +0 -0
  147. {agentirc_cli-9.8.1 → agentirc_cli-9.10.0}/tests/test_discovery.py +0 -0
  148. {agentirc_cli-9.8.1 → agentirc_cli-9.10.0}/tests/test_event_subscriptions.py +0 -0
  149. {agentirc_cli-9.8.1 → agentirc_cli-9.10.0}/tests/test_eventpub.py +0 -0
  150. {agentirc_cli-9.8.1 → agentirc_cli-9.10.0}/tests/test_events_basic.py +0 -0
  151. {agentirc_cli-9.8.1 → agentirc_cli-9.10.0}/tests/test_events_catalog.py +0 -0
  152. {agentirc_cli-9.8.1 → agentirc_cli-9.10.0}/tests/test_events_federation.py +0 -0
  153. {agentirc_cli-9.8.1 → agentirc_cli-9.10.0}/tests/test_events_history.py +0 -0
  154. {agentirc_cli-9.8.1 → agentirc_cli-9.10.0}/tests/test_events_lifecycle.py +0 -0
  155. {agentirc_cli-9.8.1 → agentirc_cli-9.10.0}/tests/test_events_reserved_nick.py +0 -0
  156. {agentirc_cli-9.8.1 → agentirc_cli-9.10.0}/tests/test_federation.py +0 -0
  157. {agentirc_cli-9.8.1 → agentirc_cli-9.10.0}/tests/test_history.py +0 -0
  158. {agentirc_cli-9.8.1 → agentirc_cli-9.10.0}/tests/test_link_reconnect.py +0 -0
  159. {agentirc_cli-9.8.1 → agentirc_cli-9.10.0}/tests/test_mentions.py +0 -0
  160. {agentirc_cli-9.8.1 → agentirc_cli-9.10.0}/tests/test_messaging.py +0 -0
  161. {agentirc_cli-9.8.1 → agentirc_cli-9.10.0}/tests/test_modes.py +0 -0
  162. {agentirc_cli-9.8.1 → agentirc_cli-9.10.0}/tests/test_protocol_bot_exports.py +0 -0
  163. {agentirc_cli-9.8.1 → agentirc_cli-9.10.0}/tests/test_room_persistence.py +0 -0
  164. {agentirc_cli-9.8.1 → agentirc_cli-9.10.0}/tests/test_rooms_federation.py +0 -0
  165. {agentirc_cli-9.8.1 → agentirc_cli-9.10.0}/tests/test_rooms_integration.py +0 -0
  166. {agentirc_cli-9.8.1 → agentirc_cli-9.10.0}/tests/test_server_icon_skill.py +0 -0
  167. {agentirc_cli-9.8.1 → agentirc_cli-9.10.0}/tests/test_skills.py +0 -0
  168. {agentirc_cli-9.8.1 → agentirc_cli-9.10.0}/tests/test_threads.py +0 -0
  169. {agentirc_cli-9.8.1 → agentirc_cli-9.10.0}/tests/test_wire_format_envelope.py +0 -0
@@ -27,11 +27,15 @@ hits1=$(echo "$files" | xargs -r grep -nE '/home/[a-z][a-z0-9_-]+/' 2>/dev/null
27
27
  # Carve-outs (allowed, NOT flagged):
28
28
  # - ~/.claude/skills/<x>/scripts/ vendored tool calls
29
29
  # - ~/.culture/ Culture mesh data this skill is supposed to read
30
+ # - ~/.eidetic/ eidetic memory store (deliberately outside any
31
+ # git worktree; see recall/remember SKILL.md).
32
+ # Local extension over the steward upstream (#46).
30
33
  md_yaml=$(echo "$files" | grep -E '\.(md|ya?ml|toml|json|jsonc)$' || true)
31
34
  if [ -n "$md_yaml" ]; then
32
35
  hits2=$(echo "$md_yaml" | xargs -r grep -nE '~/\.[A-Za-z]' 2>/dev/null \
33
36
  | grep -vE '~/\.claude/skills/[^[:space:]"]+/scripts/' \
34
37
  | grep -vE '~/\.culture/' \
38
+ | grep -vE '~/\.eidetic/' \
35
39
  || true)
36
40
  else
37
41
  hits2=""
@@ -48,7 +52,7 @@ if [ -n "$hits2" ]; then
48
52
  [ "$fail" -eq 1 ] && echo
49
53
  echo "❌ Per-user ~/.<dotfile> config refs in committed doc/config:"
50
54
  echo "$hits2" | sed 's/^/ /'
51
- echo " Allowed carve-outs: ~/.claude/skills/.../scripts/ (tool calls), ~/.culture/ (mesh data)."
55
+ echo " Allowed carve-outs: ~/.claude/skills/.../scripts/ (tool calls), ~/.culture/ (mesh data), ~/.eidetic/ (memory store)."
52
56
  echo " Otherwise: commit a repo-local config or document a portable lookup."
53
57
  fail=1
54
58
  fi
@@ -0,0 +1,181 @@
1
+ ---
2
+ name: recall
3
+ type: command
4
+ description: >
5
+ Search the shared eidetic memory store and get back ranked, provenanced
6
+ records. Drives `eidetic recall` with four search modes — exact (verbatim
7
+ substring), approximate (vector/semantic), keyword (BM25 lexical), and hybrid
8
+ (a weighted blend of vector+keyword, the default) — each hit carrying its
9
+ text, full metadata, a relevance `score`, and a freshness `signal`. Recall
10
+ passively reinforces matched records (bumps last_recall + recall_count).
11
+ Shadowed and archived records are excluded by default; use
12
+ --include-shadowed / --include-archived to retrieve them. The store lives at
13
+ ~/.eidetic/memory (a home-dir path outside any git worktree); the wrapper
14
+ defaults queries to this agent's PERSONAL, PRIVATE scope (`--scope agentirc
15
+ --visibility private`, suffix read from culture.yaml) — matching where
16
+ /remember writes — so a no-flag recall returns this agent's own private records
17
+ plus the shared public pool, and Claude and the colleague backend recall each
18
+ other's memories because both resolve the same suffix via this skill. Use
19
+ when the user says "recall", "what do we know about X", "search memory",
20
+ "have we seen X before", "look it up in memory", "eidetic recall", or before
21
+ answering from scratch when prior context may already be stored. Pairs with
22
+ the sibling /remember skill.
23
+ ---
24
+
25
+ # recall — search the shared eidetic memory
26
+
27
+ `recall` drives **`eidetic recall`**: given a query, it returns the top-k stored
28
+ records ranked by relevance, each with its `text`, full `metadata` (provenance),
29
+ a numeric `score`, and a freshness `signal`. It is the read half of the memory
30
+ surface; the write half is the sibling **/remember** skill.
31
+
32
+ The point of a *shared* store is that memory is a **team faculty**, not a
33
+ per-agent silo: a record Claude wrote is recallable by the colleague backend
34
+ (and vice versa), because both resolve the same `~/.eidetic/memory` path.
35
+
36
+ ## How to run
37
+
38
+ ```bash
39
+ bash .claude/skills/recall/scripts/recall.sh "<query>" [flags...]
40
+ ```
41
+
42
+ The wrapper resolves the CLI portably (installed `eidetic` on `PATH`, else
43
+ `uv run eidetic` from the checkout) and forwards every flag verbatim, so it is
44
+ exactly `eidetic recall …`. Run it from anywhere; the store is the same.
45
+
46
+ ## Search modes (`--mode`, default `hybrid`)
47
+
48
+ | Mode | What it matches | Needs embed server? |
49
+ |------|-----------------|---------------------|
50
+ | `exact` | case-insensitive verbatim substring (`--case-sensitive` to tighten) | no — offline-safe |
51
+ | `approximate` | vector cosine / semantic similarity | yes (falls back offline) |
52
+ | `keyword` | BM25 lexical; only records sharing a query term | no — offline-safe |
53
+ | `hybrid` | `alpha*approximate + (1-alpha)*keyword` (`--alpha`, default 0.5) | uses it when up |
54
+
55
+ `hybrid` is the default because the two signals cover each other's blind spots:
56
+ vector catches paraphrases, keyword catches exact ids/quotes. When the embed
57
+ server is unreachable, `hybrid` collapses to keyword-only (it never fuses
58
+ meaningless offline-fallback cosine).
59
+
60
+ ## Output fields
61
+
62
+ Each hit in `--json` output includes:
63
+
64
+ | Field | Notes |
65
+ |-------|-------|
66
+ | `id` | stable record identity |
67
+ | `text` | the stored chunk |
68
+ | `type` | record type |
69
+ | `metadata` | full provenance, round-tripped verbatim from ingest |
70
+ | `score` | relevance score from the chosen search mode (freshness-blended) |
71
+ | `signal` | freshness strength in [0, 1]; computed at recall time from age, recall frequency, and staleness |
72
+ | `created` | ISO-8601 ingest date (may be DATE_UNKNOWN for legacy records) |
73
+ | `last_recall` | ISO-8601 timestamp of the most recent recall hit (null if never recalled) |
74
+ | `recall_count` | number of times this record has been recalled (passive reinforcement counter) |
75
+ | `lifecycle` | `active`, `shadowed`, or `archived` |
76
+ | `links` | list of related-memory ids |
77
+
78
+ ## Freshness signal
79
+
80
+ Every `recall` hit carries a `signal` field (float in `[0, 1]`). The signal
81
+ blends **multiplicatively** into the lexical/vector score so recently-created
82
+ and frequently-recalled records surface ahead of stale ones. The formula:
83
+
84
+ ```
85
+ access_bonus = min(0.5, recall_count * 0.05)
86
+ age_factor = 1 / (1 + days_since_creation * 0.01)
87
+ staleness = days_since_last_recall * 0.01
88
+ signal = clamp((0.5 - staleness + access_bonus) * age_factor, 0, 1)
89
+ blended_score = score * (1 + 0.25 * (signal - 0.5))
90
+ ```
91
+
92
+ Records with no temporal data (legacy, undated) are an exact no-op — the blend
93
+ is skipped for them so pre-existing fixture scores are unchanged.
94
+
95
+ Each `recall` call is also **passive reinforcement**: it bumps `last_recall` and
96
+ `recall_count` on every matched record, so frequently-recalled memories organically
97
+ gain signal strength over time.
98
+
99
+ ## Lifecycle flags
100
+
101
+ By default, `recall` returns only `active` records. Use these flags to retrieve
102
+ non-active records:
103
+
104
+ - `--include-shadowed` — include records whose `lifecycle == "shadowed"` (records
105
+ superseded within their scope by a newer record). Shadowed records are preserved
106
+ and still searchable; they are just hidden from the default result set.
107
+ - `--include-archived` — include records whose `lifecycle == "archived"` (records
108
+ older than ~1 year or below the signal threshold). Archived records are fully
109
+ preserved; the flag makes them retrievable again.
110
+
111
+ Both flags can be combined. Neither affects ranking — shadowed/archived records
112
+ compete on score/signal just like active ones when included.
113
+
114
+ ## Common flags (forwarded to `eidetic recall`)
115
+
116
+ - `--mode exact|approximate|keyword|hybrid` — default `hybrid`.
117
+ - `--top-k N` — max results (default 5).
118
+ - `--alpha F` — hybrid blend weight in `[0,1]` (default 0.5).
119
+ - `--case-sensitive` — for `--mode exact`.
120
+ - `--filter KEY=VALUE` — metadata facet filter (repeatable): e.g. `--filter source=docs`.
121
+ - `--scope NAME` / `--visibility public|private` — scope isolation (no private
122
+ leak). **The wrapper defaults this to the agent's PERSONAL, PRIVATE scope**
123
+ (`--scope agentirc --visibility private`, suffix read from `culture.yaml`),
124
+ matching where `/remember` writes — so a no-flag recall returns this agent's
125
+ own private records **plus** the shared public pool, while those private records
126
+ stay invisible to a `default`/other-scope recall. Pass `--scope`/`--visibility`
127
+ to query elsewhere; a wheel install with no `culture.yaml` falls back to the
128
+ CLI default `default`/`public`.
129
+ - `--backend files|mongo|neo4j` — default `files` (the shared home-dir store).
130
+ - `--include-shadowed` — include shadowed records in results (excluded by default).
131
+ - `--include-archived` — include archived records in results (excluded by default).
132
+ - `--json` — structured list to stdout (use this when an agent parses the result).
133
+
134
+ ## Examples
135
+
136
+ ```bash
137
+ # Default hybrid recall, JSON for an agent to parse:
138
+ bash .claude/skills/recall/scripts/recall.sh "jetson nano power draw" --json
139
+
140
+ # Find the exact message that mentions a phrase:
141
+ bash .claude/skills/recall/scripts/recall.sh "Orin Nano" --mode exact
142
+
143
+ # Keyword search, offline-safe, narrowed to a source:
144
+ bash .claude/skills/recall/scripts/recall.sh "thermal throttle" --mode keyword \
145
+ --filter source=discord --top-k 10
146
+
147
+ # Retrieve a record that was recently shadowed (its superseding record is now active):
148
+ bash .claude/skills/recall/scripts/recall.sh "old topic" --include-shadowed --json
149
+
150
+ # Retrieve all records including archived (to audit stale memories):
151
+ bash .claude/skills/recall/scripts/recall.sh "power" --include-archived --include-shadowed --json
152
+ ```
153
+
154
+ ## Notes
155
+
156
+ - **Provenance is mandatory** on every hit — recall is for *cited* answers.
157
+ - The embed endpoint defaults to the local model-gear embed gear
158
+ (`http://localhost:8002/v1`, model `Qwen/Qwen3-Embedding-0.6B`); override with
159
+ `EIDETIC_EMBED_URL` / `EIDETIC_EMBED_MODEL`. `exact`/`keyword` ignore it.
160
+ - **Use the wrapper, not a bare `eidetic`.** The console script may not be on
161
+ `PATH` (in a dev checkout it isn't) — the wrapper resolves it for you (`PATH`
162
+ first, else `uv run eidetic`). For the docs, run `eidetic explain recall` if
163
+ installed, otherwise `uv run --project <eidetic-cli checkout> eidetic explain
164
+ recall`. (`explain` is an **`eidetic`** verb — a sibling tool like `devex`
165
+ won't know it.)
166
+ - **Reading scores:** `exact`, `keyword`, and `hybrid` drop non-matching records
167
+ (hybrid drops any record with a `0.0` blended score), so their hits are real
168
+ matches. `approximate` keeps every candidate ranked by raw cosine, so it can
169
+ return low/near-zero scores when the store is small — lower `--top-k` to trim.
170
+ A `--min-score` threshold is a tracked follow-up.
171
+ - **Sharing scope = one OS user.** The default store is `~/.eidetic/memory`, so
172
+ every agent/process running as the *same* OS user shares it (that is the point —
173
+ Claude + colleague). It is not isolated between OS users by anything but file
174
+ permissions; keep genuinely private data in a `--visibility private` scope and
175
+ treat the host as the trust boundary.
176
+
177
+ ## Provenance
178
+
179
+ First-party to **eidetic-cli** — eidetic owns its memory surface. Cite, don't
180
+ import: downstream repos copy this skill, they don't symlink it. See
181
+ [`docs/skill-sources.md`](../../../docs/skill-sources.md).
@@ -0,0 +1,163 @@
1
+ #!/usr/bin/env bash
2
+ # recall.sh — search the shared eidetic memory store (the /recall skill).
3
+ #
4
+ # Thin, portable wrapper around `eidetic recall`. It resolves the CLI, points
5
+ # the embedding modes at the local model-gear embed gear (overridable), and
6
+ # forwards every flag verbatim — so `recall.sh "<query>" --mode hybrid --json`
7
+ # is exactly `eidetic recall "<query>" --mode hybrid --json`.
8
+ #
9
+ # The store is the files backend. Default location resolves per-operation:
10
+ # PUBLIC records inside a git repo → <repo-root>/.eidetic/memory (committed,
11
+ # team-shared); PRIVATE records, or any record outside a git repo →
12
+ # $HOME/.eidetic/memory (never committed). Recall reads both stores and merges.
13
+ # An explicit EIDETIC_DATA_DIR wins and short-circuits to that single dir.
14
+
15
+ set -euo pipefail
16
+
17
+ # ── resolve the eidetic CLI (installed tool first, then dev checkout) ────────
18
+ EIDETIC=()
19
+ resolve_eidetic() {
20
+ if command -v eidetic >/dev/null 2>&1; then
21
+ EIDETIC=(eidetic) # installed console script — the normal case
22
+ return 0
23
+ fi
24
+ # Dev fallback: inside the eidetic-cli checkout, run via uv.
25
+ local dir
26
+ dir=$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)
27
+ while [ -n "$dir" ] && [ "$dir" != "/" ]; do
28
+ if [ -f "$dir/pyproject.toml" ] \
29
+ && grep -q '^name = "eidetic-cli"' "$dir/pyproject.toml" 2>/dev/null; then
30
+ if command -v uv >/dev/null 2>&1; then
31
+ EIDETIC=(uv run --project "$dir" eidetic)
32
+ return 0
33
+ fi
34
+ break
35
+ fi
36
+ dir=$(dirname "$dir")
37
+ done
38
+ # In a vendored copy there is no eidetic-cli checkout to fall back to, so the
39
+ # only honest remedy is to install the CLI. One `error:` + one `hint:` line.
40
+ printf 'error: eidetic CLI not found.\n' >&2
41
+ printf 'hint: install it with: uv tool install eidetic-cli (or pipx install eidetic-cli); the console script is eidetic.\n' >&2
42
+ return 1
43
+ }
44
+
45
+ usage() {
46
+ cat <<'EOF'
47
+ recall.sh — search the shared eidetic memory store (the /recall skill).
48
+
49
+ Usage:
50
+ recall.sh "<query>" [--mode exact|approximate|keyword|hybrid] [--top-k N] \
51
+ [--alpha F] [--case-sensitive] [--filter KEY=VALUE]... \
52
+ [--backend files|mongo|neo4j] [--scope NAME] [--visibility public|private] \
53
+ [--json]
54
+
55
+ Modes (default: hybrid):
56
+ exact case-insensitive verbatim substring (--case-sensitive to tighten); offline-safe
57
+ approximate vector cosine / semantic similarity (uses the embed server)
58
+ keyword BM25 lexical; only records sharing a query term; offline-safe
59
+ hybrid alpha*approximate + (1-alpha)*keyword (--alpha, default 0.5);
60
+ degrades to keyword-only when the embed server is offline
61
+
62
+ Every flag is forwarded verbatim to `eidetic recall`. See `eidetic explain recall`.
63
+ EOF
64
+ }
65
+
66
+ case "${1:-}" in
67
+ -h | --help)
68
+ usage
69
+ exit 0
70
+ ;;
71
+ "")
72
+ # A missing query is a usage error, not success. The bareword `help` is
73
+ # a legitimate search term, so it is intentionally NOT a usage alias.
74
+ printf 'error: no query given.\n' >&2
75
+ printf 'hint: recall.sh "<query>" [--mode ...] [--json]; run recall.sh --help for usage.\n' >&2
76
+ exit 1
77
+ ;;
78
+ esac
79
+
80
+ resolve_eidetic || exit 2
81
+
82
+ # ── default to this agent's PERSONAL, PRIVATE scope (culture.yaml `suffix`) ──
83
+ # Query this agent's OWN personal scope by default, matching where /remember
84
+ # writes, instead of the global `default` scope shared by every project on this
85
+ # host. We read the `suffix` from the nearest culture.yaml (walking up from this
86
+ # script), so the scope follows the repo identity rather than being hard-coded —
87
+ # a downstream cite-don't-import copy adapts to its own suffix, and the colleague
88
+ # backend (running in a worktree of this same repo) resolves the same suffix,
89
+ # keeping the Claude↔colleague shared-memory story intact.
90
+ #
91
+ # The personal scope is PRIVATE by default to match /remember: in eidetic's model
92
+ # a private record is served only to a recall in the SAME scope (`can_serve`), so
93
+ # querying with --scope <suffix> --visibility private is what retrieves those
94
+ # isolated records (a public/default recall can't see them). Scope and visibility
95
+ # are paired — the private default applies only when we inject the resolved scope,
96
+ # and only if the caller didn't pass --visibility (so an explicit
97
+ # `--visibility public` still wins). An explicit --scope on the command line takes
98
+ # over steering entirely; a wheel install with no culture.yaml falls back to the
99
+ # plain CLI default (`default`/`public`).
100
+ resolve_scope() {
101
+ local dir suffix=""
102
+ dir=$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)
103
+ while [ -n "$dir" ] && [ "$dir" != "/" ]; do
104
+ if [ -f "$dir/culture.yaml" ]; then
105
+ # Capture only the first non-space token after `suffix:` (so an
106
+ # inline `# comment` or trailing space can't bleed into the scope),
107
+ # then strip surrounding quotes only — matching the canonical parser
108
+ # in .claude/skills/cicd/scripts/_resolve-nick.sh.
109
+ # `|| true`: under `set -o pipefail`, `head -n1` closing the pipe
110
+ # early can SIGPIPE `sed`, making the substitution non-zero and
111
+ # aborting the script. An empty parse must yield "" here, not exit.
112
+ suffix=$(sed -n \
113
+ 's/^[[:space:]]*-\{0,1\}[[:space:]]*suffix:[[:space:]]*\([^[:space:]]*\).*/\1/p' \
114
+ "$dir/culture.yaml" | head -n1 | tr -d "\"'" || true)
115
+ break
116
+ fi
117
+ dir=$(dirname "$dir")
118
+ done
119
+ printf '%s' "$suffix"
120
+ }
121
+
122
+ has_flag() {
123
+ local needle=$1
124
+ shift
125
+ local a
126
+ for a in "$@"; do
127
+ case "$a" in
128
+ "$needle" | "$needle"=*) return 0 ;;
129
+ esac
130
+ done
131
+ return 1
132
+ }
133
+
134
+ SCOPE_ARGS=()
135
+ if ! has_flag --scope "$@"; then
136
+ EIDETIC_SCOPE=$(resolve_scope)
137
+ if [ -n "$EIDETIC_SCOPE" ]; then
138
+ SCOPE_ARGS+=(--scope "$EIDETIC_SCOPE")
139
+ # rollout-cli eidetic-memory recipe POLICY OVERRIDE (not eidetic's
140
+ # upstream private default): default to PUBLIC, so a plain recall queries
141
+ # the in-repo public pool (<repo>/.eidetic/memory) this repo writes to.
142
+ # Pass --visibility private to also surface this agent's private ($HOME)
143
+ # notes. The two-store read model reads both dirs regardless.
144
+ has_flag --visibility "$@" || SCOPE_ARGS+=(--visibility public)
145
+ elif ! has_flag --visibility "$@"; then
146
+ # No suffix AND no explicit --visibility: the query runs against
147
+ # eidetic's own default (scope=default, visibility=public), not this
148
+ # agent's private personal scope — so an empty result isn't silently
149
+ # misread. Warn on stderr (stdout stays clean for --json). Warn ONLY
150
+ # here: an explicit --scope (outer guard) or --visibility (this guard) is
151
+ # a deliberate choice, honored verbatim, so either flag silences this.
152
+ printf 'warning: no culture.yaml suffix resolved; querying the public default scope rather than a private personal scope. Pass --scope or --visibility to target deliberately.\n' >&2
153
+ fi
154
+ fi
155
+
156
+ # Default the embedding endpoint to the local model-gear embed gear. eidetic
157
+ # falls back to a deterministic offline embedding if it's unreachable, so this
158
+ # is safe even when the gear is down. Override by exporting these yourself.
159
+ : "${EIDETIC_EMBED_URL:=http://localhost:8002/v1}"
160
+ : "${EIDETIC_EMBED_MODEL:=Qwen/Qwen3-Embedding-0.6B}"
161
+ export EIDETIC_EMBED_URL EIDETIC_EMBED_MODEL
162
+
163
+ exec "${EIDETIC[@]}" recall "${SCOPE_ARGS[@]}" "$@"
@@ -0,0 +1,118 @@
1
+ ---
2
+ name: remember
3
+ type: command
4
+ description: >
5
+ Ingest records into the shared eidetic memory store so they can be recalled
6
+ later. Drives `eidetic remember`: accepts one record as a JSON object, or a
7
+ batch as NDJSON on stdin for bulk ingest. Upsert is idempotent by id (and
8
+ dedups by content hash) — re-remembering updates in place, never duplicates.
9
+ Stamps a `created` date on every record at ingest time. Accepts `supersedes`
10
+ (id of the record this one replaces, for within-scope shadowing via `sweep`)
11
+ and `links` (list of related-memory ids). The store lives at
12
+ ~/.eidetic/memory (a home-dir path outside any git worktree), and the wrapper
13
+ defaults records to this agent's PERSONAL, PRIVATE scope (`--scope agentirc
14
+ --visibility private`, suffix read from culture.yaml) so they don't leak to a
15
+ default/other-scope recall — Claude and the colleague backend still share them
16
+ because both resolve the same suffix via this skill. Pass `--visibility public`
17
+ to contribute to the shared public pool instead. Use when the user says
18
+ "remember this", "store this", "save to memory", "index these", "eidetic
19
+ remember", or when something learned this session should outlive it. Pairs with
20
+ the sibling /recall skill.
21
+ ---
22
+
23
+ # remember — write to the shared eidetic memory
24
+
25
+ `remember` drives **`eidetic remember`**, the write half of the memory surface
26
+ (the read half is the sibling **/recall** skill). Records you store here are
27
+ recallable later by *any* agent on this machine — Claude or the colleague
28
+ backend — because the default store is one shared `~/.eidetic/memory` path.
29
+
30
+ ## How to run
31
+
32
+ ```bash
33
+ # One record (JSON object as the argument):
34
+ bash .claude/skills/remember/scripts/remember.sh \
35
+ '{"id":"d1","text":"Orin Nano draws 7-15W","type":"docs","metadata":{"source":"docs","permalink":"https://..."}}' --json
36
+
37
+ # Batch (NDJSON on stdin, one record per line) — for bulk re-index:
38
+ cat records.ndjson | bash .claude/skills/remember/scripts/remember.sh --json
39
+
40
+ # Record that supersedes an older one (same scope required for sweep to shadow):
41
+ bash .claude/skills/remember/scripts/remember.sh \
42
+ '{"id":"r2","text":"Updated Orin Nano draw: 10-20W","type":"note","supersedes":"r1","links":["r3"]}' --json
43
+ ```
44
+
45
+ The wrapper resolves the CLI portably (installed `eidetic` on `PATH`, else
46
+ `uv run eidetic` from the checkout) and forwards every flag verbatim.
47
+
48
+ ## Record shape
49
+
50
+ | Field | Required? | Notes |
51
+ |-------|-----------|-------|
52
+ | `id` | yes | stable identity; the upsert key |
53
+ | `text` | yes | the chunk being remembered |
54
+ | `type` | yes | e.g. `note`, `docs`, `discord`, a research object type |
55
+ | `hash` | optional | content hash for dedup; derived from `text` when omitted |
56
+ | `metadata` | recommended | provenance + facets; **round-trips verbatim** on recall |
57
+ | `created` | auto-stamped | ISO-8601 UTC date; stamped at ingest if absent; drives freshness signal age-decay |
58
+ | `supersedes` | optional | id of an earlier same-scope record this one replaces; `sweep` auto-shadows the target |
59
+ | `links` | optional | list of related-memory ids; persisted for future corroboration scoring |
60
+
61
+ `score` and `signal` are recall-only and are ignored on ingest. **Mind the
62
+ scope:** the default personal scope is **private** (`--scope agentirc
63
+ --visibility private`), so personal/role-gated notes stay isolated to this
64
+ agent's recall and are safe to store. Only when you deliberately write to a
65
+ **public** scope (`--visibility public`) does the record enter the shared pool
66
+ visible to every scope — keep public-scope records to public data only.
67
+
68
+ ## Idempotency
69
+
70
+ Re-submitting a record with the same `id` overwrites the previous value; a record
71
+ with a matching content `hash` is de-duplicated. So re-running an ingest (e.g. a
72
+ periodic re-scan) is safe and will not create duplicates.
73
+
74
+ ## Lifecycle — supersedes and sweep
75
+
76
+ Setting `supersedes` on a record declares that this record replaces an earlier one
77
+ **within the same scope**. The actual lifecycle transition (marking the older record
78
+ as `shadowed`) is applied by `eidetic sweep`, not by `remember` itself. Cross-scope
79
+ `supersedes` links are recorded but never auto-shadow (preserving the
80
+ public/private no-leak invariant).
81
+
82
+ To apply pending transitions after ingesting superseding records:
83
+
84
+ ```bash
85
+ eidetic sweep --dry-run # preview what would change
86
+ eidetic sweep # apply transitions
87
+ ```
88
+
89
+ ## Flags (forwarded to `eidetic remember`)
90
+
91
+ - `--json` — structured result (`{"upserted": N, "ids": [...]}`) to stdout.
92
+ - `--scope NAME` / `--visibility public|private` — record scope. **The wrapper
93
+ defaults this to the agent's PERSONAL, PRIVATE scope** — `--scope <suffix>
94
+ --visibility private`, where `<suffix>` is read from the nearest `culture.yaml`
95
+ (here, `agentirc`). Private records are served only to a recall in the same
96
+ scope, so they don't leak to a `default`/other-scope query. Pass `--scope` to
97
+ steer to a different scope (which then uses the plain CLI default visibility),
98
+ or `--visibility public` to keep the personal scope but make it shared. A wheel
99
+ install with no `culture.yaml` falls back to the CLI default `default`/`public`.
100
+ - `--backend files|mongo|neo4j` — default `files` (the shared home-dir store);
101
+ use `mongo`/`neo4j` (with `EIDETIC_MONGO_URI` / `NEO4J_URI`) for a server store.
102
+
103
+ ## Notes
104
+
105
+ - The embed endpoint defaults to the local model-gear embed gear
106
+ (`http://localhost:8002/v1`); override with `EIDETIC_EMBED_URL` /
107
+ `EIDETIC_EMBED_MODEL`. Ingest still works offline (embeddings are recomputed at
108
+ recall time).
109
+ - **Use the wrapper, not a bare `eidetic`.** The console script may not be on
110
+ `PATH` (in a dev checkout it isn't); the wrapper resolves it (`PATH` first, else
111
+ `uv run eidetic`). For the docs, run `eidetic explain remember` if installed,
112
+ otherwise `uv run --project <eidetic-cli checkout> eidetic explain remember`.
113
+
114
+ ## Provenance
115
+
116
+ First-party to **eidetic-cli** — eidetic owns its memory surface. Cite, don't
117
+ import: downstream repos copy this skill, they don't symlink it. See
118
+ [`docs/skill-sources.md`](../../../docs/skill-sources.md).
@@ -0,0 +1,164 @@
1
+ #!/usr/bin/env bash
2
+ # remember.sh — ingest records into the shared eidetic memory store (the /remember skill).
3
+ #
4
+ # Thin, portable wrapper around `eidetic remember`. It resolves the CLI, points
5
+ # the embedding endpoint at the local model-gear embed gear (overridable), and
6
+ # forwards every argument verbatim. Accepts ONE record as a JSON object argument,
7
+ # or a BATCH as NDJSON on stdin (one JSON object per line) for bulk ingest.
8
+ #
9
+ # remember.sh '{"id":"d1","text":"...","type":"docs","metadata":{...}}' --json
10
+ # cat records.ndjson | remember.sh --json
11
+ #
12
+ # Upsert is idempotent by id (and dedups by content hash): re-remembering the
13
+ # same record updates it in place, never duplicates.
14
+ #
15
+ # The store is the files backend. Default location resolves per-operation:
16
+ # PUBLIC records inside a git repo → <repo-root>/.eidetic/memory (committed,
17
+ # team-shared); PRIVATE records, or any record outside a git repo →
18
+ # $HOME/.eidetic/memory (never committed). An explicit EIDETIC_DATA_DIR still
19
+ # wins and short-circuits to that single dir. Use --backend mongo|neo4j (with
20
+ # EIDETIC_MONGO_URI / NEO4J_URI) for a server-backed shared store.
21
+
22
+ set -euo pipefail
23
+
24
+ # ── resolve the eidetic CLI (installed tool first, then dev checkout) ────────
25
+ EIDETIC=()
26
+ resolve_eidetic() {
27
+ if command -v eidetic >/dev/null 2>&1; then
28
+ EIDETIC=(eidetic)
29
+ return 0
30
+ fi
31
+ local dir
32
+ dir=$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)
33
+ while [ -n "$dir" ] && [ "$dir" != "/" ]; do
34
+ if [ -f "$dir/pyproject.toml" ] \
35
+ && grep -q '^name = "eidetic-cli"' "$dir/pyproject.toml" 2>/dev/null; then
36
+ if command -v uv >/dev/null 2>&1; then
37
+ EIDETIC=(uv run --project "$dir" eidetic)
38
+ return 0
39
+ fi
40
+ break
41
+ fi
42
+ dir=$(dirname "$dir")
43
+ done
44
+ # In a vendored copy there is no eidetic-cli checkout to fall back to, so the
45
+ # only honest remedy is to install the CLI. One `error:` + one `hint:` line.
46
+ printf 'error: eidetic CLI not found.\n' >&2
47
+ printf 'hint: install it with: uv tool install eidetic-cli (or pipx install eidetic-cli); the console script is eidetic.\n' >&2
48
+ return 1
49
+ }
50
+
51
+ usage() {
52
+ cat <<'EOF'
53
+ remember.sh — ingest records into the shared eidetic memory store (the /remember skill).
54
+
55
+ Usage:
56
+ remember.sh '<json-object>' [--json] [--backend files|mongo|neo4j] \
57
+ [--scope NAME] [--visibility public|private]
58
+ cat records.ndjson | remember.sh [--json] ...
59
+
60
+ A record needs `id`, `text`, and `type`; `hash` and `metadata` are recommended
61
+ (hash is derived from text when omitted). Upsert is idempotent by id.
62
+ Records default to this agent's PRIVATE personal scope (--scope from the
63
+ culture.yaml suffix); pass --visibility public to contribute to the shared
64
+ public pool. Every flag is forwarded verbatim to `eidetic remember`.
65
+ See `eidetic explain remember`.
66
+ EOF
67
+ }
68
+
69
+ case "${1:-}" in
70
+ -h | --help | help)
71
+ usage
72
+ exit 0
73
+ ;;
74
+ esac
75
+
76
+ # No record argument AND stdin is an interactive terminal → `eidetic remember`
77
+ # would block forever waiting for NDJSON. Show usage instead of hanging. A piped
78
+ # or redirected stdin (`cat records.ndjson | remember.sh`) is not a TTY and
79
+ # proceeds to the batch path normally.
80
+ if [ "$#" -eq 0 ] && [ -t 0 ]; then
81
+ usage >&2
82
+ printf 'hint: pass a JSON record as an argument, or pipe NDJSON on stdin.\n' >&2
83
+ exit 1
84
+ fi
85
+
86
+ resolve_eidetic || exit 2
87
+
88
+ # ── default to this agent's PERSONAL, PRIVATE scope (culture.yaml `suffix`) ──
89
+ # A record this agent remembers should land in its OWN personal scope, not the
90
+ # global `default` scope shared by every project on this host. We read the
91
+ # `suffix` from the nearest culture.yaml (walking up from this script), so the
92
+ # scope follows the repo identity rather than being hard-coded — a downstream
93
+ # cite-don't-import copy adapts to its own suffix, and the colleague backend
94
+ # (running in a worktree of this same repo) resolves the same suffix, keeping
95
+ # the Claude↔colleague shared-memory story intact.
96
+ #
97
+ # The personal scope is PRIVATE by default: in eidetic's model only a private
98
+ # record is isolated to its scope (`can_serve`), so private is what actually
99
+ # keeps these records from leaking to a default/other-scope recall. Scope and
100
+ # visibility are paired — the private default applies only when we inject the
101
+ # resolved scope, and only if the caller didn't pass --visibility (so an
102
+ # explicit `--visibility public` still wins). An explicit --scope on the command
103
+ # line takes over steering entirely; a wheel install with no culture.yaml falls
104
+ # back to the plain CLI default (`default`/`public`).
105
+ resolve_scope() {
106
+ local dir suffix=""
107
+ dir=$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)
108
+ while [ -n "$dir" ] && [ "$dir" != "/" ]; do
109
+ if [ -f "$dir/culture.yaml" ]; then
110
+ # Capture only the first non-space token after `suffix:` (so an
111
+ # inline `# comment` or trailing space can't bleed into the scope),
112
+ # then strip surrounding quotes only — matching the canonical parser
113
+ # in .claude/skills/cicd/scripts/_resolve-nick.sh.
114
+ # `|| true`: under `set -o pipefail`, `head -n1` closing the pipe
115
+ # early can SIGPIPE `sed`, making the substitution non-zero and
116
+ # aborting the script. An empty parse must yield "" here, not exit.
117
+ suffix=$(sed -n \
118
+ 's/^[[:space:]]*-\{0,1\}[[:space:]]*suffix:[[:space:]]*\([^[:space:]]*\).*/\1/p' \
119
+ "$dir/culture.yaml" | head -n1 | tr -d "\"'" || true)
120
+ break
121
+ fi
122
+ dir=$(dirname "$dir")
123
+ done
124
+ printf '%s' "$suffix"
125
+ }
126
+
127
+ has_flag() {
128
+ local needle=$1
129
+ shift
130
+ local a
131
+ for a in "$@"; do
132
+ case "$a" in
133
+ "$needle" | "$needle"=*) return 0 ;;
134
+ esac
135
+ done
136
+ return 1
137
+ }
138
+
139
+ SCOPE_ARGS=()
140
+ if ! has_flag --scope "$@"; then
141
+ EIDETIC_SCOPE=$(resolve_scope)
142
+ if [ -n "$EIDETIC_SCOPE" ]; then
143
+ SCOPE_ARGS+=(--scope "$EIDETIC_SCOPE")
144
+ # rollout-cli eidetic-memory recipe POLICY OVERRIDE (not eidetic's
145
+ # upstream private default): default to PUBLIC so a plain remember lands
146
+ # in <repo>/.eidetic/memory — committed, team- and mesh-shared. Pass
147
+ # --visibility private to keep a record in $HOME (uncommitted).
148
+ has_flag --visibility "$@" || SCOPE_ARGS+=(--visibility public)
149
+ elif ! has_flag --visibility "$@"; then
150
+ # No suffix AND no explicit --visibility: the record falls back to
151
+ # eidetic's own default (scope=default, visibility=public). Don't let an
152
+ # expected-private record go public silently — warn on stderr (stdout
153
+ # stays clean for --json). Warn ONLY here: an explicit --scope (outer
154
+ # guard) or --visibility (this guard) means the caller chose deliberately
155
+ # and is honored verbatim, so either flag silences this.
156
+ printf 'warning: no culture.yaml suffix resolved; this record falls back to the public default scope. Pass --scope or --visibility to place it deliberately.\n' >&2
157
+ fi
158
+ fi
159
+
160
+ : "${EIDETIC_EMBED_URL:=http://localhost:8002/v1}"
161
+ : "${EIDETIC_EMBED_MODEL:=Qwen/Qwen3-Embedding-0.6B}"
162
+ export EIDETIC_EMBED_URL EIDETIC_EMBED_MODEL
163
+
164
+ exec "${EIDETIC[@]}" remember "${SCOPE_ARGS[@]}" "$@"
@@ -0,0 +1 @@
1
+ agentirc-ships-an-agent-accessibility-release-ai-a
@@ -0,0 +1 @@
1
+ agentirc-ships-an-agent-accessibility-release-ai-a