agentic-cli 0.5.0__tar.gz → 0.5.1__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 (220) hide show
  1. {agentic_cli-0.5.0 → agentic_cli-0.5.1}/CHANGELOG.md +26 -0
  2. {agentic_cli-0.5.0 → agentic_cli-0.5.1}/CLAUDE.md +7 -3
  3. {agentic_cli-0.5.0 → agentic_cli-0.5.1}/PKG-INFO +36 -32
  4. {agentic_cli-0.5.0 → agentic_cli-0.5.1}/README.md +33 -31
  5. {agentic_cli-0.5.0 → agentic_cli-0.5.1}/pyproject.toml +3 -1
  6. {agentic_cli-0.5.0 → agentic_cli-0.5.1}/src/agentic_cli/__init__.py +1 -1
  7. agentic_cli-0.5.1/src/agentic_cli/knowledge_base/_bm25_backends.py +137 -0
  8. {agentic_cli-0.5.0 → agentic_cli-0.5.1}/src/agentic_cli/knowledge_base/manager.py +61 -18
  9. {agentic_cli-0.5.0 → agentic_cli-0.5.1}/src/agentic_cli/tools/__init__.py +0 -5
  10. {agentic_cli-0.5.0 → agentic_cli-0.5.1}/src/agentic_cli/tools/adk/state_tools.py +6 -0
  11. {agentic_cli-0.5.0 → agentic_cli-0.5.1}/src/agentic_cli/tools/arxiv_tools.py +7 -4
  12. {agentic_cli-0.5.0 → agentic_cli-0.5.1}/src/agentic_cli/tools/execution_tools.py +2 -2
  13. {agentic_cli-0.5.0 → agentic_cli-0.5.1}/src/agentic_cli/tools/factories.py +27 -75
  14. {agentic_cli-0.5.0 → agentic_cli-0.5.1}/src/agentic_cli/tools/file_read.py +3 -5
  15. {agentic_cli-0.5.0 → agentic_cli-0.5.1}/src/agentic_cli/tools/file_write.py +3 -4
  16. {agentic_cli-0.5.0 → agentic_cli-0.5.1}/src/agentic_cli/tools/glob_tool.py +3 -5
  17. {agentic_cli-0.5.0 → agentic_cli-0.5.1}/src/agentic_cli/tools/grep_tool.py +2 -4
  18. {agentic_cli-0.5.0 → agentic_cli-0.5.1}/src/agentic_cli/tools/interaction_tools.py +2 -2
  19. {agentic_cli-0.5.0 → agentic_cli-0.5.1}/src/agentic_cli/tools/knowledge_tools.py +8 -9
  20. {agentic_cli-0.5.0 → agentic_cli-0.5.1}/src/agentic_cli/tools/langgraph/state_tools.py +6 -0
  21. {agentic_cli-0.5.0 → agentic_cli-0.5.1}/src/agentic_cli/tools/memory_tools.py +87 -48
  22. {agentic_cli-0.5.0 → agentic_cli-0.5.1}/src/agentic_cli/tools/reflection_tools.py +3 -2
  23. {agentic_cli-0.5.0 → agentic_cli-0.5.1}/src/agentic_cli/tools/registry.py +49 -25
  24. {agentic_cli-0.5.0 → agentic_cli-0.5.1}/src/agentic_cli/tools/sandbox/__init__.py +3 -2
  25. {agentic_cli-0.5.0 → agentic_cli-0.5.1}/src/agentic_cli/tools/search.py +2 -2
  26. {agentic_cli-0.5.0 → agentic_cli-0.5.1}/src/agentic_cli/tools/shell/executor.py +3 -3
  27. {agentic_cli-0.5.0 → agentic_cli-0.5.1}/src/agentic_cli/tools/webfetch_tool.py +3 -2
  28. {agentic_cli-0.5.0 → agentic_cli-0.5.1}/src/agentic_cli/workflow/adk/__init__.py +1 -1
  29. {agentic_cli-0.5.0 → agentic_cli-0.5.1}/src/agentic_cli/workflow/adk/manager.py +3 -2
  30. agentic_cli-0.5.1/src/agentic_cli/workflow/adk/permission_plugin.py +72 -0
  31. {agentic_cli-0.5.0 → agentic_cli-0.5.1}/src/agentic_cli/workflow/adk/plugins.py +1 -97
  32. {agentic_cli-0.5.0 → agentic_cli-0.5.1}/src/agentic_cli/workflow/base_manager.py +20 -4
  33. {agentic_cli-0.5.0 → agentic_cli-0.5.1}/src/agentic_cli/workflow/langgraph/graph_builder.py +2 -45
  34. agentic_cli-0.5.1/src/agentic_cli/workflow/langgraph/permission_wrap.py +58 -0
  35. agentic_cli-0.5.1/src/agentic_cli/workflow/permissions/__init__.py +37 -0
  36. agentic_cli-0.5.1/src/agentic_cli/workflow/permissions/capabilities.py +44 -0
  37. agentic_cli-0.5.1/src/agentic_cli/workflow/permissions/engine.py +226 -0
  38. agentic_cli-0.5.1/src/agentic_cli/workflow/permissions/matchers.py +193 -0
  39. agentic_cli-0.5.1/src/agentic_cli/workflow/permissions/prompt.py +66 -0
  40. agentic_cli-0.5.1/src/agentic_cli/workflow/permissions/rules.py +39 -0
  41. agentic_cli-0.5.1/src/agentic_cli/workflow/permissions/store.py +114 -0
  42. {agentic_cli-0.5.0 → agentic_cli-0.5.1}/src/agentic_cli/workflow/service_registry.py +4 -3
  43. {agentic_cli-0.5.0 → agentic_cli-0.5.1}/src/agentic_cli/workflow/settings.py +26 -6
  44. agentic_cli-0.5.1/tests/integration/test_permission_adk.py +141 -0
  45. agentic_cli-0.5.1/tests/integration/test_permission_langgraph.py +110 -0
  46. agentic_cli-0.5.1/tests/permissions/test_capabilities.py +47 -0
  47. agentic_cli-0.5.1/tests/permissions/test_engine.py +435 -0
  48. agentic_cli-0.5.1/tests/permissions/test_matchers.py +175 -0
  49. agentic_cli-0.5.1/tests/permissions/test_prompt.py +92 -0
  50. agentic_cli-0.5.1/tests/permissions/test_rules.py +47 -0
  51. agentic_cli-0.5.1/tests/permissions/test_store.py +179 -0
  52. {agentic_cli-0.5.0 → agentic_cli-0.5.1}/tests/test_adk_plugins.py +4 -228
  53. agentic_cli-0.5.1/tests/test_bm25_index.py +188 -0
  54. {agentic_cli-0.5.0 → agentic_cli-0.5.1}/tests/test_config.py +0 -4
  55. {agentic_cli-0.5.0 → agentic_cli-0.5.1}/tests/test_kb_sidecar.py +71 -0
  56. {agentic_cli-0.5.0 → agentic_cli-0.5.1}/tests/test_memory.py +83 -0
  57. {agentic_cli-0.5.0 → agentic_cli-0.5.1}/tests/test_tools.py +28 -86
  58. agentic_cli-0.5.1/tests/tools/test_registry.py +66 -0
  59. agentic_cli-0.5.1/tests/workflow/__init__.py +0 -0
  60. agentic_cli-0.5.1/tests/workflow/test_backend_isolation.py +56 -0
  61. agentic_cli-0.5.1/tests/workflow/test_base_manager_permissions.py +27 -0
  62. agentic_cli-0.5.1/tests/workflow/test_settings_permissions.py +37 -0
  63. agentic_cli-0.5.0/src/agentic_cli/tools/hitl_tools.py +0 -81
  64. agentic_cli-0.5.0/tests/test_bm25_index.py +0 -67
  65. agentic_cli-0.5.0/tests/test_hitl.py +0 -61
  66. agentic_cli-0.5.0/tests/test_langgraph_confirmation.py +0 -134
  67. {agentic_cli-0.5.0 → agentic_cli-0.5.1}/.gitignore +0 -0
  68. {agentic_cli-0.5.0 → agentic_cli-0.5.1}/LICENSE +0 -0
  69. {agentic_cli-0.5.0 → agentic_cli-0.5.1}/environment.yml +0 -0
  70. {agentic_cli-0.5.0 → agentic_cli-0.5.1}/examples/arxiv_demo.py +0 -0
  71. {agentic_cli-0.5.0 → agentic_cli-0.5.1}/examples/fileops_demo.py +0 -0
  72. {agentic_cli-0.5.0 → agentic_cli-0.5.1}/examples/hello_agent.py +0 -0
  73. {agentic_cli-0.5.0 → agentic_cli-0.5.1}/examples/hello_langgraph.py +0 -0
  74. {agentic_cli-0.5.0 → agentic_cli-0.5.1}/examples/memory_demo.py +0 -0
  75. {agentic_cli-0.5.0 → agentic_cli-0.5.1}/examples/research_demo/__init__.py +0 -0
  76. {agentic_cli-0.5.0 → agentic_cli-0.5.1}/examples/research_demo/__main__.py +0 -0
  77. {agentic_cli-0.5.0 → agentic_cli-0.5.1}/examples/research_demo/agents.py +0 -0
  78. {agentic_cli-0.5.0 → agentic_cli-0.5.1}/examples/research_demo/app.py +0 -0
  79. {agentic_cli-0.5.0 → agentic_cli-0.5.1}/examples/research_demo/commands.py +0 -0
  80. {agentic_cli-0.5.0 → agentic_cli-0.5.1}/examples/research_demo/settings.py +0 -0
  81. {agentic_cli-0.5.0 → agentic_cli-0.5.1}/examples/shell_demo.py +0 -0
  82. {agentic_cli-0.5.0 → agentic_cli-0.5.1}/examples/webfetch_demo.py +0 -0
  83. {agentic_cli-0.5.0 → agentic_cli-0.5.1}/examples/websearch_demo.py +0 -0
  84. {agentic_cli-0.5.0 → agentic_cli-0.5.1}/requirements-dev.txt +0 -0
  85. {agentic_cli-0.5.0 → agentic_cli-0.5.1}/requirements.txt +0 -0
  86. {agentic_cli-0.5.0 → agentic_cli-0.5.1}/src/agentic_cli/cli/__init__.py +0 -0
  87. {agentic_cli-0.5.0 → agentic_cli-0.5.1}/src/agentic_cli/cli/app.py +0 -0
  88. {agentic_cli-0.5.0 → agentic_cli-0.5.1}/src/agentic_cli/cli/builtin_commands.py +0 -0
  89. {agentic_cli-0.5.0 → agentic_cli-0.5.1}/src/agentic_cli/cli/commands.py +0 -0
  90. {agentic_cli-0.5.0 → agentic_cli-0.5.1}/src/agentic_cli/cli/message_processor.py +0 -0
  91. {agentic_cli-0.5.0 → agentic_cli-0.5.1}/src/agentic_cli/cli/settings_command.py +0 -0
  92. {agentic_cli-0.5.0 → agentic_cli-0.5.1}/src/agentic_cli/cli/settings_introspection.py +0 -0
  93. {agentic_cli-0.5.0 → agentic_cli-0.5.1}/src/agentic_cli/cli/usage_tracker.py +0 -0
  94. {agentic_cli-0.5.0 → agentic_cli-0.5.1}/src/agentic_cli/cli/workflow_controller.py +0 -0
  95. {agentic_cli-0.5.0 → agentic_cli-0.5.1}/src/agentic_cli/config.py +0 -0
  96. {agentic_cli-0.5.0 → agentic_cli-0.5.1}/src/agentic_cli/constants.py +0 -0
  97. {agentic_cli-0.5.0 → agentic_cli-0.5.1}/src/agentic_cli/file_utils.py +0 -0
  98. {agentic_cli-0.5.0 → agentic_cli-0.5.1}/src/agentic_cli/knowledge_base/__init__.py +0 -0
  99. {agentic_cli-0.5.0 → agentic_cli-0.5.1}/src/agentic_cli/knowledge_base/_mock_bm25.py +0 -0
  100. {agentic_cli-0.5.0 → agentic_cli-0.5.1}/src/agentic_cli/knowledge_base/_mocks.py +0 -0
  101. {agentic_cli-0.5.0 → agentic_cli-0.5.1}/src/agentic_cli/knowledge_base/bm25_index.py +0 -0
  102. {agentic_cli-0.5.0 → agentic_cli-0.5.1}/src/agentic_cli/knowledge_base/concepts.py +0 -0
  103. {agentic_cli-0.5.0 → agentic_cli-0.5.1}/src/agentic_cli/knowledge_base/embeddings.py +0 -0
  104. {agentic_cli-0.5.0 → agentic_cli-0.5.1}/src/agentic_cli/knowledge_base/models.py +0 -0
  105. {agentic_cli-0.5.0 → agentic_cli-0.5.1}/src/agentic_cli/knowledge_base/sidecar.py +0 -0
  106. {agentic_cli-0.5.0 → agentic_cli-0.5.1}/src/agentic_cli/knowledge_base/sources.py +0 -0
  107. {agentic_cli-0.5.0 → agentic_cli-0.5.1}/src/agentic_cli/knowledge_base/vector_store.py +0 -0
  108. {agentic_cli-0.5.0 → agentic_cli-0.5.1}/src/agentic_cli/logging.py +0 -0
  109. {agentic_cli-0.5.0 → agentic_cli-0.5.1}/src/agentic_cli/persistence/__init__.py +0 -0
  110. {agentic_cli-0.5.0 → agentic_cli-0.5.1}/src/agentic_cli/persistence/session.py +0 -0
  111. {agentic_cli-0.5.0 → agentic_cli-0.5.1}/src/agentic_cli/settings_mixins.py +0 -0
  112. {agentic_cli-0.5.0 → agentic_cli-0.5.1}/src/agentic_cli/settings_persistence.py +0 -0
  113. {agentic_cli-0.5.0 → agentic_cli-0.5.1}/src/agentic_cli/tools/_core/__init__.py +0 -0
  114. {agentic_cli-0.5.0 → agentic_cli-0.5.1}/src/agentic_cli/tools/_core/planning.py +0 -0
  115. {agentic_cli-0.5.0 → agentic_cli-0.5.1}/src/agentic_cli/tools/_core/tasks.py +0 -0
  116. {agentic_cli-0.5.0 → agentic_cli-0.5.1}/src/agentic_cli/tools/adk/__init__.py +0 -0
  117. {agentic_cli-0.5.0 → agentic_cli-0.5.1}/src/agentic_cli/tools/arxiv_source.py +0 -0
  118. {agentic_cli-0.5.0 → agentic_cli-0.5.1}/src/agentic_cli/tools/executor.py +0 -0
  119. {agentic_cli-0.5.0 → agentic_cli-0.5.1}/src/agentic_cli/tools/langgraph/__init__.py +0 -0
  120. {agentic_cli-0.5.0 → agentic_cli-0.5.1}/src/agentic_cli/tools/pdf_utils.py +0 -0
  121. {agentic_cli-0.5.0 → agentic_cli-0.5.1}/src/agentic_cli/tools/sandbox/backends/__init__.py +0 -0
  122. {agentic_cli-0.5.0 → agentic_cli-0.5.1}/src/agentic_cli/tools/sandbox/backends/base.py +0 -0
  123. {agentic_cli-0.5.0 → agentic_cli-0.5.1}/src/agentic_cli/tools/sandbox/backends/jupyter_local.py +0 -0
  124. {agentic_cli-0.5.0 → agentic_cli-0.5.1}/src/agentic_cli/tools/sandbox/manager.py +0 -0
  125. {agentic_cli-0.5.0 → agentic_cli-0.5.1}/src/agentic_cli/tools/sandbox/models.py +0 -0
  126. {agentic_cli-0.5.0 → agentic_cli-0.5.1}/src/agentic_cli/tools/shell/__init__.py +0 -0
  127. {agentic_cli-0.5.0 → agentic_cli-0.5.1}/src/agentic_cli/tools/shell/audit.py +0 -0
  128. {agentic_cli-0.5.0 → agentic_cli-0.5.1}/src/agentic_cli/tools/shell/classifier.py +0 -0
  129. {agentic_cli-0.5.0 → agentic_cli-0.5.1}/src/agentic_cli/tools/shell/config.py +0 -0
  130. {agentic_cli-0.5.0 → agentic_cli-0.5.1}/src/agentic_cli/tools/shell/models.py +0 -0
  131. {agentic_cli-0.5.0 → agentic_cli-0.5.1}/src/agentic_cli/tools/shell/os_sandbox/__init__.py +0 -0
  132. {agentic_cli-0.5.0 → agentic_cli-0.5.1}/src/agentic_cli/tools/shell/os_sandbox/base.py +0 -0
  133. {agentic_cli-0.5.0 → agentic_cli-0.5.1}/src/agentic_cli/tools/shell/os_sandbox/bubblewrap.py +0 -0
  134. {agentic_cli-0.5.0 → agentic_cli-0.5.1}/src/agentic_cli/tools/shell/os_sandbox/detect.py +0 -0
  135. {agentic_cli-0.5.0 → agentic_cli-0.5.1}/src/agentic_cli/tools/shell/os_sandbox/policy.py +0 -0
  136. {agentic_cli-0.5.0 → agentic_cli-0.5.1}/src/agentic_cli/tools/shell/os_sandbox/seatbelt.py +0 -0
  137. {agentic_cli-0.5.0 → agentic_cli-0.5.1}/src/agentic_cli/tools/shell/os_sandbox/seatbelt_profile.py +0 -0
  138. {agentic_cli-0.5.0 → agentic_cli-0.5.1}/src/agentic_cli/tools/shell/path_analyzer.py +0 -0
  139. {agentic_cli-0.5.0 → agentic_cli-0.5.1}/src/agentic_cli/tools/shell/preprocessor.py +0 -0
  140. {agentic_cli-0.5.0 → agentic_cli-0.5.1}/src/agentic_cli/tools/shell/risk_assessor.py +0 -0
  141. {agentic_cli-0.5.0 → agentic_cli-0.5.1}/src/agentic_cli/tools/shell/sandbox.py +0 -0
  142. {agentic_cli-0.5.0 → agentic_cli-0.5.1}/src/agentic_cli/tools/shell/tokenizer.py +0 -0
  143. {agentic_cli-0.5.0 → agentic_cli-0.5.1}/src/agentic_cli/tools/webfetch/__init__.py +0 -0
  144. {agentic_cli-0.5.0 → agentic_cli-0.5.1}/src/agentic_cli/tools/webfetch/converter.py +0 -0
  145. {agentic_cli-0.5.0 → agentic_cli-0.5.1}/src/agentic_cli/tools/webfetch/fetcher.py +0 -0
  146. {agentic_cli-0.5.0 → agentic_cli-0.5.1}/src/agentic_cli/tools/webfetch/robots.py +0 -0
  147. {agentic_cli-0.5.0 → agentic_cli-0.5.1}/src/agentic_cli/tools/webfetch/summarizer.py +0 -0
  148. {agentic_cli-0.5.0 → agentic_cli-0.5.1}/src/agentic_cli/tools/webfetch/validator.py +0 -0
  149. {agentic_cli-0.5.0 → agentic_cli-0.5.1}/src/agentic_cli/workflow/__init__.py +0 -0
  150. {agentic_cli-0.5.0 → agentic_cli-0.5.1}/src/agentic_cli/workflow/adk/event_processor.py +0 -0
  151. {agentic_cli-0.5.0 → agentic_cli-0.5.1}/src/agentic_cli/workflow/adk/task_progress_plugin.py +0 -0
  152. {agentic_cli-0.5.0 → agentic_cli-0.5.1}/src/agentic_cli/workflow/config.py +0 -0
  153. {agentic_cli-0.5.0 → agentic_cli-0.5.1}/src/agentic_cli/workflow/events.py +0 -0
  154. {agentic_cli-0.5.0 → agentic_cli-0.5.1}/src/agentic_cli/workflow/factory.py +0 -0
  155. {agentic_cli-0.5.0 → agentic_cli-0.5.1}/src/agentic_cli/workflow/langgraph/__init__.py +0 -0
  156. {agentic_cli-0.5.0 → agentic_cli-0.5.1}/src/agentic_cli/workflow/langgraph/manager.py +0 -0
  157. {agentic_cli-0.5.0 → agentic_cli-0.5.1}/src/agentic_cli/workflow/langgraph/persistence/__init__.py +0 -0
  158. {agentic_cli-0.5.0 → agentic_cli-0.5.1}/src/agentic_cli/workflow/langgraph/persistence/checkpointers.py +0 -0
  159. {agentic_cli-0.5.0 → agentic_cli-0.5.1}/src/agentic_cli/workflow/langgraph/persistence/stores.py +0 -0
  160. {agentic_cli-0.5.0 → agentic_cli-0.5.1}/src/agentic_cli/workflow/langgraph/state.py +0 -0
  161. {agentic_cli-0.5.0 → agentic_cli-0.5.1}/src/agentic_cli/workflow/models.py +0 -0
  162. {agentic_cli-0.5.0 → agentic_cli-0.5.1}/src/agentic_cli/workflow/retry.py +0 -0
  163. {agentic_cli-0.5.0 → agentic_cli-0.5.1}/src/agentic_cli/workflow/tool_summaries.py +0 -0
  164. {agentic_cli-0.5.0 → agentic_cli-0.5.1}/tests/__init__.py +0 -0
  165. {agentic_cli-0.5.0 → agentic_cli-0.5.1}/tests/conftest.py +0 -0
  166. {agentic_cli-0.5.0 → agentic_cli-0.5.1}/tests/integration/__init__.py +0 -0
  167. {agentic_cli-0.5.0 → agentic_cli-0.5.1}/tests/integration/conftest.py +0 -0
  168. {agentic_cli-0.5.0 → agentic_cli-0.5.1}/tests/integration/helpers.py +0 -0
  169. {agentic_cli-0.5.0 → agentic_cli-0.5.1}/tests/integration/test_adk_integration.py +0 -0
  170. {agentic_cli-0.5.0 → agentic_cli-0.5.1}/tests/integration/test_kb_research_flow.py +0 -0
  171. {agentic_cli-0.5.0 → agentic_cli-0.5.1}/tests/integration/test_langgraph_integration.py +0 -0
  172. {agentic_cli-0.5.0 → agentic_cli-0.5.1}/tests/integration/test_live.py +0 -0
  173. {agentic_cli-0.5.0/tests/workflow → agentic_cli-0.5.1/tests/permissions}/__init__.py +0 -0
  174. {agentic_cli-0.5.0 → agentic_cli-0.5.1}/tests/test_adk_event_processor.py +0 -0
  175. {agentic_cli-0.5.0 → agentic_cli-0.5.1}/tests/test_arxiv_tools.py +0 -0
  176. {agentic_cli-0.5.0 → agentic_cli-0.5.1}/tests/test_commands.py +0 -0
  177. {agentic_cli-0.5.0 → agentic_cli-0.5.1}/tests/test_concept_store.py +0 -0
  178. {agentic_cli-0.5.0 → agentic_cli-0.5.1}/tests/test_concurrent_stores.py +0 -0
  179. {agentic_cli-0.5.0 → agentic_cli-0.5.1}/tests/test_context_trimming.py +0 -0
  180. {agentic_cli-0.5.0 → agentic_cli-0.5.1}/tests/test_context_window.py +0 -0
  181. {agentic_cli-0.5.0 → agentic_cli-0.5.1}/tests/test_dual_thinking_boxes.py +0 -0
  182. {agentic_cli-0.5.0 → agentic_cli-0.5.1}/tests/test_embeddings.py +0 -0
  183. {agentic_cli-0.5.0 → agentic_cli-0.5.1}/tests/test_grep_tool_cache.py +0 -0
  184. {agentic_cli-0.5.0 → agentic_cli-0.5.1}/tests/test_input_callback.py +0 -0
  185. {agentic_cli-0.5.0 → agentic_cli-0.5.1}/tests/test_kb_concepts.py +0 -0
  186. {agentic_cli-0.5.0 → agentic_cli-0.5.1}/tests/test_kb_helpers.py +0 -0
  187. {agentic_cli-0.5.0 → agentic_cli-0.5.1}/tests/test_kb_index_md.py +0 -0
  188. {agentic_cli-0.5.0 → agentic_cli-0.5.1}/tests/test_kb_ingest_log.py +0 -0
  189. {agentic_cli-0.5.0 → agentic_cli-0.5.1}/tests/test_kb_tool_renames.py +0 -0
  190. {agentic_cli-0.5.0 → agentic_cli-0.5.1}/tests/test_knowledge_base.py +0 -0
  191. {agentic_cli-0.5.0 → agentic_cli-0.5.1}/tests/test_knowledge_tools.py +0 -0
  192. {agentic_cli-0.5.0 → agentic_cli-0.5.1}/tests/test_langgraph.py +0 -0
  193. {agentic_cli-0.5.0 → agentic_cli-0.5.1}/tests/test_model_registry.py +0 -0
  194. {agentic_cli-0.5.0 → agentic_cli-0.5.1}/tests/test_persistence.py +0 -0
  195. {agentic_cli-0.5.0 → agentic_cli-0.5.1}/tests/test_planning.py +0 -0
  196. {agentic_cli-0.5.0 → agentic_cli-0.5.1}/tests/test_reflection.py +0 -0
  197. {agentic_cli-0.5.0 → agentic_cli-0.5.1}/tests/test_session_save_resume.py +0 -0
  198. {agentic_cli-0.5.0 → agentic_cli-0.5.1}/tests/test_settings_persistence.py +0 -0
  199. {agentic_cli-0.5.0 → agentic_cli-0.5.1}/tests/test_task_tools.py +0 -0
  200. {agentic_cli-0.5.0 → agentic_cli-0.5.1}/tests/test_token_caching.py +0 -0
  201. {agentic_cli-0.5.0 → agentic_cli-0.5.1}/tests/test_tool_summaries.py +0 -0
  202. {agentic_cli-0.5.0 → agentic_cli-0.5.1}/tests/test_update_setting.py +0 -0
  203. {agentic_cli-0.5.0 → agentic_cli-0.5.1}/tests/test_usage_tracker.py +0 -0
  204. {agentic_cli-0.5.0 → agentic_cli-0.5.1}/tests/test_vector_store.py +0 -0
  205. {agentic_cli-0.5.0 → agentic_cli-0.5.1}/tests/test_webfetch.py +0 -0
  206. {agentic_cli-0.5.0 → agentic_cli-0.5.1}/tests/test_workflow.py +0 -0
  207. {agentic_cli-0.5.0 → agentic_cli-0.5.1}/tests/test_workflow_controller.py +0 -0
  208. {agentic_cli-0.5.0 → agentic_cli-0.5.1}/tests/tools/__init__.py +0 -0
  209. {agentic_cli-0.5.0 → agentic_cli-0.5.1}/tests/tools/test_adk_state_tools.py +0 -0
  210. {agentic_cli-0.5.0 → agentic_cli-0.5.1}/tests/tools/test_bubblewrap.py +0 -0
  211. {agentic_cli-0.5.0 → agentic_cli-0.5.1}/tests/tools/test_core_planning.py +0 -0
  212. {agentic_cli-0.5.0 → agentic_cli-0.5.1}/tests/tools/test_core_tasks.py +0 -0
  213. {agentic_cli-0.5.0 → agentic_cli-0.5.1}/tests/tools/test_factories.py +0 -0
  214. {agentic_cli-0.5.0 → agentic_cli-0.5.1}/tests/tools/test_langgraph_state_tools.py +0 -0
  215. {agentic_cli-0.5.0 → agentic_cli-0.5.1}/tests/tools/test_os_sandbox.py +0 -0
  216. {agentic_cli-0.5.0 → agentic_cli-0.5.1}/tests/tools/test_sandbox.py +0 -0
  217. {agentic_cli-0.5.0 → agentic_cli-0.5.1}/tests/tools/test_seatbelt_profile.py +0 -0
  218. {agentic_cli-0.5.0 → agentic_cli-0.5.1}/tests/tools/test_shell_security.py +0 -0
  219. {agentic_cli-0.5.0 → agentic_cli-0.5.1}/tests/workflow/test_task_progress_plugin.py +0 -0
  220. {agentic_cli-0.5.0 → agentic_cli-0.5.1}/tests/workflow/test_tool_assembly.py +0 -0
@@ -5,6 +5,32 @@ All notable changes to this project will be documented in this file.
5
5
  The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
6
6
  and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
7
7
 
8
+ ## [0.5.1] - 2026-04-18
9
+
10
+ ### Added
11
+ - **Capability-based permission engine** (PR #72): Framework-independent engine that replaces the ConfirmationPlugin-based HITL. Tools declare capabilities (e.g. `filesystem.write(path=...)`, `http.read`, `shell.exec`) via `@register_tool(capabilities=...)`. Rules are evaluated from four sources — builtin defaults, user `~/.{app_name}/settings.json`, project `./.{app_name}/settings.json`, and in-memory session. When no rule matches, the user is prompted with `Allow once / for session / always (save to project) / Deny`; "always" grants persist into project settings.
12
+ - **Matchers**: `PathMatcher` (with `**` glob), `URLMatcher`, `ShellMatcher`, and `StringGlobMatcher`.
13
+ - **ADK `PermissionPlugin`**: capability gating for ADK agents.
14
+ - **LangGraph `wrap_tool_for_permission`**: per-tool permission wrapping for LangGraph `ToolNode`.
15
+ - **`permissions` and `permissions_enabled` settings** for runtime control.
16
+ - **`EXEMPT` sentinel**: opts a tool out of the permission engine (used for backend state tools and ADK `transfer_to_agent`).
17
+ - **Real BM25 backends**: `bm25s` (preferred) and `rank_bm25` fallback.
18
+
19
+ ### Changed
20
+ - **`@register_tool`** now requires the `capabilities=` kwarg.
21
+ - **Default grants broadened**: `filesystem.*` auto-extends to the parent directory; `memory.*` and `kb.*` are allowed by default.
22
+ - **Ask prompt** now shows the directory-scope target.
23
+ - **Workflow init**: `_ensure_managers_initialized` runs in a worker thread to avoid blocking the event loop.
24
+ - **HITL confirmation** extracted into a backend-neutral module during the permissions migration.
25
+ - **Memory tools**: deduped registry-bound and factory-bound entry points.
26
+
27
+ ### Fixed
28
+ - Matcher canonicalize/matches preserves the `*` wildcard.
29
+ - `KnowledgeBaseManager` concurrency contract tightened.
30
+
31
+ ### Removed
32
+ - **`PermissionLevel`** (SAFE / CAUTION / DANGEROUS), `ConfirmationPlugin`, `_wrap_for_confirmation`, `hitl_tools`, and the `hitl_enabled` setting — superseded by the permission engine.
33
+
8
34
  ## [0.5.0] - 2026-04-17
9
35
 
10
36
  ### Added
@@ -47,7 +47,7 @@ agentic-cli/
47
47
  │ │ ├── persistence/ # Checkpointers, stores
48
48
  │ │ └── tools/ # LangChain-compatible wrappers
49
49
  │ ├── tools/
50
- │ │ ├── registry.py # ToolRegistry, @register_tool, ToolCategory, PermissionLevel
50
+ │ │ ├── registry.py # ToolRegistry, @register_tool, ToolCategory
51
51
  │ │ ├── executor.py # SafePythonExecutor
52
52
  │ │ ├── knowledge_tools.py # kb_search, kb_ingest, kb_list, kb_read
53
53
  │ │ ├── arxiv_tools.py # search_arxiv, fetch_arxiv_paper, analyze_arxiv_paper
@@ -62,7 +62,7 @@ agentic-cli/
62
62
  │ │ ├── memory_tools.py # save_memory, search_memory + MemoryStore
63
63
  │ │ ├── planning_tools.py # save_plan, get_plan + PlanStore
64
64
  │ │ ├── task_tools.py # save_tasks, get_tasks + TaskStore
65
- │ │ ├── hitl_tools.py # request_approval + ApprovalManager, HITLConfig
65
+ │ │ ├── reflection_tools.py # save_reflection + ToolReflectionStore
66
66
  │ │ ├── shell/ # 8-layer shell security
67
67
  │ │ └── webfetch/ # Fetcher, converter, validator, robots
68
68
  │ ├── knowledge_base/
@@ -114,6 +114,9 @@ Workflow:
114
114
  2. For features: create `feature/<name>` (or `fix/<name>` or `refactor/<name>`) from `develop`, work there, merge back to `develop`
115
115
  3. When ready to release: merge `develop` → `main` and tag the release
116
116
 
117
+ ### What NOT to commit
118
+ - `docs/` is gitignored on purpose (see `.gitignore`). It is a scratchpad for review notes, plans, and internal analysis. **Never `git add docs/…` or suggest committing anything under `docs/`.** If a document belongs in the repo, it lives elsewhere (README, CHANGELOG, top-level `*.md`).
119
+
117
120
  ## Development Principles
118
121
 
119
122
  ### Code Style
@@ -130,7 +133,8 @@ Workflow:
130
133
 
131
134
  ### Key Design Patterns
132
135
  - **Tool error handling**: All tools return `{"success": bool, ...}` dicts. Never raise `ToolError`.
133
- - **Tool registration**: Use `@register_tool(category=..., permission_level=..., description=...)` decorator. Tools are auto-discovered via the global `ToolRegistry`.
136
+ - **Tool registration**: Use `@register_tool(category=..., capabilities=..., description=...)` decorator. `capabilities=` is required — pass `EXEMPT` for tools that need no permission check or a list of `Capability(name, target_arg=...)` tuples the engine matches against rules. Tools are auto-discovered via the global `ToolRegistry`.
137
+ - **Permissions**: `workflow/permissions/` holds a framework-independent engine that evaluates declared capabilities against rules from four sources (builtin, user `~/.{app_name}/settings.json`, project `./.{app_name}/settings.json`, in-memory session). ADK + LangGraph gate tool calls via `workflow/adk/permission_plugin.py::PermissionPlugin` and `workflow/langgraph/permission_wrap.py::wrap_tool_for_permission`. See `docs/superpowers/specs/2026-04-18-permissions-system-design.md`.
134
138
  - **Service registry**: Tools access services and shared state via `get_service(key)` from `workflow.service_registry`. A single ContextVar holds a `dict[str, Any]` set by the workflow manager during processing. Complex services (KBManager, SandboxManager, MemoryStore) are lazily created; simple state (plan string, task list) lives directly in the registry dict.
135
139
  - **Manager detection**: Tools decorated with `@requires("kb_manager")` etc. are scanned by `BaseWorkflowManager._detect_required_managers()` which lazily creates only the needed services.
136
140
  - **Atomic writes**: Use `atomic_write_json`/`atomic_write_text` from `persistence/_utils.py` for file persistence.
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: agentic-cli
3
- Version: 0.5.0
3
+ Version: 0.5.1
4
4
  Summary: A framework for building domain-specific agentic CLI applications
5
5
  Project-URL: Homepage, https://github.com/shoom1/agentic-cli
6
6
  Project-URL: Repository, https://github.com/shoom1/agentic-cli
@@ -32,7 +32,9 @@ Requires-Dist: pytest-asyncio>=0.24.0; extra == 'dev'
32
32
  Requires-Dist: pytest-cov>=6.0.0; extra == 'dev'
33
33
  Requires-Dist: pytest>=8.0.0; extra == 'dev'
34
34
  Provides-Extra: kb
35
+ Requires-Dist: bm25s>=0.2.0; extra == 'kb'
35
36
  Requires-Dist: faiss-cpu>=1.7.0; extra == 'kb'
37
+ Requires-Dist: rank-bm25>=0.2.2; extra == 'kb'
36
38
  Requires-Dist: sentence-transformers<3.0.0,>=2.0.0; extra == 'kb'
37
39
  Requires-Dist: torch>=2.2.0; extra == 'kb'
38
40
  Provides-Extra: langgraph
@@ -84,7 +86,7 @@ Agentic CLI provides the core infrastructure for building interactive CLI applic
84
86
  ├─────────────────────────────┬───────────────────────────────────────┤
85
87
  │ GoogleADKWorkflowManager │ LangGraphWorkflowManager │
86
88
  │ (default) │ (optional: langgraph extra) │
87
- │ + ConfirmationPlugin │ + confirmation tool wrapper │
89
+ │ + PermissionPlugin │ + wrap_tool_for_permission │
88
90
  │ + LLMLoggingPlugin │ + native ToolNode │
89
91
  │ + TaskProgressPlugin │ │
90
92
  └─────────────────────────────┴───────────────────────────────────────┘
@@ -191,7 +193,7 @@ manager = GoogleADKWorkflowManager(
191
193
  ```
192
194
 
193
195
  ADK integrations:
194
- - **ConfirmationPlugin** — intercepts `DANGEROUS` tools and prompts the user for approval
196
+ - **PermissionPlugin** — evaluates each tool's declared capabilities against the permission engine and prompts the user when no rule matches
195
197
  - **LLMLoggingPlugin** — structured logging of LLM requests/responses
196
198
  - **TaskProgressPlugin** — streams the task checklist into its own thinking box
197
199
 
@@ -217,7 +219,7 @@ Features:
217
219
  - **Explicit provider support**: Uses `langchain-google-genai` for Gemini (not VertexAI)
218
220
  - **Thinking mode**: Native support for Claude and Gemini thinking/reasoning
219
221
  - **Retry policies**: Automatic retry with exponential backoff
220
- - **Confirmation wrapper**: Wraps `DANGEROUS` tools to request HITL approval
222
+ - **Permission wrapper**: Wraps each tool to gate execution through the permission engine
221
223
  - **Event streaming**: Real-time workflow events via `WorkflowEvent`
222
224
 
223
225
  Requires: `pip install agentic-cli[langgraph]`
@@ -232,7 +234,7 @@ Requires: `pip install agentic-cli[langgraph]`
232
234
  | State persistence | In-memory | Memory, PostgreSQL, or SQLite |
233
235
  | Thinking support | Native (Gemini) | Native (Claude & Gemini) |
234
236
  | Retry handling | Built-in | Built-in with backoff |
235
- | HITL confirmation | ConfirmationPlugin | Tool wrapper |
237
+ | Permission gate | PermissionPlugin | wrap_tool_for_permission |
236
238
  | Context trimming | Native | Native |
237
239
 
238
240
  ### Auto-selection via Settings
@@ -366,11 +368,12 @@ configs = [coordinator, researcher, analyst]
366
368
  Tools are regular Python functions with type hints and docstrings. **All tools return `{"success": bool, ...}` dicts** — never raise exceptions.
367
369
 
368
370
  ```python
369
- from agentic_cli.tools import register_tool, ToolCategory, PermissionLevel
371
+ from agentic_cli.tools import register_tool, ToolCategory
372
+ from agentic_cli.workflow.permissions import Capability, EXEMPT
370
373
 
371
374
  @register_tool(
372
- category=ToolCategory.DATA,
373
- permission_level=PermissionLevel.SAFE,
375
+ category=ToolCategory.NETWORK,
376
+ capabilities=[Capability("http.read")],
374
377
  description="Search the database for matching records.",
375
378
  )
376
379
  def search_database(query: str, limit: int = 10) -> dict:
@@ -387,15 +390,14 @@ def search_database(query: str, limit: int = 10) -> dict:
387
390
  return {"success": True, "results": results, "count": len(results)}
388
391
  ```
389
392
 
390
- Registering via `@register_tool` is optional — you can pass raw callables into `AgentConfig.tools`. Registering gives the tool metadata for the registry, permission-aware HITL wrapping, and tool-summary formatting.
393
+ Registering via `@register_tool` is optional — you can pass raw callables into `AgentConfig.tools`. Registering gives the tool metadata for the registry, capability-aware permission gating, and tool-summary formatting.
391
394
 
392
- ### Permission Levels
395
+ ### Capabilities
393
396
 
394
- | Level | Behavior |
395
- |-------|----------|
396
- | `SAFE` | Runs silently |
397
- | `CAUTION` | Runs; result is surfaced prominently |
398
- | `DANGEROUS` | Intercepted by HITL — user must approve before execution |
397
+ Tool access is gated by the **permission engine** (see the HITL section below). Each registered tool declares what it touches via `capabilities=`:
398
+
399
+ - **`Capability("namespace.action", target_arg="...")`** — e.g. `Capability("filesystem.write", target_arg="path")`, `Capability("http.read", target_arg="url")`, `Capability("shell.exec", target_arg="command")`. The engine resolves `target_arg` against the actual tool-call arguments and matches against rules.
400
+ - **`EXEMPT`** — opts the tool out of the engine entirely. Reserved for backend-internal tools (e.g. ADK `transfer_to_agent`, backend state tools).
399
401
 
400
402
  ### Built-in Tools
401
403
 
@@ -476,7 +478,7 @@ list_dir("src/", include_hidden=False)
476
478
  diff_compare(source_a="old.txt", source_b="new.txt")
477
479
  ```
478
480
 
479
- **WRITE tools (caution)**
481
+ **WRITE tools**
480
482
 
481
483
  ```python
482
484
  from agentic_cli.tools import write_file, edit_file
@@ -580,15 +582,9 @@ Each tool keeps at most N reflections (FIFO eviction). Reflections can be inject
580
582
 
581
583
  #### HITL (Human-in-the-Loop)
582
584
 
583
- Two mechanisms:
584
-
585
- 1. **Automatic confirmation for `DANGEROUS` tools** — handled by ADK's `ConfirmationPlugin` and LangGraph's tool wrapper. No code changes needed; just mark the tool `DANGEROUS`.
586
- 2. **Explicit `request_approval` tool** — for domain-level checkpoints:
585
+ Tool calls are gated by the **permission engine** (`workflow/permissions/`). Each tool declares a list of capabilities (e.g. `filesystem.write(path=...)`); the engine evaluates them against rules from four sources (builtin defaults, user `~/.{app_name}/settings.json`, project `./.{app_name}/settings.json`, in-memory session). When no rule matches, the user is prompted with `Allow once / Allow for session / Allow always (save to project) / Deny`. Always-grants persist into the project settings file so the next run picks them up automatically.
587
586
 
588
- ```python
589
- from agentic_cli.tools import hitl_tools
590
- # request_approval(message, options) -> {"success": True, "choice": "..."}
591
- ```
587
+ See `docs/superpowers/specs/2026-04-18-permissions-system-design.md` for the full design.
592
588
 
593
589
  ## CLI Commands
594
590
 
@@ -764,15 +760,24 @@ agentic-cli/
764
760
  │ │ ├── adk/
765
761
  │ │ │ ├── manager.py # GoogleADKWorkflowManager
766
762
  │ │ │ ├── event_processor.py
767
- │ │ │ ├── plugins.py # ConfirmationPlugin, LLMLoggingPlugin
763
+ │ │ │ ├── plugins.py # LLMLoggingPlugin
764
+ │ │ │ ├── permission_plugin.py # PermissionPlugin (capability gating)
768
765
  │ │ │ └── task_progress_plugin.py
769
- │ │ └── langgraph/
770
- │ │ ├── manager.py # LangGraphWorkflowManager
771
- │ │ ├── graph_builder.py
772
- │ │ ├── state.py
773
- │ │ └── persistence/ # Checkpointers and stores
766
+ │ │ ├── langgraph/
767
+ │ │ │ ├── manager.py # LangGraphWorkflowManager
768
+ │ │ │ ├── graph_builder.py
769
+ │ │ │ ├── permission_wrap.py # wrap_tool_for_permission
770
+ │ │ │ ├── state.py
771
+ │ │ │ └── persistence/ # Checkpointers and stores
772
+ │ │ └── permissions/ # Framework-independent permission engine
773
+ │ │ ├── capabilities.py # Capability, ResolvedCapability, EXEMPT
774
+ │ │ ├── rules.py # Rule, Effect, RuleSource, CheckResult, AskScope
775
+ │ │ ├── matchers.py # Path/URL/Shell/StringGlob matchers
776
+ │ │ ├── store.py # PermissionContext, BUILTIN_RULES, JSON load/save
777
+ │ │ ├── prompt.py # build_request + parse_response
778
+ │ │ └── engine.py # PermissionEngine
774
779
  │ ├── tools/
775
- │ │ ├── registry.py # ToolRegistry, ToolCategory, PermissionLevel
780
+ │ │ ├── registry.py # ToolRegistry, ToolCategory, register_tool
776
781
  │ │ ├── factories.py # Backend-aware tool factories
777
782
  │ │ ├── executor.py # SafePythonExecutor
778
783
  │ │ ├── arxiv_tools.py # search_arxiv, fetch_arxiv_paper, ingest_arxiv_paper
@@ -790,7 +795,6 @@ agentic-cli/
790
795
  │ │ ├── pdf_utils.py # PDF text extraction helpers
791
796
  │ │ ├── memory_tools.py # save/search/update/delete + MemoryStore
792
797
  │ │ ├── reflection_tools.py # save_reflection + ToolReflectionStore
793
- │ │ ├── hitl_tools.py # request_approval
794
798
  │ │ ├── _core/ # Shared planning/task logic
795
799
  │ │ │ ├── planning.py
796
800
  │ │ │ └── tasks.py
@@ -38,7 +38,7 @@ Agentic CLI provides the core infrastructure for building interactive CLI applic
38
38
  ├─────────────────────────────┬───────────────────────────────────────┤
39
39
  │ GoogleADKWorkflowManager │ LangGraphWorkflowManager │
40
40
  │ (default) │ (optional: langgraph extra) │
41
- │ + ConfirmationPlugin │ + confirmation tool wrapper │
41
+ │ + PermissionPlugin │ + wrap_tool_for_permission │
42
42
  │ + LLMLoggingPlugin │ + native ToolNode │
43
43
  │ + TaskProgressPlugin │ │
44
44
  └─────────────────────────────┴───────────────────────────────────────┘
@@ -145,7 +145,7 @@ manager = GoogleADKWorkflowManager(
145
145
  ```
146
146
 
147
147
  ADK integrations:
148
- - **ConfirmationPlugin** — intercepts `DANGEROUS` tools and prompts the user for approval
148
+ - **PermissionPlugin** — evaluates each tool's declared capabilities against the permission engine and prompts the user when no rule matches
149
149
  - **LLMLoggingPlugin** — structured logging of LLM requests/responses
150
150
  - **TaskProgressPlugin** — streams the task checklist into its own thinking box
151
151
 
@@ -171,7 +171,7 @@ Features:
171
171
  - **Explicit provider support**: Uses `langchain-google-genai` for Gemini (not VertexAI)
172
172
  - **Thinking mode**: Native support for Claude and Gemini thinking/reasoning
173
173
  - **Retry policies**: Automatic retry with exponential backoff
174
- - **Confirmation wrapper**: Wraps `DANGEROUS` tools to request HITL approval
174
+ - **Permission wrapper**: Wraps each tool to gate execution through the permission engine
175
175
  - **Event streaming**: Real-time workflow events via `WorkflowEvent`
176
176
 
177
177
  Requires: `pip install agentic-cli[langgraph]`
@@ -186,7 +186,7 @@ Requires: `pip install agentic-cli[langgraph]`
186
186
  | State persistence | In-memory | Memory, PostgreSQL, or SQLite |
187
187
  | Thinking support | Native (Gemini) | Native (Claude & Gemini) |
188
188
  | Retry handling | Built-in | Built-in with backoff |
189
- | HITL confirmation | ConfirmationPlugin | Tool wrapper |
189
+ | Permission gate | PermissionPlugin | wrap_tool_for_permission |
190
190
  | Context trimming | Native | Native |
191
191
 
192
192
  ### Auto-selection via Settings
@@ -320,11 +320,12 @@ configs = [coordinator, researcher, analyst]
320
320
  Tools are regular Python functions with type hints and docstrings. **All tools return `{"success": bool, ...}` dicts** — never raise exceptions.
321
321
 
322
322
  ```python
323
- from agentic_cli.tools import register_tool, ToolCategory, PermissionLevel
323
+ from agentic_cli.tools import register_tool, ToolCategory
324
+ from agentic_cli.workflow.permissions import Capability, EXEMPT
324
325
 
325
326
  @register_tool(
326
- category=ToolCategory.DATA,
327
- permission_level=PermissionLevel.SAFE,
327
+ category=ToolCategory.NETWORK,
328
+ capabilities=[Capability("http.read")],
328
329
  description="Search the database for matching records.",
329
330
  )
330
331
  def search_database(query: str, limit: int = 10) -> dict:
@@ -341,15 +342,14 @@ def search_database(query: str, limit: int = 10) -> dict:
341
342
  return {"success": True, "results": results, "count": len(results)}
342
343
  ```
343
344
 
344
- Registering via `@register_tool` is optional — you can pass raw callables into `AgentConfig.tools`. Registering gives the tool metadata for the registry, permission-aware HITL wrapping, and tool-summary formatting.
345
+ Registering via `@register_tool` is optional — you can pass raw callables into `AgentConfig.tools`. Registering gives the tool metadata for the registry, capability-aware permission gating, and tool-summary formatting.
345
346
 
346
- ### Permission Levels
347
+ ### Capabilities
347
348
 
348
- | Level | Behavior |
349
- |-------|----------|
350
- | `SAFE` | Runs silently |
351
- | `CAUTION` | Runs; result is surfaced prominently |
352
- | `DANGEROUS` | Intercepted by HITL — user must approve before execution |
349
+ Tool access is gated by the **permission engine** (see the HITL section below). Each registered tool declares what it touches via `capabilities=`:
350
+
351
+ - **`Capability("namespace.action", target_arg="...")`** — e.g. `Capability("filesystem.write", target_arg="path")`, `Capability("http.read", target_arg="url")`, `Capability("shell.exec", target_arg="command")`. The engine resolves `target_arg` against the actual tool-call arguments and matches against rules.
352
+ - **`EXEMPT`** — opts the tool out of the engine entirely. Reserved for backend-internal tools (e.g. ADK `transfer_to_agent`, backend state tools).
353
353
 
354
354
  ### Built-in Tools
355
355
 
@@ -430,7 +430,7 @@ list_dir("src/", include_hidden=False)
430
430
  diff_compare(source_a="old.txt", source_b="new.txt")
431
431
  ```
432
432
 
433
- **WRITE tools (caution)**
433
+ **WRITE tools**
434
434
 
435
435
  ```python
436
436
  from agentic_cli.tools import write_file, edit_file
@@ -534,15 +534,9 @@ Each tool keeps at most N reflections (FIFO eviction). Reflections can be inject
534
534
 
535
535
  #### HITL (Human-in-the-Loop)
536
536
 
537
- Two mechanisms:
538
-
539
- 1. **Automatic confirmation for `DANGEROUS` tools** — handled by ADK's `ConfirmationPlugin` and LangGraph's tool wrapper. No code changes needed; just mark the tool `DANGEROUS`.
540
- 2. **Explicit `request_approval` tool** — for domain-level checkpoints:
537
+ Tool calls are gated by the **permission engine** (`workflow/permissions/`). Each tool declares a list of capabilities (e.g. `filesystem.write(path=...)`); the engine evaluates them against rules from four sources (builtin defaults, user `~/.{app_name}/settings.json`, project `./.{app_name}/settings.json`, in-memory session). When no rule matches, the user is prompted with `Allow once / Allow for session / Allow always (save to project) / Deny`. Always-grants persist into the project settings file so the next run picks them up automatically.
541
538
 
542
- ```python
543
- from agentic_cli.tools import hitl_tools
544
- # request_approval(message, options) -> {"success": True, "choice": "..."}
545
- ```
539
+ See `docs/superpowers/specs/2026-04-18-permissions-system-design.md` for the full design.
546
540
 
547
541
  ## CLI Commands
548
542
 
@@ -718,15 +712,24 @@ agentic-cli/
718
712
  │ │ ├── adk/
719
713
  │ │ │ ├── manager.py # GoogleADKWorkflowManager
720
714
  │ │ │ ├── event_processor.py
721
- │ │ │ ├── plugins.py # ConfirmationPlugin, LLMLoggingPlugin
715
+ │ │ │ ├── plugins.py # LLMLoggingPlugin
716
+ │ │ │ ├── permission_plugin.py # PermissionPlugin (capability gating)
722
717
  │ │ │ └── task_progress_plugin.py
723
- │ │ └── langgraph/
724
- │ │ ├── manager.py # LangGraphWorkflowManager
725
- │ │ ├── graph_builder.py
726
- │ │ ├── state.py
727
- │ │ └── persistence/ # Checkpointers and stores
718
+ │ │ ├── langgraph/
719
+ │ │ │ ├── manager.py # LangGraphWorkflowManager
720
+ │ │ │ ├── graph_builder.py
721
+ │ │ │ ├── permission_wrap.py # wrap_tool_for_permission
722
+ │ │ │ ├── state.py
723
+ │ │ │ └── persistence/ # Checkpointers and stores
724
+ │ │ └── permissions/ # Framework-independent permission engine
725
+ │ │ ├── capabilities.py # Capability, ResolvedCapability, EXEMPT
726
+ │ │ ├── rules.py # Rule, Effect, RuleSource, CheckResult, AskScope
727
+ │ │ ├── matchers.py # Path/URL/Shell/StringGlob matchers
728
+ │ │ ├── store.py # PermissionContext, BUILTIN_RULES, JSON load/save
729
+ │ │ ├── prompt.py # build_request + parse_response
730
+ │ │ └── engine.py # PermissionEngine
728
731
  │ ├── tools/
729
- │ │ ├── registry.py # ToolRegistry, ToolCategory, PermissionLevel
732
+ │ │ ├── registry.py # ToolRegistry, ToolCategory, register_tool
730
733
  │ │ ├── factories.py # Backend-aware tool factories
731
734
  │ │ ├── executor.py # SafePythonExecutor
732
735
  │ │ ├── arxiv_tools.py # search_arxiv, fetch_arxiv_paper, ingest_arxiv_paper
@@ -744,7 +747,6 @@ agentic-cli/
744
747
  │ │ ├── pdf_utils.py # PDF text extraction helpers
745
748
  │ │ ├── memory_tools.py # save/search/update/delete + MemoryStore
746
749
  │ │ ├── reflection_tools.py # save_reflection + ToolReflectionStore
747
- │ │ ├── hitl_tools.py # request_approval
748
750
  │ │ ├── _core/ # Shared planning/task logic
749
751
  │ │ │ ├── planning.py
750
752
  │ │ │ └── tasks.py
@@ -4,7 +4,7 @@ build-backend = "hatchling.build"
4
4
 
5
5
  [project]
6
6
  name = "agentic-cli"
7
- version = "0.5.0"
7
+ version = "0.5.1"
8
8
  description = "A framework for building domain-specific agentic CLI applications"
9
9
  readme = "README.md"
10
10
  license = "MIT"
@@ -46,6 +46,8 @@ kb = [
46
46
  "torch>=2.2.0",
47
47
  "sentence-transformers>=2.0.0,<3.0.0",
48
48
  "faiss-cpu>=1.7.0",
49
+ "bm25s>=0.2.0",
50
+ "rank-bm25>=0.2.2",
49
51
  ]
50
52
  langgraph = [
51
53
  "langgraph>=0.2.0",
@@ -92,4 +92,4 @@ __all__ = [
92
92
  "CLISettingsMixin",
93
93
  ]
94
94
 
95
- __version__ = "0.5.0"
95
+ __version__ = "0.5.1"
@@ -0,0 +1,137 @@
1
+ """Real BM25 index backends (bm25s, rank_bm25).
2
+
3
+ Both implement the same interface as MockBM25Index so create_bm25_index()
4
+ can return any of them interchangeably. Tokenization is lowercase whitespace
5
+ split to match MockBM25Index; the scoring model is the library's own BM25.
6
+
7
+ Neither underlying library supports true incremental add/remove, so we keep
8
+ tokenized docs + chunk_ids in memory and rebuild the model lazily on search.
9
+ """
10
+
11
+ from __future__ import annotations
12
+
13
+ import json
14
+ from pathlib import Path
15
+
16
+ from agentic_cli.file_utils import atomic_write_json
17
+
18
+
19
+ def _tokenize(text: str) -> list[str]:
20
+ return text.lower().split()
21
+
22
+
23
+ class _BM25BackendBase:
24
+ _INDEX_FILE: str = ""
25
+
26
+ def __init__(self):
27
+ self._chunk_ids: list[str] = []
28
+ self._tokenized: list[list[str]] = []
29
+ self._model = None
30
+
31
+ @property
32
+ def size(self) -> int:
33
+ return len(self._chunk_ids)
34
+
35
+ def add_documents(self, chunk_ids: list[str], texts: list[str]) -> None:
36
+ for cid, text in zip(chunk_ids, texts):
37
+ self._chunk_ids.append(cid)
38
+ self._tokenized.append(_tokenize(text))
39
+ self._model = None
40
+
41
+ def remove_documents(self, chunk_ids: list[str]) -> None:
42
+ remove_set = set(chunk_ids)
43
+ keep = [
44
+ (cid, toks)
45
+ for cid, toks in zip(self._chunk_ids, self._tokenized)
46
+ if cid not in remove_set
47
+ ]
48
+ if keep:
49
+ self._chunk_ids, self._tokenized = map(list, zip(*keep))
50
+ else:
51
+ self._chunk_ids, self._tokenized = [], []
52
+ self._model = None
53
+
54
+ def rebuild(self, chunk_ids: list[str], texts: list[str]) -> None:
55
+ self._chunk_ids = []
56
+ self._tokenized = []
57
+ self._model = None
58
+ self.add_documents(chunk_ids, texts)
59
+
60
+ def save(self, path: Path) -> None:
61
+ path.mkdir(parents=True, exist_ok=True)
62
+ atomic_write_json(
63
+ path / self._INDEX_FILE,
64
+ {"chunk_ids": self._chunk_ids, "tokenized": self._tokenized},
65
+ )
66
+
67
+ def load(self, path: Path) -> None:
68
+ index_path = path / self._INDEX_FILE
69
+ if not index_path.exists():
70
+ return
71
+ data = json.loads(index_path.read_text())
72
+ self._chunk_ids = data["chunk_ids"]
73
+ self._tokenized = data["tokenized"]
74
+ self._model = None
75
+
76
+
77
+ class RankBM25Index(_BM25BackendBase):
78
+ """BM25 backed by rank_bm25.BM25Plus (pure Python).
79
+
80
+ BM25Plus rather than BM25Okapi because Okapi's IDF can go zero or
81
+ negative when a term appears in most of the corpus; BM25Plus adds a
82
+ delta offset that guarantees positive contributions on real matches.
83
+ """
84
+
85
+ _INDEX_FILE = "bm25_rank.json"
86
+
87
+ def search(self, query: str, top_k: int = 10) -> list[tuple[str, float]]:
88
+ if not self._chunk_ids:
89
+ return []
90
+ query_tokens = _tokenize(query)
91
+ if not query_tokens:
92
+ return []
93
+ query_set = set(query_tokens)
94
+ if self._model is None:
95
+ from rank_bm25 import BM25Plus
96
+
97
+ self._model = BM25Plus(self._tokenized)
98
+ scores = self._model.get_scores(query_tokens)
99
+ scored: list[tuple[str, float]] = []
100
+ for cid, doc_tokens, score in zip(
101
+ self._chunk_ids, self._tokenized, scores
102
+ ):
103
+ if query_set.intersection(doc_tokens):
104
+ scored.append((cid, float(score)))
105
+ scored.sort(key=lambda x: x[1], reverse=True)
106
+ return scored[:top_k]
107
+
108
+
109
+ class BM25sIndex(_BM25BackendBase):
110
+ """BM25 backed by bm25s (NumPy/C-accelerated)."""
111
+
112
+ _INDEX_FILE = "bm25s_sidecar.json"
113
+
114
+ def _build_model(self) -> None:
115
+ import bm25s
116
+
117
+ model = bm25s.BM25()
118
+ model.index(self._tokenized, show_progress=False)
119
+ self._model = model
120
+
121
+ def search(self, query: str, top_k: int = 10) -> list[tuple[str, float]]:
122
+ if not self._chunk_ids:
123
+ return []
124
+ query_tokens = _tokenize(query)
125
+ if not query_tokens:
126
+ return []
127
+ if self._model is None:
128
+ self._build_model()
129
+ k = min(top_k, len(self._chunk_ids))
130
+ docs, scores = self._model.retrieve(
131
+ [query_tokens], k=k, show_progress=False
132
+ )
133
+ results: list[tuple[str, float]] = []
134
+ for idx, score in zip(docs[0], scores[0]):
135
+ if score > 0:
136
+ results.append((self._chunk_ids[int(idx)], float(score)))
137
+ return results
@@ -96,6 +96,26 @@ class KnowledgeBaseManager:
96
96
  """Main interface for knowledge base operations.
97
97
 
98
98
  Provides document ingestion, semantic search, and document management.
99
+
100
+ Concurrency model
101
+ -----------------
102
+ Two lock surfaces with different semantics:
103
+
104
+ * ``_lock`` (``threading.Lock``) — guards every mutation to the
105
+ in-memory document/chunk/vector/BM25 state and the check-and-set
106
+ of ``_backfill_running``. The sync mutation API
107
+ (``ingest_document``, ``search``, ``delete_document``, ``clear``)
108
+ is safe to call from any thread.
109
+ * ``_sidecar_locks`` (``dict[str, asyncio.Lock]``) — per-doc locks
110
+ that serialize concurrent LLM-backed sidecar generation for the
111
+ same document. Always obtain these through
112
+ :meth:`get_or_create_sidecar_lock`; never index the dict directly.
113
+
114
+ The async sidecar path (``backfill_sidecars`` and the lazy
115
+ ``kb_read`` fallback in ``tools/knowledge_tools``) is safe within a
116
+ single asyncio event loop. Sharing a manager across multiple event
117
+ loops is NOT supported — the per-doc ``asyncio.Lock`` is bound to
118
+ the loop it was created on.
99
119
  """
100
120
 
101
121
  def __init__(
@@ -302,6 +322,23 @@ class KnowledgeBaseManager:
302
322
  if path.exists():
303
323
  path.unlink()
304
324
 
325
+ def get_or_create_sidecar_lock(self, doc_id: str) -> asyncio.Lock:
326
+ """Return the per-doc async lock used to serialize sidecar writes.
327
+
328
+ Same lock is returned for the same ``doc_id`` across repeat calls,
329
+ so ``backfill_sidecars`` and the lazy ``kb_read`` fallback cannot
330
+ double-generate a sidecar for the same document.
331
+
332
+ The dict insert is guarded by ``_lock`` so two threads racing to
333
+ create the lock for a new doc will not produce two distinct
334
+ ``asyncio.Lock`` instances.
335
+ """
336
+ existing = self._sidecar_locks.get(doc_id)
337
+ if existing is not None:
338
+ return existing
339
+ with self._lock:
340
+ return self._sidecar_locks.setdefault(doc_id, asyncio.Lock())
341
+
305
342
  def _index_md_path(self) -> Path:
306
343
  return self.kb_dir / "index.md"
307
344
 
@@ -1028,35 +1065,40 @@ class KnowledgeBaseManager:
1028
1065
  Returns:
1029
1066
  Number of sidecars written this call.
1030
1067
  """
1031
- if self._backfill_running:
1032
- raise BackfillAlreadyRunning(
1033
- "backfill_sidecars is already running on this KB"
1034
- )
1035
- self._backfill_running = True
1036
- try:
1037
- written = 0
1038
- # Snapshot the docs that need backfilling up front so the
1039
- # progress denominator is stable even if ingest/delete runs
1040
- # concurrently.
1068
+ # Check-and-set the running flag and snapshot the work list under
1069
+ # one lock acquisition so a second thread cannot slip past the
1070
+ # guard between the read and the write.
1071
+ with self._lock:
1072
+ if self._backfill_running:
1073
+ raise BackfillAlreadyRunning(
1074
+ "backfill_sidecars is already running on this KB"
1075
+ )
1076
+ self._backfill_running = True
1041
1077
  todo = [
1042
1078
  doc
1043
- for doc in list(self._documents.values())
1079
+ for doc in self._documents.values()
1044
1080
  if not self._sidecar_path(doc.id).exists()
1045
1081
  ]
1082
+ try:
1083
+ written = 0
1046
1084
  total = len(todo)
1047
1085
  for i, doc in enumerate(todo):
1048
1086
  # Guard against delete-during-backfill — doc may have
1049
- # been removed since the snapshot was taken.
1050
- if doc.id not in self._documents:
1051
- continue
1052
- lock = self._sidecar_locks.setdefault(doc.id, asyncio.Lock())
1087
+ # been removed since the snapshot was taken. Checked
1088
+ # under the lock so a concurrent delete_document cannot
1089
+ # interleave between the check and use.
1090
+ with self._lock:
1091
+ if doc.id not in self._documents:
1092
+ continue
1093
+ lock = self.get_or_create_sidecar_lock(doc.id)
1053
1094
  async with lock:
1054
1095
  # Double-check inside the lock — another task may
1055
1096
  # have written it (e.g. lazy kb_read fallback).
1056
1097
  if self._sidecar_path(doc.id).exists():
1057
1098
  continue
1058
- if doc.id not in self._documents:
1059
- continue
1099
+ with self._lock:
1100
+ if doc.id not in self._documents:
1101
+ continue
1060
1102
  if progress_cb is not None:
1061
1103
  progress_cb(i, total, doc)
1062
1104
  payload = await self.generate_sidecar_payload(
@@ -1066,7 +1108,8 @@ class KnowledgeBaseManager:
1066
1108
  written += 1
1067
1109
  return written
1068
1110
  finally:
1069
- self._backfill_running = False
1111
+ with self._lock:
1112
+ self._backfill_running = False
1070
1113
 
1071
1114
 
1072
1115
  class BackfillAlreadyRunning(RuntimeError):