agentmetry 0.9.7__tar.gz → 0.9.9__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 (309) hide show
  1. {agentmetry-0.9.7 → agentmetry-0.9.9}/PKG-INFO +4 -4
  2. {agentmetry-0.9.7 → agentmetry-0.9.9}/README.md +1 -1
  3. {agentmetry-0.9.7 → agentmetry-0.9.9}/agentmetry/api/main.py +33 -8
  4. {agentmetry-0.9.7 → agentmetry-0.9.9}/agentmetry/api/routes/audit.py +25 -2
  5. {agentmetry-0.9.7 → agentmetry-0.9.9}/agentmetry/cli/__init__.py +104 -6
  6. agentmetry-0.9.9/agentmetry/core/audit/antigravity_trajectory.py +81 -0
  7. {agentmetry-0.9.7 → agentmetry-0.9.9}/agentmetry/core/audit/cloud_bridge.py +24 -0
  8. agentmetry-0.9.9/agentmetry/core/audit/detection/corpus/benign_container_registry_publish.jsonl +3 -0
  9. agentmetry-0.9.9/agentmetry/core/audit/detection/corpus/benign_dependency_audit_network_burst.jsonl +4 -0
  10. agentmetry-0.9.9/agentmetry/core/audit/detection/corpus/benign_terraform_apply_with_profile.jsonl +4 -0
  11. {agentmetry-0.9.7 → agentmetry-0.9.9}/agentmetry/core/audit/detection/corpus/corpus.yaml +28 -0
  12. {agentmetry-0.9.7 → agentmetry-0.9.9}/agentmetry/core/audit/external.py +20 -2
  13. agentmetry-0.9.9/agentmetry/core/audit/grok_bot_transcript.py +58 -0
  14. {agentmetry-0.9.7 → agentmetry-0.9.9}/agentmetry/core/audit/heartbeat.py +30 -2
  15. {agentmetry-0.9.7 → agentmetry-0.9.9}/agentmetry/core/audit/hook_bootstrap.py +40 -8
  16. agentmetry-0.9.9/agentmetry/core/audit/session_tree.py +190 -0
  17. {agentmetry-0.9.7 → agentmetry-0.9.9}/agentmetry/core/audit/trail_db.py +76 -0
  18. {agentmetry-0.9.7 → agentmetry-0.9.9}/agentmetry/core/config.py +11 -0
  19. {agentmetry-0.9.7 → agentmetry-0.9.9}/agentmetry/core/diagnostics/doctor.py +161 -6
  20. {agentmetry-0.9.7 → agentmetry-0.9.9}/agentmetry/core/diagnostics/hook_coverage.py +21 -3
  21. {agentmetry-0.9.7 → agentmetry-0.9.9}/agentmetry/core/diagnostics/machine_view.py +2 -2
  22. {agentmetry-0.9.7 → agentmetry-0.9.9}/agentmetry/core/diagnostics/mcp_inventory.py +3 -2
  23. {agentmetry-0.9.7 → agentmetry-0.9.9}/agentmetry/core/diagnostics/posture.py +17 -6
  24. agentmetry-0.9.9/agentmetry/core/managed_policy.py +818 -0
  25. {agentmetry-0.9.7 → agentmetry-0.9.9}/agentmetry/core/paths.py +18 -0
  26. {agentmetry-0.9.7 → agentmetry-0.9.9}/agentmetry/core/version.py +1 -1
  27. {agentmetry-0.9.7 → agentmetry-0.9.9}/agentmetry/hooks/ingest.py +10 -0
  28. {agentmetry-0.9.7 → agentmetry-0.9.9}/pyproject.toml +2 -2
  29. {agentmetry-0.9.7 → agentmetry-0.9.9}/tests/conftest.py +11 -0
  30. agentmetry-0.9.9/tests/test_antigravity_trajectory.py +35 -0
  31. {agentmetry-0.9.7 → agentmetry-0.9.9}/tests/test_cloud_bridge.py +14 -0
  32. {agentmetry-0.9.7 → agentmetry-0.9.9}/tests/test_doctor.py +78 -0
  33. agentmetry-0.9.9/tests/test_doctor_json_and_remediation.py +154 -0
  34. agentmetry-0.9.9/tests/test_grok_bot_transcript.py +44 -0
  35. {agentmetry-0.9.7 → agentmetry-0.9.9}/tests/test_heartbeat.py +2 -0
  36. {agentmetry-0.9.7 → agentmetry-0.9.9}/tests/test_hook_bootstrap.py +59 -0
  37. agentmetry-0.9.9/tests/test_managed_policy.py +518 -0
  38. agentmetry-0.9.9/tests/test_session_tree.py +188 -0
  39. agentmetry-0.9.9/tests/test_transcript_watcher.py +162 -0
  40. agentmetry-0.9.9/tests/test_user_profile_dir.py +138 -0
  41. {agentmetry-0.9.7 → agentmetry-0.9.9}/.dockerignore +0 -0
  42. {agentmetry-0.9.7 → agentmetry-0.9.9}/.env.agentmetry-demo +0 -0
  43. {agentmetry-0.9.7 → agentmetry-0.9.9}/.env.example +0 -0
  44. {agentmetry-0.9.7 → agentmetry-0.9.9}/.gitignore +0 -0
  45. {agentmetry-0.9.7 → agentmetry-0.9.9}/Dockerfile +0 -0
  46. {agentmetry-0.9.7 → agentmetry-0.9.9}/agentmetry/__init__.py +0 -0
  47. {agentmetry-0.9.7 → agentmetry-0.9.9}/agentmetry/api/__init__.py +0 -0
  48. {agentmetry-0.9.7 → agentmetry-0.9.9}/agentmetry/api/routes/__init__.py +0 -0
  49. {agentmetry-0.9.7 → agentmetry-0.9.9}/agentmetry/api/routes/auth.py +0 -0
  50. {agentmetry-0.9.7 → agentmetry-0.9.9}/agentmetry/api/trusted_host.py +0 -0
  51. {agentmetry-0.9.7 → agentmetry-0.9.9}/agentmetry/api/websocket.py +0 -0
  52. {agentmetry-0.9.7 → agentmetry-0.9.9}/agentmetry/api/ws_bridge.py +0 -0
  53. {agentmetry-0.9.7 → agentmetry-0.9.9}/agentmetry/cli/__main__.py +0 -0
  54. {agentmetry-0.9.7 → agentmetry-0.9.9}/agentmetry/core/__init__.py +0 -0
  55. {agentmetry-0.9.7 → agentmetry-0.9.9}/agentmetry/core/api_token.py +0 -0
  56. {agentmetry-0.9.7 → agentmetry-0.9.9}/agentmetry/core/audit/__init__.py +0 -0
  57. {agentmetry-0.9.7 → agentmetry-0.9.9}/agentmetry/core/audit/adapters/__init__.py +0 -0
  58. {agentmetry-0.9.7 → agentmetry-0.9.9}/agentmetry/core/audit/adapters/agt.py +0 -0
  59. {agentmetry-0.9.7 → agentmetry-0.9.9}/agentmetry/core/audit/adapters/chronicle.py +0 -0
  60. {agentmetry-0.9.7 → agentmetry-0.9.9}/agentmetry/core/audit/adapters/cloudevents.py +0 -0
  61. {agentmetry-0.9.7 → agentmetry-0.9.9}/agentmetry/core/audit/adapters/ecs.py +0 -0
  62. {agentmetry-0.9.7 → agentmetry-0.9.9}/agentmetry/core/audit/adapters/sentinel.py +0 -0
  63. {agentmetry-0.9.7 → agentmetry-0.9.9}/agentmetry/core/audit/adapters/splunk.py +0 -0
  64. {agentmetry-0.9.7 → agentmetry-0.9.9}/agentmetry/core/audit/alerts.py +0 -0
  65. {agentmetry-0.9.7 → agentmetry-0.9.9}/agentmetry/core/audit/atlas.py +0 -0
  66. {agentmetry-0.9.7 → agentmetry-0.9.9}/agentmetry/core/audit/canonical.py +0 -0
  67. {agentmetry-0.9.7 → agentmetry-0.9.9}/agentmetry/core/audit/compliance_digest.py +0 -0
  68. {agentmetry-0.9.7 → agentmetry-0.9.9}/agentmetry/core/audit/detection/__init__.py +0 -0
  69. {agentmetry-0.9.7 → agentmetry-0.9.9}/agentmetry/core/audit/detection/benchmark.py +0 -0
  70. {agentmetry-0.9.7 → agentmetry-0.9.9}/agentmetry/core/audit/detection/corpus/attack_approval_denied_then_executed.jsonl +0 -0
  71. {agentmetry-0.9.7 → agentmetry-0.9.9}/agentmetry/core/audit/detection/corpus/attack_arbitrary_host_stage_execute.jsonl +0 -0
  72. {agentmetry-0.9.7 → agentmetry-0.9.9}/agentmetry/core/audit/detection/corpus/attack_autonomous_unapproved_write.jsonl +0 -0
  73. {agentmetry-0.9.7 → agentmetry-0.9.9}/agentmetry/core/audit/detection/corpus/attack_credential_exfil.jsonl +0 -0
  74. {agentmetry-0.9.7 → agentmetry-0.9.9}/agentmetry/core/audit/detection/corpus/attack_credential_then_cloud_api.jsonl +0 -0
  75. {agentmetry-0.9.7 → agentmetry-0.9.9}/agentmetry/core/audit/detection/corpus/attack_destructive_delete_burst.jsonl +0 -0
  76. {agentmetry-0.9.7 → agentmetry-0.9.9}/agentmetry/core/audit/detection/corpus/attack_discovery_then_collect.jsonl +0 -0
  77. {agentmetry-0.9.7 → agentmetry-0.9.9}/agentmetry/core/audit/detection/corpus/attack_dotfile_then_git_push.jsonl +0 -0
  78. {agentmetry-0.9.7 → agentmetry-0.9.9}/agentmetry/core/audit/detection/corpus/attack_encoded_command_download.jsonl +0 -0
  79. {agentmetry-0.9.7 → agentmetry-0.9.9}/agentmetry/core/audit/detection/corpus/attack_env_credential_exfil.jsonl +0 -0
  80. {agentmetry-0.9.7 → agentmetry-0.9.9}/agentmetry/core/audit/detection/corpus/attack_hashed_only_no_command.jsonl +0 -0
  81. {agentmetry-0.9.7 → agentmetry-0.9.9}/agentmetry/core/audit/detection/corpus/attack_interpreter_egress.jsonl +0 -0
  82. {agentmetry-0.9.7 → agentmetry-0.9.9}/agentmetry/core/audit/detection/corpus/attack_pr_merged_without_review.jsonl +0 -0
  83. {agentmetry-0.9.7 → agentmetry-0.9.9}/agentmetry/core/audit/detection/corpus/attack_proc_substitution_cradle.jsonl +0 -0
  84. {agentmetry-0.9.7 → agentmetry-0.9.9}/agentmetry/core/audit/detection/corpus/attack_remote_pipe_to_shell.jsonl +0 -0
  85. {agentmetry-0.9.7 → agentmetry-0.9.9}/agentmetry/core/audit/detection/corpus/attack_remote_staging_then_execute.jsonl +0 -0
  86. {agentmetry-0.9.7 → agentmetry-0.9.9}/agentmetry/core/audit/detection/corpus/attack_secret_manager_then_egress.jsonl +0 -0
  87. {agentmetry-0.9.7 → agentmetry-0.9.9}/agentmetry/core/audit/detection/corpus/attack_session_tool_burst.jsonl +0 -0
  88. {agentmetry-0.9.7 → agentmetry-0.9.9}/agentmetry/core/audit/detection/corpus/attack_single_command_exfil.jsonl +0 -0
  89. {agentmetry-0.9.7 → agentmetry-0.9.9}/agentmetry/core/audit/detection/corpus/attack_single_quoted_interpreter_egress.jsonl +0 -0
  90. {agentmetry-0.9.7 → agentmetry-0.9.9}/agentmetry/core/audit/detection/corpus/attack_ssh_directory_exfil.jsonl +0 -0
  91. {agentmetry-0.9.7 → agentmetry-0.9.9}/agentmetry/core/audit/detection/corpus/attack_subagent_swarm.jsonl +0 -0
  92. {agentmetry-0.9.7 → agentmetry-0.9.9}/agentmetry/core/audit/detection/corpus/attack_timestamp_collision.jsonl +0 -0
  93. {agentmetry-0.9.7 → agentmetry-0.9.9}/agentmetry/core/audit/detection/corpus/attack_untrusted_input_then_action.jsonl +0 -0
  94. {agentmetry-0.9.7 → agentmetry-0.9.9}/agentmetry/core/audit/detection/corpus/benign_authoring_merge_fixtures.jsonl +0 -0
  95. {agentmetry-0.9.7 → agentmetry-0.9.9}/agentmetry/core/audit/detection/corpus/benign_autonomous_after_approval.jsonl +0 -0
  96. {agentmetry-0.9.7 → agentmetry-0.9.9}/agentmetry/core/audit/detection/corpus/benign_build_artifact_cleanup.jsonl +0 -0
  97. {agentmetry-0.9.7 → agentmetry-0.9.9}/agentmetry/core/audit/detection/corpus/benign_ci_artifact_download.jsonl +0 -0
  98. {agentmetry-0.9.7 → agentmetry-0.9.9}/agentmetry/core/audit/detection/corpus/benign_commit_message_names_cloud_clis.jsonl +0 -0
  99. {agentmetry-0.9.7 → agentmetry-0.9.9}/agentmetry/core/audit/detection/corpus/benign_database_migration.jsonl +0 -0
  100. {agentmetry-0.9.7 → agentmetry-0.9.9}/agentmetry/core/audit/detection/corpus/benign_dependency_install_and_build.jsonl +0 -0
  101. {agentmetry-0.9.7 → agentmetry-0.9.9}/agentmetry/core/audit/detection/corpus/benign_download_release_archive.jsonl +0 -0
  102. {agentmetry-0.9.7 → agentmetry-0.9.9}/agentmetry/core/audit/detection/corpus/benign_env_example_read.jsonl +0 -0
  103. {agentmetry-0.9.7 → agentmetry-0.9.9}/agentmetry/core/audit/detection/corpus/benign_fetch_data_then_run_repo_script.jsonl +0 -0
  104. {agentmetry-0.9.7 → agentmetry-0.9.9}/agentmetry/core/audit/detection/corpus/benign_fetch_dataset_then_analyse.jsonl +0 -0
  105. {agentmetry-0.9.7 → agentmetry-0.9.9}/agentmetry/core/audit/detection/corpus/benign_fetch_lockfile_then_install.jsonl +0 -0
  106. {agentmetry-0.9.7 → agentmetry-0.9.9}/agentmetry/core/audit/detection/corpus/benign_fetch_piped_into_own_script.jsonl +0 -0
  107. {agentmetry-0.9.7 → agentmetry-0.9.9}/agentmetry/core/audit/detection/corpus/benign_git_review_and_push.jsonl +0 -0
  108. {agentmetry-0.9.7 → agentmetry-0.9.9}/agentmetry/core/audit/detection/corpus/benign_github_raw_docs_then_script.jsonl +0 -0
  109. {agentmetry-0.9.7 → agentmetry-0.9.9}/agentmetry/core/audit/detection/corpus/benign_human_driven_deletes.jsonl +0 -0
  110. {agentmetry-0.9.7 → agentmetry-0.9.9}/agentmetry/core/audit/detection/corpus/benign_local_api_probing.jsonl +0 -0
  111. {agentmetry-0.9.7 → agentmetry-0.9.9}/agentmetry/core/audit/detection/corpus/benign_long_but_calm_session.jsonl +0 -0
  112. {agentmetry-0.9.7 → agentmetry-0.9.9}/agentmetry/core/audit/detection/corpus/benign_loopback_is_not_egress.jsonl +0 -0
  113. {agentmetry-0.9.7 → agentmetry-0.9.9}/agentmetry/core/audit/detection/corpus/benign_loopback_pipe_to_interpreter.jsonl +0 -0
  114. {agentmetry-0.9.7 → agentmetry-0.9.9}/agentmetry/core/audit/detection/corpus/benign_mcp_text_mentions_credential_path.jsonl +0 -0
  115. {agentmetry-0.9.7 → agentmetry-0.9.9}/agentmetry/core/audit/detection/corpus/benign_ordinary_development.jsonl +0 -0
  116. {agentmetry-0.9.7 → agentmetry-0.9.9}/agentmetry/core/audit/detection/corpus/benign_package_manager_after_fetch.jsonl +0 -0
  117. {agentmetry-0.9.7 → agentmetry-0.9.9}/agentmetry/core/audit/detection/corpus/benign_reading_config_that_is_not_secret.jsonl +0 -0
  118. {agentmetry-0.9.7 → agentmetry-0.9.9}/agentmetry/core/audit/detection/corpus/benign_remote_api_call_no_credentials.jsonl +0 -0
  119. {agentmetry-0.9.7 → agentmetry-0.9.9}/agentmetry/core/audit/detection/corpus/benign_research_then_docs.jsonl +0 -0
  120. {agentmetry-0.9.7 → agentmetry-0.9.9}/agentmetry/core/audit/detection/corpus/benign_reversed_order_is_not_exfil.jsonl +0 -0
  121. {agentmetry-0.9.7 → agentmetry-0.9.9}/agentmetry/core/audit/detection/corpus/benign_secret_names_and_docs.jsonl +0 -0
  122. {agentmetry-0.9.7 → agentmetry-0.9.9}/agentmetry/core/audit/detection/corpus/benign_test_and_fix_loop.jsonl +0 -0
  123. {agentmetry-0.9.7 → agentmetry-0.9.9}/agentmetry/core/audit/detection/corpus/benign_writing_about_credentials.jsonl +0 -0
  124. {agentmetry-0.9.7 → agentmetry-0.9.9}/agentmetry/core/audit/detection/disposition.py +0 -0
  125. {agentmetry-0.9.7 → agentmetry-0.9.9}/agentmetry/core/audit/detection/engine.py +0 -0
  126. {agentmetry-0.9.7 → agentmetry-0.9.9}/agentmetry/core/audit/detection/live.py +0 -0
  127. {agentmetry-0.9.7 → agentmetry-0.9.9}/agentmetry/core/audit/detection/live_store.py +0 -0
  128. {agentmetry-0.9.7 → agentmetry-0.9.9}/agentmetry/core/audit/detection/models.py +0 -0
  129. {agentmetry-0.9.7 → agentmetry-0.9.9}/agentmetry/core/audit/detection/rules.py +0 -0
  130. {agentmetry-0.9.7 → agentmetry-0.9.9}/agentmetry/core/audit/detection/traits.py +0 -0
  131. {agentmetry-0.9.7 → agentmetry-0.9.9}/agentmetry/core/audit/detection/yaml_config.py +0 -0
  132. {agentmetry-0.9.7 → agentmetry-0.9.9}/agentmetry/core/audit/detection/yaml_rules.py +0 -0
  133. {agentmetry-0.9.7 → agentmetry-0.9.9}/agentmetry/core/audit/dlp/__init__.py +0 -0
  134. {agentmetry-0.9.7 → agentmetry-0.9.9}/agentmetry/core/audit/dlp/loader.py +0 -0
  135. {agentmetry-0.9.7 → agentmetry-0.9.9}/agentmetry/core/audit/dlp/models.py +0 -0
  136. {agentmetry-0.9.7 → agentmetry-0.9.9}/agentmetry/core/audit/dlp/scanner.py +0 -0
  137. {agentmetry-0.9.7 → agentmetry-0.9.9}/agentmetry/core/audit/dogfood.py +0 -0
  138. {agentmetry-0.9.7 → agentmetry-0.9.9}/agentmetry/core/audit/evidence_pack.py +0 -0
  139. {agentmetry-0.9.7 → agentmetry-0.9.9}/agentmetry/core/audit/forwarder.py +0 -0
  140. {agentmetry-0.9.7 → agentmetry-0.9.9}/agentmetry/core/audit/hashing.py +0 -0
  141. {agentmetry-0.9.7 → agentmetry-0.9.9}/agentmetry/core/audit/identity.py +0 -0
  142. {agentmetry-0.9.7 → agentmetry-0.9.9}/agentmetry/core/audit/ingest.py +0 -0
  143. {agentmetry-0.9.7 → agentmetry-0.9.9}/agentmetry/core/audit/managed_hooks.py +0 -0
  144. {agentmetry-0.9.7 → agentmetry-0.9.9}/agentmetry/core/audit/mcp_inventory_event.py +0 -0
  145. {agentmetry-0.9.7 → agentmetry-0.9.9}/agentmetry/core/audit/migrate.py +0 -0
  146. {agentmetry-0.9.7 → agentmetry-0.9.9}/agentmetry/core/audit/mitre.py +0 -0
  147. {agentmetry-0.9.7 → agentmetry-0.9.9}/agentmetry/core/audit/otel_ingest.py +0 -0
  148. {agentmetry-0.9.7 → agentmetry-0.9.9}/agentmetry/core/audit/policy.py +0 -0
  149. {agentmetry-0.9.7 → agentmetry-0.9.9}/agentmetry/core/audit/policy_settings.py +0 -0
  150. {agentmetry-0.9.7 → agentmetry-0.9.9}/agentmetry/core/audit/posture_event.py +0 -0
  151. {agentmetry-0.9.7 → agentmetry-0.9.9}/agentmetry/core/audit/redaction.py +0 -0
  152. {agentmetry-0.9.7 → agentmetry-0.9.9}/agentmetry/core/audit/replay.py +0 -0
  153. {agentmetry-0.9.7 → agentmetry-0.9.9}/agentmetry/core/audit/run_context.py +0 -0
  154. {agentmetry-0.9.7 → agentmetry-0.9.9}/agentmetry/core/audit/signing.py +0 -0
  155. {agentmetry-0.9.7 → agentmetry-0.9.9}/agentmetry/core/audit/sinks.py +0 -0
  156. {agentmetry-0.9.7 → agentmetry-0.9.9}/agentmetry/core/audit/spool.py +0 -0
  157. {agentmetry-0.9.7 → agentmetry-0.9.9}/agentmetry/core/audit/tool_policy/__init__.py +0 -0
  158. {agentmetry-0.9.7 → agentmetry-0.9.9}/agentmetry/core/audit/tool_policy/capability.py +0 -0
  159. {agentmetry-0.9.7 → agentmetry-0.9.9}/agentmetry/core/audit/tool_policy/evaluator.py +0 -0
  160. {agentmetry-0.9.7 → agentmetry-0.9.9}/agentmetry/core/audit/tool_policy/loader.py +0 -0
  161. {agentmetry-0.9.7 → agentmetry-0.9.9}/agentmetry/core/audit/tool_policy/models.py +0 -0
  162. {agentmetry-0.9.7 → agentmetry-0.9.9}/agentmetry/core/audit/trail_anchor.py +0 -0
  163. {agentmetry-0.9.7 → agentmetry-0.9.9}/agentmetry/core/audit/trail_chain.py +0 -0
  164. {agentmetry-0.9.7 → agentmetry-0.9.9}/agentmetry/core/audit/trail_merkle.py +0 -0
  165. {agentmetry-0.9.7 → agentmetry-0.9.9}/agentmetry/core/audit/trail_rotation.py +0 -0
  166. {agentmetry-0.9.7 → agentmetry-0.9.9}/agentmetry/core/auth.py +0 -0
  167. {agentmetry-0.9.7 → agentmetry-0.9.9}/agentmetry/core/bus/__init__.py +0 -0
  168. {agentmetry-0.9.7 → agentmetry-0.9.9}/agentmetry/core/bus/audit_exporter.py +0 -0
  169. {agentmetry-0.9.7 → agentmetry-0.9.9}/agentmetry/core/bus/bridges.py +0 -0
  170. {agentmetry-0.9.7 → agentmetry-0.9.9}/agentmetry/core/bus/bus.py +0 -0
  171. {agentmetry-0.9.7 → agentmetry-0.9.9}/agentmetry/core/bus/events.py +0 -0
  172. {agentmetry-0.9.7 → agentmetry-0.9.9}/agentmetry/core/bus/outbox.py +0 -0
  173. {agentmetry-0.9.7 → agentmetry-0.9.9}/agentmetry/core/dashboard_session.py +0 -0
  174. {agentmetry-0.9.7 → agentmetry-0.9.9}/agentmetry/core/diagnostics/__init__.py +0 -0
  175. {agentmetry-0.9.7 → agentmetry-0.9.9}/agentmetry/core/diagnostics/autostart.py +0 -0
  176. {agentmetry-0.9.7 → agentmetry-0.9.9}/agentmetry/core/diagnostics/driver_paths.py +0 -0
  177. {agentmetry-0.9.7 → agentmetry-0.9.9}/agentmetry/core/diagnostics/env_file.py +0 -0
  178. {agentmetry-0.9.7 → agentmetry-0.9.9}/agentmetry/core/diagnostics/mcp_schema.py +0 -0
  179. {agentmetry-0.9.7 → agentmetry-0.9.9}/agentmetry/core/drivers/__init__.py +0 -0
  180. {agentmetry-0.9.7 → agentmetry-0.9.9}/agentmetry/core/drivers/host.py +0 -0
  181. {agentmetry-0.9.7 → agentmetry-0.9.9}/agentmetry/core/drivers/permissions.py +0 -0
  182. {agentmetry-0.9.7 → agentmetry-0.9.9}/agentmetry/core/drivers/spec.py +0 -0
  183. {agentmetry-0.9.7 → agentmetry-0.9.9}/agentmetry/core/extensions.py +0 -0
  184. {agentmetry-0.9.7 → agentmetry-0.9.9}/agentmetry/core/health.py +0 -0
  185. {agentmetry-0.9.7 → agentmetry-0.9.9}/agentmetry/core/operator_identity.py +0 -0
  186. {agentmetry-0.9.7 → agentmetry-0.9.9}/agentmetry/demo.py +0 -0
  187. {agentmetry-0.9.7 → agentmetry-0.9.9}/agentmetry/hooks/__init__.py +0 -0
  188. {agentmetry-0.9.7 → agentmetry-0.9.9}/agentmetry/hooks/mcp_proxy.py +0 -0
  189. {agentmetry-0.9.7 → agentmetry-0.9.9}/agentmetry/policies/detection/manifest.yaml +0 -0
  190. {agentmetry-0.9.7 → agentmetry-0.9.9}/agentmetry/policies/dlp/manifest.yaml +0 -0
  191. {agentmetry-0.9.7 → agentmetry-0.9.9}/agentmetry/policies/opa/agent_rules.rego +0 -0
  192. {agentmetry-0.9.7 → agentmetry-0.9.9}/agentmetry/policies/tool/manifest.yaml +0 -0
  193. {agentmetry-0.9.7 → agentmetry-0.9.9}/tests/fixtures/agt_filesink_exfil.jsonl +0 -0
  194. {agentmetry-0.9.7 → agentmetry-0.9.9}/tests/fixtures/agt_filesink_mixed.jsonl +0 -0
  195. {agentmetry-0.9.7 → agentmetry-0.9.9}/tests/fixtures/codex_hooks/README.md +0 -0
  196. {agentmetry-0.9.7 → agentmetry-0.9.9}/tests/fixtures/codex_hooks/permission-request.command.output.schema.json +0 -0
  197. {agentmetry-0.9.7 → agentmetry-0.9.9}/tests/fixtures/codex_hooks/pre-tool-use.command.output.schema.json +0 -0
  198. {agentmetry-0.9.7 → agentmetry-0.9.9}/tests/fixtures/fake_mcp_server.py +0 -0
  199. {agentmetry-0.9.7 → agentmetry-0.9.9}/tests/fixtures/otel_claude_code_session.json +0 -0
  200. {agentmetry-0.9.7 → agentmetry-0.9.9}/tests/test_agentmetry_audit.py +0 -0
  201. {agentmetry-0.9.7 → agentmetry-0.9.9}/tests/test_agentmetry_ingest_client.py +0 -0
  202. {agentmetry-0.9.7 → agentmetry-0.9.9}/tests/test_agt_adapter.py +0 -0
  203. {agentmetry-0.9.7 → agentmetry-0.9.9}/tests/test_ask_gate_phase_a.py +0 -0
  204. {agentmetry-0.9.7 → agentmetry-0.9.9}/tests/test_atlas_detection_enrichment.py +0 -0
  205. {agentmetry-0.9.7 → agentmetry-0.9.9}/tests/test_atlas_mapping.py +0 -0
  206. {agentmetry-0.9.7 → agentmetry-0.9.9}/tests/test_audit_sinks.py +0 -0
  207. {agentmetry-0.9.7 → agentmetry-0.9.9}/tests/test_audit_stats.py +0 -0
  208. {agentmetry-0.9.7 → agentmetry-0.9.9}/tests/test_audit_tail.py +0 -0
  209. {agentmetry-0.9.7 → agentmetry-0.9.9}/tests/test_auth.py +0 -0
  210. {agentmetry-0.9.7 → agentmetry-0.9.9}/tests/test_auth_default.py +0 -0
  211. {agentmetry-0.9.7 → agentmetry-0.9.9}/tests/test_autostart.py +0 -0
  212. {agentmetry-0.9.7 → agentmetry-0.9.9}/tests/test_boot_sequence.py +0 -0
  213. {agentmetry-0.9.7 → agentmetry-0.9.9}/tests/test_burst_windows.py +0 -0
  214. {agentmetry-0.9.7 → agentmetry-0.9.9}/tests/test_chinese_agent_hooks.py +0 -0
  215. {agentmetry-0.9.7 → agentmetry-0.9.9}/tests/test_chinese_agent_sprint_b.py +0 -0
  216. {agentmetry-0.9.7 → agentmetry-0.9.9}/tests/test_chinese_agent_sprint_c.py +0 -0
  217. {agentmetry-0.9.7 → agentmetry-0.9.9}/tests/test_chronicle_adapter.py +0 -0
  218. {agentmetry-0.9.7 → agentmetry-0.9.9}/tests/test_cli_backup.py +0 -0
  219. {agentmetry-0.9.7 → agentmetry-0.9.9}/tests/test_cli_commands.py +0 -0
  220. {agentmetry-0.9.7 → agentmetry-0.9.9}/tests/test_cli_env_var_names.py +0 -0
  221. {agentmetry-0.9.7 → agentmetry-0.9.9}/tests/test_cloudevents_adapter.py +0 -0
  222. {agentmetry-0.9.7 → agentmetry-0.9.9}/tests/test_compliance_digest.py +0 -0
  223. {agentmetry-0.9.7 → agentmetry-0.9.9}/tests/test_container_and_legacy_runtime.py +0 -0
  224. {agentmetry-0.9.7 → agentmetry-0.9.9}/tests/test_copilot_hooks.py +0 -0
  225. {agentmetry-0.9.7 → agentmetry-0.9.9}/tests/test_crewai_adapter.py +0 -0
  226. {agentmetry-0.9.7 → agentmetry-0.9.9}/tests/test_demo.py +0 -0
  227. {agentmetry-0.9.7 → agentmetry-0.9.9}/tests/test_detection_adi.py +0 -0
  228. {agentmetry-0.9.7 → agentmetry-0.9.9}/tests/test_detection_benchmark.py +0 -0
  229. {agentmetry-0.9.7 → agentmetry-0.9.9}/tests/test_detection_default_config.py +0 -0
  230. {agentmetry-0.9.7 → agentmetry-0.9.9}/tests/test_detection_disposition.py +0 -0
  231. {agentmetry-0.9.7 → agentmetry-0.9.9}/tests/test_detection_disposition_api.py +0 -0
  232. {agentmetry-0.9.7 → agentmetry-0.9.9}/tests/test_detection_duplicate_emission.py +0 -0
  233. {agentmetry-0.9.7 → agentmetry-0.9.9}/tests/test_detection_emit_durability.py +0 -0
  234. {agentmetry-0.9.7 → agentmetry-0.9.9}/tests/test_detection_engine.py +0 -0
  235. {agentmetry-0.9.7 → agentmetry-0.9.9}/tests/test_detection_evasion.py +0 -0
  236. {agentmetry-0.9.7 → agentmetry-0.9.9}/tests/test_detection_hf_incident.py +0 -0
  237. {agentmetry-0.9.7 → agentmetry-0.9.9}/tests/test_detection_quoted_command_words.py +0 -0
  238. {agentmetry-0.9.7 → agentmetry-0.9.9}/tests/test_detection_rule_identity.py +0 -0
  239. {agentmetry-0.9.7 → agentmetry-0.9.9}/tests/test_detection_rules_v2.py +0 -0
  240. {agentmetry-0.9.7 → agentmetry-0.9.9}/tests/test_dlp_agent_env_override.py +0 -0
  241. {agentmetry-0.9.7 → agentmetry-0.9.9}/tests/test_dlp_invisible_unicode.py +0 -0
  242. {agentmetry-0.9.7 → agentmetry-0.9.9}/tests/test_dlp_markdown_exfil.py +0 -0
  243. {agentmetry-0.9.7 → agentmetry-0.9.9}/tests/test_dlp_scanner.py +0 -0
  244. {agentmetry-0.9.7 → agentmetry-0.9.9}/tests/test_dogfood.py +0 -0
  245. {agentmetry-0.9.7 → agentmetry-0.9.9}/tests/test_dogfood_freeze.py +0 -0
  246. {agentmetry-0.9.7 → agentmetry-0.9.9}/tests/test_driver_paths.py +0 -0
  247. {agentmetry-0.9.7 → agentmetry-0.9.9}/tests/test_ecs_threat.py +0 -0
  248. {agentmetry-0.9.7 → agentmetry-0.9.9}/tests/test_event_bus.py +0 -0
  249. {agentmetry-0.9.7 → agentmetry-0.9.9}/tests/test_event_signing.py +0 -0
  250. {agentmetry-0.9.7 → agentmetry-0.9.9}/tests/test_evidence_pack.py +0 -0
  251. {agentmetry-0.9.7 → agentmetry-0.9.9}/tests/test_extensions.py +0 -0
  252. {agentmetry-0.9.7 → agentmetry-0.9.9}/tests/test_external_ingest.py +0 -0
  253. {agentmetry-0.9.7 → agentmetry-0.9.9}/tests/test_forwarder.py +0 -0
  254. {agentmetry-0.9.7 → agentmetry-0.9.9}/tests/test_forwarding_shape.py +0 -0
  255. {agentmetry-0.9.7 → agentmetry-0.9.9}/tests/test_funding_manifest.py +0 -0
  256. {agentmetry-0.9.7 → agentmetry-0.9.9}/tests/test_hash_key.py +0 -0
  257. {agentmetry-0.9.7 → agentmetry-0.9.9}/tests/test_hook_coverage.py +0 -0
  258. {agentmetry-0.9.7 → agentmetry-0.9.9}/tests/test_hook_decision_format.py +0 -0
  259. {agentmetry-0.9.7 → agentmetry-0.9.9}/tests/test_hook_enforcement_timing.py +0 -0
  260. {agentmetry-0.9.7 → agentmetry-0.9.9}/tests/test_hook_import_cost.py +0 -0
  261. {agentmetry-0.9.7 → agentmetry-0.9.9}/tests/test_hook_real_process.py +0 -0
  262. {agentmetry-0.9.7 → agentmetry-0.9.9}/tests/test_hook_spool.py +0 -0
  263. {agentmetry-0.9.7 → agentmetry-0.9.9}/tests/test_identity_enrich.py +0 -0
  264. {agentmetry-0.9.7 → agentmetry-0.9.9}/tests/test_identity_provider.py +0 -0
  265. {agentmetry-0.9.7 → agentmetry-0.9.9}/tests/test_ingest_roundtrip.py +0 -0
  266. {agentmetry-0.9.7 → agentmetry-0.9.9}/tests/test_isolation.py +0 -0
  267. {agentmetry-0.9.7 → agentmetry-0.9.9}/tests/test_launch_targets.py +0 -0
  268. {agentmetry-0.9.7 → agentmetry-0.9.9}/tests/test_live_detection.py +0 -0
  269. {agentmetry-0.9.7 → agentmetry-0.9.9}/tests/test_live_detection_store.py +0 -0
  270. {agentmetry-0.9.7 → agentmetry-0.9.9}/tests/test_machine_view.py +0 -0
  271. {agentmetry-0.9.7 → agentmetry-0.9.9}/tests/test_managed_hooks.py +0 -0
  272. {agentmetry-0.9.7 → agentmetry-0.9.9}/tests/test_mcp_audit_proxy.py +0 -0
  273. {agentmetry-0.9.7 → agentmetry-0.9.9}/tests/test_mcp_concealed_text.py +0 -0
  274. {agentmetry-0.9.7 → agentmetry-0.9.9}/tests/test_mcp_inventory.py +0 -0
  275. {agentmetry-0.9.7 → agentmetry-0.9.9}/tests/test_mcp_inventory_event.py +0 -0
  276. {agentmetry-0.9.7 → agentmetry-0.9.9}/tests/test_mcp_meta_fingerprint.py +0 -0
  277. {agentmetry-0.9.7 → agentmetry-0.9.9}/tests/test_mcp_proxy_packaged.py +0 -0
  278. {agentmetry-0.9.7 → agentmetry-0.9.9}/tests/test_mcp_schema.py +0 -0
  279. {agentmetry-0.9.7 → agentmetry-0.9.9}/tests/test_mitre_dlp.py +0 -0
  280. {agentmetry-0.9.7 → agentmetry-0.9.9}/tests/test_opensre_adapter.py +0 -0
  281. {agentmetry-0.9.7 → agentmetry-0.9.9}/tests/test_operator_attribution.py +0 -0
  282. {agentmetry-0.9.7 → agentmetry-0.9.9}/tests/test_otel_ingest.py +0 -0
  283. {agentmetry-0.9.7 → agentmetry-0.9.9}/tests/test_paths.py +0 -0
  284. {agentmetry-0.9.7 → agentmetry-0.9.9}/tests/test_posture.py +0 -0
  285. {agentmetry-0.9.7 → agentmetry-0.9.9}/tests/test_powershell_encoding.py +0 -0
  286. {agentmetry-0.9.7 → agentmetry-0.9.9}/tests/test_readme_claims.py +0 -0
  287. {agentmetry-0.9.7 → agentmetry-0.9.9}/tests/test_redaction.py +0 -0
  288. {agentmetry-0.9.7 → agentmetry-0.9.9}/tests/test_replay.py +0 -0
  289. {agentmetry-0.9.7 → agentmetry-0.9.9}/tests/test_sentinel.py +0 -0
  290. {agentmetry-0.9.7 → agentmetry-0.9.9}/tests/test_serve_script_binds_loopback.py +0 -0
  291. {agentmetry-0.9.7 → agentmetry-0.9.9}/tests/test_shared_install.py +0 -0
  292. {agentmetry-0.9.7 → agentmetry-0.9.9}/tests/test_sigma_pack.py +0 -0
  293. {agentmetry-0.9.7 → agentmetry-0.9.9}/tests/test_static_serving.py +0 -0
  294. {agentmetry-0.9.7 → agentmetry-0.9.9}/tests/test_subagent_lifecycle.py +0 -0
  295. {agentmetry-0.9.7 → agentmetry-0.9.9}/tests/test_suite_leaves_real_home_alone.py +0 -0
  296. {agentmetry-0.9.7 → agentmetry-0.9.9}/tests/test_tool_policy.py +0 -0
  297. {agentmetry-0.9.7 → agentmetry-0.9.9}/tests/test_tool_policy_agent_cli.py +0 -0
  298. {agentmetry-0.9.7 → agentmetry-0.9.9}/tests/test_tool_policy_git_hooks.py +0 -0
  299. {agentmetry-0.9.7 → agentmetry-0.9.9}/tests/test_trail_anchor.py +0 -0
  300. {agentmetry-0.9.7 → agentmetry-0.9.9}/tests/test_trail_chain.py +0 -0
  301. {agentmetry-0.9.7 → agentmetry-0.9.9}/tests/test_trail_concurrency.py +0 -0
  302. {agentmetry-0.9.7 → agentmetry-0.9.9}/tests/test_trail_merkle.py +0 -0
  303. {agentmetry-0.9.7 → agentmetry-0.9.9}/tests/test_trusted_host.py +0 -0
  304. {agentmetry-0.9.7 → agentmetry-0.9.9}/tests/test_unattended_agent_policy.py +0 -0
  305. {agentmetry-0.9.7 → agentmetry-0.9.9}/tests/test_version.py +0 -0
  306. {agentmetry-0.9.7 → agentmetry-0.9.9}/tests/test_yaml_detection_rules.py +0 -0
  307. {agentmetry-0.9.7 → agentmetry-0.9.9}/tools/generate_sigma_pack.py +0 -0
  308. {agentmetry-0.9.7 → agentmetry-0.9.9}/tools/mcp_audit_proxy.py +0 -0
  309. {agentmetry-0.9.7 → agentmetry-0.9.9}/tools/vault_fs_server.py +0 -0
@@ -1,7 +1,7 @@
1
1
  Metadata-Version: 2.5
2
2
  Name: agentmetry
3
- Version: 0.9.7
4
- Summary: Local-first flight recorder for AI coding agents: hash-chained audit trail, MITRE-mapped sequence detection, DLP
3
+ Version: 0.9.9
4
+ Summary: Local-first endpoint sensor for AI coding agents: hash-chained audit trail, MITRE-mapped sequence detection, DLP
5
5
  Project-URL: Homepage, https://agentmetry.ai
6
6
  Project-URL: Source, https://github.com/blitzcrieg1/agentmetry
7
7
  Project-URL: Issues, https://github.com/blitzcrieg1/agentmetry/issues
@@ -37,12 +37,12 @@ Provides-Extra: dev
37
37
  Requires-Dist: pytest-asyncio>=0.24; extra == 'dev'
38
38
  Requires-Dist: pytest-cov>=6.0; extra == 'dev'
39
39
  Requires-Dist: pytest>=8.0; extra == 'dev'
40
- Requires-Dist: ruff==0.16.10; extra == 'dev'
40
+ Requires-Dist: ruff==0.17.1; extra == 'dev'
41
41
  Description-Content-Type: text/markdown
42
42
 
43
43
  # Agentmetry
44
44
 
45
- A local-first flight recorder for AI coding agents.
45
+ A local-first endpoint sensor for AI coding agents.
46
46
 
47
47
  Agentmetry hooks the tool lifecycle of Claude Code, Cursor, Codex and Antigravity,
48
48
  writes every tool call, approval and denial to a hash-chained JSONL trail on your
@@ -1,6 +1,6 @@
1
1
  # Agentmetry
2
2
 
3
- A local-first flight recorder for AI coding agents.
3
+ A local-first endpoint sensor for AI coding agents.
4
4
 
5
5
  Agentmetry hooks the tool lifecycle of Claude Code, Cursor, Codex and Antigravity,
6
6
  writes every tool call, approval and denial to a hash-chained JSONL trail on your
@@ -2,6 +2,9 @@ from __future__ import annotations
2
2
 
3
3
  import asyncio
4
4
  import logging
5
+ import os
6
+ import subprocess
7
+ import sys
5
8
  from contextlib import asynccontextmanager
6
9
  from logging.handlers import RotatingFileHandler
7
10
  from pathlib import Path
@@ -76,6 +79,34 @@ def _start_forwarders() -> list[asyncio.Task]:
76
79
  return tasks
77
80
 
78
81
 
82
+ def start_transcript_watcher(watcher_path: Path) -> subprocess.Popen:
83
+ """Start the Antigravity transcript watcher, tied to this process's life.
84
+
85
+ Its stdin is a pipe only this process holds, and the lifeline variable
86
+ tells it to exit at end-of-file. When this process dies, however it dies,
87
+ the pipe closes and the watcher follows. Without it each restart left a
88
+ watcher behind, and every one of them recorded every Antigravity call.
89
+ """
90
+ return subprocess.Popen(
91
+ [sys.executable, str(watcher_path)],
92
+ stdin=subprocess.PIPE,
93
+ stdout=subprocess.DEVNULL,
94
+ stderr=subprocess.DEVNULL,
95
+ env={**os.environ, "AGENTMETRY_WATCHER_LIFELINE": "1"},
96
+ )
97
+
98
+
99
+ def stop_transcript_watcher(process: subprocess.Popen) -> None:
100
+ """Close the lifeline, then terminate. Closing alone is enough; terminate is
101
+ for a watcher that predates the lifeline."""
102
+ try:
103
+ if process.stdin:
104
+ process.stdin.close()
105
+ except OSError:
106
+ pass
107
+ process.terminate()
108
+
109
+
79
110
  @asynccontextmanager
80
111
  async def lifespan(app: FastAPI):
81
112
  from agentmetry.core.audit.migrate import backfill_db_from_jsonl
@@ -177,17 +208,11 @@ async def lifespan(app: FastAPI):
177
208
  )
178
209
 
179
210
  # Launch transcript watcher for Antigravity in the background
180
- import subprocess
181
- import sys
182
211
  watcher_process = None
183
212
  try:
184
213
  watcher_path = Path(__file__).resolve().parents[4] / "scripts" / "antigravity_transcript_watcher.py"
185
214
  if watcher_path.exists():
186
- watcher_process = subprocess.Popen(
187
- [sys.executable, str(watcher_path)],
188
- stdout=subprocess.DEVNULL,
189
- stderr=subprocess.DEVNULL,
190
- )
215
+ watcher_process = start_transcript_watcher(watcher_path)
191
216
  logger.info("Started Antigravity transcript watcher (PID: %s)", watcher_process.pid)
192
217
  except Exception as exc:
193
218
  logger.warning("Failed to start Antigravity transcript watcher: %s", exc)
@@ -201,7 +226,7 @@ async def lifespan(app: FastAPI):
201
226
  for task in bridge_tasks:
202
227
  task.cancel()
203
228
  if watcher_process:
204
- watcher_process.terminate()
229
+ stop_transcript_watcher(watcher_process)
205
230
 
206
231
 
207
232
  app = FastAPI(
@@ -101,11 +101,11 @@ class ExternalIngestBody(BaseModel):
101
101
 
102
102
  source_app: str = Field(
103
103
  ...,
104
- description="cursor | claude | antigravity | codex | copilot | mcp_proxy | qwen | kimi | qoder | codebuddy | trae | crewai | opensre",
104
+ description="cursor | claude | antigravity | codex | copilot | mcp_proxy | qwen | kimi | qoder | codebuddy | trae | crewai | opensre | zcode | grok_bot",
105
105
  )
106
106
  event_type: str = Field(
107
107
  ...,
108
- description="session_start | session_end | tool_called | tool_denied | tool_failed | approval_request | approval_response | mcp_schema",
108
+ description="session_start | session_end | tool_called | tool_denied | tool_failed | approval_request | approval_response | mcp_schema | hook_remediation",
109
109
  )
110
110
  correlation_id: str = ""
111
111
  session_id: str = ""
@@ -309,6 +309,29 @@ async def audit_session(
309
309
  }
310
310
 
311
311
 
312
+ @router.get("/session/{correlation_id}/tree", dependencies=[Depends(require_api_key)])
313
+ async def audit_session_tree(
314
+ correlation_id: str,
315
+ limit: int = Query(20000, ge=1, le=50000),
316
+ around_utc: str = Query("", max_length=40),
317
+ ):
318
+ """One session folded into investigation-tree nodes.
319
+
320
+ Each ask, its answer and the call it led to become one node, so a detection
321
+ that cites the ask and a policy verdict recorded on it land on the call
322
+ they belong to. A session longer than `limit` is windowed on `around_utc`
323
+ (a detection's first_seen_utc) when given. See `core/audit/session_tree.py`.
324
+ """
325
+ from agentmetry.core.audit.session_tree import session_tree
326
+
327
+ if not settings.audit_export_enabled:
328
+ return {"correlation_id": correlation_id, "enabled": False, "nodes": [], "summary": {}, "truncated": False}
329
+ try:
330
+ return await asyncio.to_thread(session_tree, correlation_id, limit, around_utc)
331
+ except Exception as exc:
332
+ raise HTTPException(status_code=500, detail=str(exc)) from exc
333
+
334
+
312
335
  @router.get("/detections/{correlation_id}", dependencies=[Depends(require_api_key)])
313
336
  async def audit_detections(correlation_id: str):
314
337
  """Run correlated behavioral rules over one session and return detections.
@@ -48,6 +48,70 @@ def _api_base_url(port: int) -> str:
48
48
  return os.environ.get("AGENTMETRY_URL", "").strip().rstrip("/") or _base_url(port)
49
49
 
50
50
 
51
+ def _add_dir_flags(parser: argparse.ArgumentParser) -> None:
52
+ """The path split. Omit the flags and nothing moves.
53
+
54
+ `SUPPRESS`, so the same flag on `agentmetry` and on the subcommand do not
55
+ overwrite each other with a default of None.
56
+ """
57
+ parser.add_argument(
58
+ "--data-dir", default=argparse.SUPPRESS,
59
+ help="trail, spool, token and logs (env AGENTMETRY_DATA_DIR)",
60
+ )
61
+ parser.add_argument(
62
+ "--user-profile-dir", default=argparse.SUPPRESS,
63
+ help="the developer home whose IDE configs to install into and check "
64
+ "(env AGENTMETRY_USER_PROFILE_DIR); for a recorder run as root or LocalSystem",
65
+ )
66
+ parser.add_argument(
67
+ "--policy-dir", default=argparse.SUPPRESS,
68
+ help="an extra managed policy location, checked like the platform one "
69
+ "(env AGENTMETRY_POLICY_DIR)",
70
+ )
71
+
72
+
73
+ def _apply_dir_flags(args: argparse.Namespace) -> None:
74
+ """Export the path flags before a command reads data_dir() or the profile.
75
+
76
+ Exported, not passed: `start` spawns `serve`, and the hook commands it
77
+ writes run in other processes, all of which read the environment.
78
+ """
79
+ global _DATA_DIR, _PID_FILE
80
+ if getattr(args, "user_profile_dir", None):
81
+ os.environ["AGENTMETRY_USER_PROFILE_DIR"] = args.user_profile_dir
82
+ if getattr(args, "policy_dir", None):
83
+ os.environ["AGENTMETRY_POLICY_DIR"] = args.policy_dir
84
+ if getattr(args, "data_dir", None):
85
+ os.environ["AGENTMETRY_DATA_DIR"] = args.data_dir
86
+ # Only when the flag was given: a caller that already pointed these at
87
+ # a temp tree must not have them recomputed under it.
88
+ _DATA_DIR = data_dir()
89
+ _PID_FILE = _DATA_DIR / "agentmetry.pid"
90
+
91
+
92
+ def _apply_managed_policy(args: argparse.Namespace) -> None:
93
+ """Locks over flags and environment, for this process and everything it starts.
94
+
95
+ After `_apply_dir_flags`, so `--data-dir` cannot move a trail a fleet
96
+ locked. Says so when it overrides a flag: a silently ignored flag reads as
97
+ a bug.
98
+ """
99
+ global _DATA_DIR, _PID_FILE
100
+ from agentmetry.core import managed_policy
101
+
102
+ try:
103
+ policy = managed_policy.apply_locks()
104
+ except Exception as exc:
105
+ print(f"Managed policy was not applied: {exc}", file=sys.stderr)
106
+ return
107
+ if "data_dir" not in policy.locked:
108
+ return
109
+ if getattr(args, "data_dir", None):
110
+ print("--data-dir ignored: managed policy locks data_dir.", file=sys.stderr)
111
+ _DATA_DIR = data_dir()
112
+ _PID_FILE = _DATA_DIR / "agentmetry.pid"
113
+
114
+
51
115
  def _api_headers() -> dict[str, str]:
52
116
  from agentmetry.core.api_token import client_token
53
117
 
@@ -551,6 +615,7 @@ def cmd_cloud(args: argparse.Namespace) -> int:
551
615
  map_claude_messages,
552
616
  map_claude_session,
553
617
  map_cursor_agent,
618
+ map_outbound_poll,
554
619
  )
555
620
 
556
621
  base = (args.url or "").strip() or None
@@ -576,7 +641,9 @@ def cmd_cloud(args: argparse.Namespace) -> int:
576
641
  if not api_key:
577
642
  print("CURSOR_API_KEY is not set.")
578
643
  return 2
579
- for agent in fetch_cursor_agents(api_key, base or __import__("agentmetry.core.audit.cloud_bridge", fromlist=["CURSOR_DEFAULT_BASE"]).CURSOR_DEFAULT_BASE):
644
+ cursor_base = base or __import__("agentmetry.core.audit.cloud_bridge", fromlist=["CURSOR_DEFAULT_BASE"]).CURSOR_DEFAULT_BASE
645
+ post(map_outbound_poll("cursor_cloud", "GET", f"{cursor_base.rstrip('/')}/v1/agents"))
646
+ for agent in fetch_cursor_agents(api_key, cursor_base):
580
647
  runs = []
581
648
  if not args.skip_runs:
582
649
  try:
@@ -594,7 +661,9 @@ def cmd_cloud(args: argparse.Namespace) -> int:
594
661
  print("ANTHROPIC_COMPLIANCE_KEY is not set (a Compliance Access Key with read:compliance_user_data, not an admin key).")
595
662
  return 2
596
663
  kind = "remote" if args.remote else "local"
597
- sessions = fetch_claude_sessions(api_key, kind=kind, base=base or "https://api.anthropic.com", limit=args.limit)
664
+ claude_base = base or "https://api.anthropic.com"
665
+ post(map_outbound_poll("claude_cloud", "GET", f"{claude_base.rstrip('/')}/v1/compliance/apps/sessions/{kind}"))
666
+ sessions = fetch_claude_sessions(api_key, kind=kind, base=claude_base, limit=args.limit)
598
667
  for session in sessions[: args.limit]:
599
668
  post(map_claude_session(session))
600
669
  try:
@@ -631,11 +700,12 @@ def _hooks_status() -> int:
631
700
  """
632
701
  from agentmetry.core.diagnostics import hook_coverage
633
702
 
634
- if hook_coverage.is_service_profile():
703
+ if hook_coverage.profile_blind():
635
704
  print(
636
705
  "UNDETERMINABLE: running under a service profile. Hook configuration "
637
706
  "lives in each developer's profile and none is visible from here. "
638
- "Run this in the user's context.",
707
+ "Run this in the user's context, or name the developer's home with "
708
+ "--user-profile-dir.",
639
709
  file=sys.stderr,
640
710
  )
641
711
  return 2
@@ -663,6 +733,21 @@ def cmd_hooks(args: argparse.Namespace) -> int:
663
733
  from agentmetry.core.audit import hook_bootstrap
664
734
  from agentmetry.core.diagnostics import hook_coverage
665
735
 
736
+ if args.action == "install":
737
+ # Before --managed too: a managed install would put the temporary path
738
+ # into machine-wide policy files.
739
+ reason = hook_bootstrap.ephemeral_environment()
740
+ if reason:
741
+ print(
742
+ f"Not installing hooks: {reason}. That cache can be deleted at any "
743
+ "time, and every hook would then fail (most agents silently stop "
744
+ "recording; Copilot CLI blocks every tool call). Install Agentmetry "
745
+ "somewhere that stays, then run this again:\n"
746
+ " uv tool install agentmetry (or: pipx install agentmetry, or: pip install agentmetry)\n"
747
+ " agentmetry hooks install",
748
+ file=sys.stderr,
749
+ )
750
+ return 1
666
751
  if getattr(args, "managed", False):
667
752
  return _hooks_managed(args)
668
753
  if args.action == "status":
@@ -1394,10 +1479,10 @@ def cmd_demo(args: argparse.Namespace) -> int:
1394
1479
  def cmd_doctor(args: argparse.Namespace) -> int:
1395
1480
  """SIEM preflight: manifests, trail chain, orchestrator health, hooks."""
1396
1481
  sys.path.insert(0, str(_ORCH_ROOT))
1397
- from agentmetry.core.diagnostics.doctor import format_report, run_doctor
1482
+ from agentmetry.core.diagnostics.doctor import print_report, run_doctor
1398
1483
 
1399
1484
  report = run_doctor(fix_drivers=getattr(args, "fix", False))
1400
- print("Agentmetry doctor\n" + format_report(report))
1485
+ print_report(report, as_json=getattr(args, "json", False))
1401
1486
  return report.exit_code
1402
1487
 
1403
1488
 
@@ -1431,6 +1516,7 @@ def main(argv: list[str] | None = None) -> int:
1431
1516
  prog="agentmetry",
1432
1517
  description="Agentmetry local ops. New here? Start with `agentmetry demo`.",
1433
1518
  )
1519
+ _add_dir_flags(parser)
1434
1520
  parser.add_argument("--port", type=int, default=8000)
1435
1521
  sub = parser.add_subparsers(dest="command", required=True)
1436
1522
  # First, so it heads the help: it is the one command that needs nothing set up.
@@ -1445,6 +1531,7 @@ def main(argv: list[str] | None = None) -> int:
1445
1531
 
1446
1532
  sub.add_parser("stop", help="stop the orchestrator")
1447
1533
  start = sub.add_parser("start", help="start the orchestrator (detached)")
1534
+ _add_dir_flags(start)
1448
1535
  start.add_argument(
1449
1536
  "--host",
1450
1537
  default="127.0.0.1",
@@ -1454,12 +1541,14 @@ def main(argv: list[str] | None = None) -> int:
1454
1541
  "serve",
1455
1542
  help="run the orchestrator in the foreground (what autostart registers)",
1456
1543
  )
1544
+ _add_dir_flags(serve)
1457
1545
  serve.add_argument("--host", default="127.0.0.1")
1458
1546
  serve.add_argument("--port", type=int, default=8000)
1459
1547
  hooks = sub.add_parser(
1460
1548
  "hooks",
1461
1549
  help="install IDE hook configs for the agents on this machine",
1462
1550
  )
1551
+ _add_dir_flags(hooks)
1463
1552
  hooks.add_argument(
1464
1553
  "action",
1465
1554
  choices=("install", "status"),
@@ -1661,6 +1750,13 @@ def main(argv: list[str] | None = None) -> int:
1661
1750
  doctor = sub.add_parser(
1662
1751
  "doctor", help="SIEM preflight (manifests, trail chain, health, hooks)"
1663
1752
  )
1753
+ _add_dir_flags(doctor)
1754
+ doctor.add_argument(
1755
+ "--json",
1756
+ action="store_true",
1757
+ help="the report as JSON, for a deployment script: exit_code, "
1758
+ "orchestrator_reachable, hooks, policy and findings",
1759
+ )
1664
1760
  doctor.add_argument(
1665
1761
  "--fix",
1666
1762
  action="store_true",
@@ -1688,6 +1784,8 @@ def main(argv: list[str] | None = None) -> int:
1688
1784
  replay.add_argument("--trail", default="", help="trail to read (default: the configured trail)")
1689
1785
 
1690
1786
  args = parser.parse_args(argv)
1787
+ _apply_dir_flags(args)
1788
+ _apply_managed_policy(args)
1691
1789
  handlers = {
1692
1790
  "demo": cmd_demo,
1693
1791
  "start": cmd_start,
@@ -0,0 +1,81 @@
1
+ """Read tool calls out of an Antigravity conversation database.
2
+
3
+ The installed hook only matches `run_command`, so file reads, writes, search
4
+ and the other tools never reach the trail. The conversation database stores
5
+ every step. The payload is a protobuf blob. The tool name is a length-prefixed
6
+ ASCII field and the arguments are the JSON object that follows it. That framing
7
+ was read off a real database on 2026-10-16. A blob that does not match it
8
+ yields nothing.
9
+ """
10
+
11
+ from __future__ import annotations
12
+
13
+ import json
14
+ import sqlite3
15
+ from pathlib import Path
16
+ from typing import Any, Iterator
17
+
18
+
19
+ def _varint(buf: bytes, index: int) -> tuple[int, int]:
20
+ value = 0
21
+ shift = 0
22
+ while index < len(buf) and shift <= 28:
23
+ byte = buf[index]
24
+ index += 1
25
+ value |= (byte & 0x7F) << shift
26
+ if not byte & 0x80:
27
+ return value, index
28
+ shift += 7
29
+ raise ValueError("truncated varint")
30
+
31
+
32
+ def iter_tool_calls(payload: bytes) -> Iterator[tuple[str, dict[str, Any]]]:
33
+ """Yield (tool name, arguments) from one step payload."""
34
+ index = 0
35
+ while True:
36
+ marker = payload.find(b"\x12", index)
37
+ if marker < 0 or marker + 2 >= len(payload):
38
+ return
39
+ name_len = payload[marker + 1]
40
+ if name_len == 0 or name_len > 64 or name_len & 0x80:
41
+ index = marker + 1
42
+ continue
43
+ name_at = marker + 2
44
+ name_end = name_at + name_len
45
+ if name_end >= len(payload) or payload[name_end] != 0x1A:
46
+ index = marker + 1
47
+ continue
48
+ name = payload[name_at:name_end]
49
+ if not name.isascii() or not all(chr(c).isalnum() or c == ord("_") for c in name):
50
+ index = marker + 1
51
+ continue
52
+ try:
53
+ json_len, json_at = _varint(payload, name_end + 1)
54
+ except ValueError:
55
+ index = marker + 1
56
+ continue
57
+ blob = payload[json_at : json_at + json_len]
58
+ if not blob.startswith(b"{"):
59
+ index = marker + 1
60
+ continue
61
+ try:
62
+ args = json.loads(blob)
63
+ except json.JSONDecodeError:
64
+ index = marker + 1
65
+ continue
66
+ if isinstance(args, dict):
67
+ yield name.decode("ascii"), args
68
+ index = json_at + max(json_len, 1)
69
+
70
+
71
+ def tool_names(db_path: Path) -> dict[str, int]:
72
+ """Count tool names in one conversation database. Arguments are not returned."""
73
+ counts: dict[str, int] = {}
74
+ uri = f"file:{Path(db_path).as_posix()}?mode=ro"
75
+ with sqlite3.connect(uri, uri=True) as con:
76
+ for (payload,) in con.execute("SELECT step_payload FROM steps"):
77
+ if not isinstance(payload, (bytes, bytearray)):
78
+ continue
79
+ for name, _args in iter_tool_calls(payload):
80
+ counts[name] = counts.get(name, 0) + 1
81
+ return counts
@@ -26,6 +26,7 @@ from __future__ import annotations
26
26
  import hashlib
27
27
  import json
28
28
  from typing import Any
29
+ from urllib.parse import urlsplit
29
30
 
30
31
  REDACT_KEYS = frozenset(
31
32
  {"authorization", "api_key", "apikey", "password", "secret", "token", "session_key"}
@@ -110,6 +111,29 @@ def tool_summary(args: Any) -> dict[str, Any]:
110
111
  return summary
111
112
 
112
113
 
114
+ def map_outbound_poll(source_app: str, method: str, url: str) -> dict[str, Any]:
115
+ """The request this machine sends. The key is not in the payload.
116
+
117
+ Query strings are dropped: a token does not belong in the trail. The path
118
+ and host are the trace of the send.
119
+ """
120
+ parts = urlsplit(url)
121
+ target = f"{method} {parts.netloc}{parts.path}"
122
+ return {
123
+ "source_app": source_app,
124
+ "adapter": "cloud_bridge",
125
+ "event_type": "tool_called",
126
+ "outcome": "success",
127
+ "correlation_id": f"{source_app}-outbound",
128
+ "reason": "outbound poll; key sent and not stored",
129
+ "tool": {
130
+ "qualified": f"{source_app}.poll",
131
+ "server": source_app,
132
+ "command": target,
133
+ },
134
+ }
135
+
136
+
113
137
  # --- Cursor Cloud Agents API -------------------------------------------------
114
138
 
115
139
  CURSOR_DEFAULT_BASE = "https://api.cursor.com"
@@ -0,0 +1,3 @@
1
+ {"action":{"outcome":"success","reason":"decision:allow;hook:PreToolUse","type":"tool_called"},"actor":{"id":"operator","role":"operator","type":"agent"},"agent":{"name":"claude","skill_id":""},"correlation_id":"b-registry-publish","event_id":"7a7001f1-6c5b-51ab-92e7-8a72c8f6c001","fleet_id":"example-fleet","host_id":"BUILD-01","initiator":{"actor_type":"agent","operator_id":"operator","trigger":"manual"},"model":{"id":"claude","provider":"claude"},"schema_version":"1.1.0","session_id":"b-registry-publish","source":{"adapter":"claude_hook","app":"claude","tier":"external"},"source_topic":"external/claude/tool_called","timestamp_utc":"2026-08-12T10:00:00+00:00","tool":{"arguments":{"command":"docker login registry.example.com --username ci-bot --password-stdin"},"command":"docker login registry.example.com --username ci-bot --password-stdin","input_hash":"1f3b6a8a28dd96c5d2e9b8a6a722f78c87b80ac3e4d2cc9d7f5d3a44a9148d01","input_redaction":"hash+command","mitre":{"tactic":"Command and Control","tactic_id":"TA0011","technique":"Web Protocols","technique_id":"T1071.001"},"name":"Bash","parameters_redacted":false,"qualified":"Bash","server":"claude","traits":["net_egress"]}}
2
+ {"action":{"outcome":"success","reason":"decision:allow;hook:PreToolUse","type":"tool_called"},"actor":{"id":"operator","role":"operator","type":"agent"},"agent":{"name":"claude","skill_id":""},"correlation_id":"b-registry-publish","event_id":"7a7001f1-6c5b-51ab-92e7-8a72c8f6c002","fleet_id":"example-fleet","host_id":"BUILD-01","initiator":{"actor_type":"agent","operator_id":"operator","trigger":"manual"},"model":{"id":"claude","provider":"claude"},"schema_version":"1.1.0","session_id":"b-registry-publish","source":{"adapter":"claude_hook","app":"claude","tier":"external"},"source_topic":"external/claude/tool_called","timestamp_utc":"2026-08-12T10:00:30+00:00","tool":{"arguments":{"command":"docker build -t registry.example.com/team/service:build-42 ."},"command":"docker build -t registry.example.com/team/service:build-42 .","input_hash":"2a4c6d8e28dd96c5d2e9b8a6a722f78c87b80ac3e4d2cc9d7f5d3a44a9148d02","input_redaction":"hash+command","mitre":{"tactic":"Execution","tactic_id":"TA0002","technique":"Unix Shell","technique_id":"T1059.004"},"name":"Bash","parameters_redacted":false,"qualified":"Bash","server":"claude"}}
3
+ {"action":{"outcome":"success","reason":"decision:allow;hook:PreToolUse","type":"tool_called"},"actor":{"id":"operator","role":"operator","type":"agent"},"agent":{"name":"claude","skill_id":""},"correlation_id":"b-registry-publish","event_id":"7a7001f1-6c5b-51ab-92e7-8a72c8f6c003","fleet_id":"example-fleet","host_id":"BUILD-01","initiator":{"actor_type":"agent","operator_id":"operator","trigger":"manual"},"model":{"id":"claude","provider":"claude"},"schema_version":"1.1.0","session_id":"b-registry-publish","source":{"adapter":"claude_hook","app":"claude","tier":"external"},"source_topic":"external/claude/tool_called","timestamp_utc":"2026-08-12T10:01:20+00:00","tool":{"arguments":{"command":"docker push registry.example.com/team/service:build-42"},"command":"docker push registry.example.com/team/service:build-42","input_hash":"3b5d7e9f28dd96c5d2e9b8a6a722f78c87b80ac3e4d2cc9d7f5d3a44a9148d03","input_redaction":"hash+command","mitre":{"tactic":"Command and Control","tactic_id":"TA0011","technique":"Web Protocols","technique_id":"T1071.001"},"name":"Bash","parameters_redacted":false,"qualified":"Bash","server":"claude","traits":["net_egress"]}}
@@ -0,0 +1,4 @@
1
+ {"action":{"outcome":"success","reason":"decision:allow;hook:PreToolUse","type":"tool_called"},"actor":{"id":"operator","role":"operator","type":"agent"},"agent":{"name":"claude","skill_id":""},"correlation_id":"b-dependency-audit","event_id":"9c7001f1-6c5b-51ab-92e7-8a72c8f6e001","fleet_id":"example-fleet","host_id":"BUILD-01","initiator":{"actor_type":"agent","operator_id":"operator","trigger":"manual"},"model":{"id":"claude","provider":"claude"},"schema_version":"1.1.0","session_id":"b-dependency-audit","source":{"adapter":"claude_hook","app":"claude","tier":"external"},"source_topic":"external/claude/tool_called","timestamp_utc":"2026-08-12T12:00:00+00:00","tool":{"arguments":{"command":"npm audit --json > reports/npm-audit.json"},"command":"npm audit --json > reports/npm-audit.json","input_hash":"8091234428dd96c5d2e9b8a6a722f78c87b80ac3e4d2cc9d7f5d3a44a9148d08","input_redaction":"hash+command","mitre":{"tactic":"Execution","tactic_id":"TA0002","technique":"Unix Shell","technique_id":"T1059.004"},"name":"Bash","parameters_redacted":false,"qualified":"Bash","server":"claude"}}
2
+ {"action":{"outcome":"success","reason":"decision:allow;hook:PreToolUse","type":"tool_called"},"actor":{"id":"operator","role":"operator","type":"agent"},"agent":{"name":"claude","skill_id":""},"correlation_id":"b-dependency-audit","event_id":"9c7001f1-6c5b-51ab-92e7-8a72c8f6e002","fleet_id":"example-fleet","host_id":"BUILD-01","initiator":{"actor_type":"agent","operator_id":"operator","trigger":"manual"},"model":{"id":"claude","provider":"claude"},"schema_version":"1.1.0","session_id":"b-dependency-audit","source":{"adapter":"claude_hook","app":"claude","tier":"external"},"source_topic":"external/claude/tool_called","timestamp_utc":"2026-08-12T12:00:20+00:00","tool":{"arguments":{"command":"pip-audit -r requirements.txt -f json -o reports/pip-audit.json"},"command":"pip-audit -r requirements.txt -f json -o reports/pip-audit.json","input_hash":"91a2345528dd96c5d2e9b8a6a722f78c87b80ac3e4d2cc9d7f5d3a44a9148d09","input_redaction":"hash+command","mitre":{"tactic":"Execution","tactic_id":"TA0002","technique":"Unix Shell","technique_id":"T1059.004"},"name":"Bash","parameters_redacted":false,"qualified":"Bash","server":"claude"}}
3
+ {"action":{"outcome":"success","reason":"decision:allow;hook:PreToolUse","type":"tool_called"},"actor":{"id":"operator","role":"operator","type":"agent"},"agent":{"name":"claude","skill_id":""},"correlation_id":"b-dependency-audit","event_id":"9c7001f1-6c5b-51ab-92e7-8a72c8f6e003","fleet_id":"example-fleet","host_id":"BUILD-01","initiator":{"actor_type":"agent","operator_id":"operator","trigger":"manual"},"model":{"id":"claude","provider":"claude"},"schema_version":"1.1.0","session_id":"b-dependency-audit","source":{"adapter":"claude_hook","app":"claude","tier":"external"},"source_topic":"external/claude/tool_called","timestamp_utc":"2026-08-12T12:00:40+00:00","tool":{"arguments":{"command":"curl -s https://registry.npmjs.org/-/npm/v1/security/advisories?limit=20 > reports/npm-advisories.json"},"command":"curl -s https://registry.npmjs.org/-/npm/v1/security/advisories?limit=20 > reports/npm-advisories.json","input_hash":"a2b3456628dd96c5d2e9b8a6a722f78c87b80ac3e4d2cc9d7f5d3a44a9148d10","input_redaction":"hash+command","mitre":{"tactic":"Command and Control","tactic_id":"TA0011","technique":"Web Protocols","technique_id":"T1071.001"},"name":"Bash","parameters_redacted":false,"qualified":"Bash","server":"claude","traits":["net_egress"]}}
4
+ {"action":{"outcome":"success","reason":"decision:allow;hook:PreToolUse","type":"tool_called"},"actor":{"id":"operator","role":"operator","type":"agent"},"agent":{"name":"claude","skill_id":""},"correlation_id":"b-dependency-audit","event_id":"9c7001f1-6c5b-51ab-92e7-8a72c8f6e004","fleet_id":"example-fleet","host_id":"BUILD-01","initiator":{"actor_type":"agent","operator_id":"operator","trigger":"manual"},"model":{"id":"claude","provider":"claude"},"schema_version":"1.1.0","session_id":"b-dependency-audit","source":{"adapter":"claude_hook","app":"claude","tier":"external"},"source_topic":"external/claude/tool_called","timestamp_utc":"2026-08-12T12:01:20+00:00","tool":{"arguments":{"command":"python scripts/summarise_audit.py reports/npm-audit.json reports/pip-audit.json reports/npm-advisories.json"},"command":"python scripts/summarise_audit.py reports/npm-audit.json reports/pip-audit.json reports/npm-advisories.json","input_hash":"b3c4567728dd96c5d2e9b8a6a722f78c87b80ac3e4d2cc9d7f5d3a44a9148d11","input_redaction":"hash+command","mitre":{"tactic":"Execution","tactic_id":"TA0002","technique":"Unix Shell","technique_id":"T1059.004"},"name":"Bash","parameters_redacted":false,"qualified":"Bash","server":"claude"}}
@@ -0,0 +1,4 @@
1
+ {"action":{"outcome":"success","reason":"decision:allow;hook:PreToolUse","type":"tool_called"},"actor":{"id":"operator","role":"operator","type":"agent"},"agent":{"name":"claude","skill_id":""},"correlation_id":"b-terraform-apply","event_id":"8b7001f1-6c5b-51ab-92e7-8a72c8f6d001","fleet_id":"example-fleet","host_id":"BUILD-01","initiator":{"actor_type":"agent","operator_id":"operator","trigger":"manual"},"model":{"id":"claude","provider":"claude"},"schema_version":"1.1.0","session_id":"b-terraform-apply","source":{"adapter":"claude_hook","app":"claude","tier":"external"},"source_topic":"external/claude/tool_called","timestamp_utc":"2026-08-12T11:00:00+00:00","tool":{"arguments":{"command":"terraform init -backend-config=env/staging.backend"},"command":"terraform init -backend-config=env/staging.backend","input_hash":"4c6e8f1028dd96c5d2e9b8a6a722f78c87b80ac3e4d2cc9d7f5d3a44a9148d04","input_redaction":"hash+command","mitre":{"tactic":"Execution","tactic_id":"TA0002","technique":"Unix Shell","technique_id":"T1059.004"},"name":"Bash","parameters_redacted":false,"qualified":"Bash","server":"claude"}}
2
+ {"action":{"outcome":"success","reason":"decision:allow;hook:PreToolUse","type":"tool_called"},"actor":{"id":"operator","role":"operator","type":"agent"},"agent":{"name":"claude","skill_id":""},"correlation_id":"b-terraform-apply","event_id":"8b7001f1-6c5b-51ab-92e7-8a72c8f6d002","fleet_id":"example-fleet","host_id":"BUILD-01","initiator":{"actor_type":"agent","operator_id":"operator","trigger":"manual"},"model":{"id":"claude","provider":"claude"},"schema_version":"1.1.0","session_id":"b-terraform-apply","source":{"adapter":"claude_hook","app":"claude","tier":"external"},"source_topic":"external/claude/tool_called","timestamp_utc":"2026-08-12T11:02:00+00:00","tool":{"arguments":{"command":"terraform plan -var-file env/staging.tfvars -out tfplan"},"command":"terraform plan -var-file env/staging.tfvars -out tfplan","input_hash":"5d7f901128dd96c5d2e9b8a6a722f78c87b80ac3e4d2cc9d7f5d3a44a9148d05","input_redaction":"hash+command","mitre":{"tactic":"Execution","tactic_id":"TA0002","technique":"Unix Shell","technique_id":"T1059.004"},"name":"Bash","parameters_redacted":false,"qualified":"Bash","server":"claude"}}
3
+ {"action":{"outcome":"success","reason":"decision:allow;hook:PreToolUse","type":"tool_called"},"actor":{"id":"operator","role":"operator","type":"agent"},"agent":{"name":"claude","skill_id":""},"correlation_id":"b-terraform-apply","event_id":"8b7001f1-6c5b-51ab-92e7-8a72c8f6d003","fleet_id":"example-fleet","host_id":"BUILD-01","initiator":{"actor_type":"agent","operator_id":"operator","trigger":"manual"},"model":{"id":"claude","provider":"claude"},"schema_version":"1.1.0","session_id":"b-terraform-apply","source":{"adapter":"claude_hook","app":"claude","tier":"external"},"source_topic":"external/claude/tool_called","timestamp_utc":"2026-08-12T11:04:00+00:00","tool":{"arguments":{"command":"AWS_PROFILE=staging terraform apply -auto-approve tfplan"},"command":"AWS_PROFILE=staging terraform apply -auto-approve tfplan","input_hash":"6e80912228dd96c5d2e9b8a6a722f78c87b80ac3e4d2cc9d7f5d3a44a9148d06","input_redaction":"hash+command","mitre":{"tactic":"Execution","tactic_id":"TA0002","technique":"Unix Shell","technique_id":"T1059.004"},"name":"Bash","parameters_redacted":false,"qualified":"Bash","server":"claude"}}
4
+ {"action":{"outcome":"success","reason":"decision:allow;hook:PreToolUse","type":"tool_called"},"actor":{"id":"operator","role":"operator","type":"agent"},"agent":{"name":"claude","skill_id":""},"correlation_id":"b-terraform-apply","event_id":"8b7001f1-6c5b-51ab-92e7-8a72c8f6d004","fleet_id":"example-fleet","host_id":"BUILD-01","initiator":{"actor_type":"agent","operator_id":"operator","trigger":"manual"},"model":{"id":"claude","provider":"claude"},"schema_version":"1.1.0","session_id":"b-terraform-apply","source":{"adapter":"claude_hook","app":"claude","tier":"external"},"source_topic":"external/claude/tool_called","timestamp_utc":"2026-08-12T11:06:00+00:00","tool":{"arguments":{"command":"aws sts get-caller-identity --profile staging"},"command":"aws sts get-caller-identity --profile staging","input_hash":"7f90123328dd96c5d2e9b8a6a722f78c87b80ac3e4d2cc9d7f5d3a44a9148d07","input_redaction":"hash+command","mitre":{"tactic":"Execution","tactic_id":"TA0002","technique":"Unix Shell","technique_id":"T1059.004"},"name":"Bash","parameters_redacted":false,"qualified":"Bash","server":"claude","traits":["cloud_api"]}}
@@ -419,6 +419,34 @@ cases:
419
419
  Thirty read-only calls spread over half an hour. Burst rules measure a
420
420
  time window; a long session must not trip them by length alone.
421
421
 
422
+ - name: container registry publish
423
+ session: benign_container_registry_publish.jsonl
424
+ expect: []
425
+ benign: true
426
+ note: >
427
+ `docker login`, `docker build` and `docker push` against a registry. This
428
+ looks like authenticated egress, but no credential is read from disk or
429
+ the environment; the password arrives through stdin and never belongs in
430
+ the trail.
431
+
432
+ - name: terraform apply with a named profile
433
+ session: benign_terraform_apply_with_profile.jsonl
434
+ expect: []
435
+ benign: true
436
+ note: >
437
+ Infrastructure automation touches cloud state and later calls `aws sts`,
438
+ but it uses a named profile rather than reading credentials. Cloud API use
439
+ by itself is not lateral movement after credential harvest.
440
+
441
+ - name: dependency audit touches remote advisories
442
+ session: benign_dependency_audit_network_burst.jsonl
443
+ expect: []
444
+ benign: true
445
+ note: >
446
+ Package audits query registries and advisory feeds in quick succession,
447
+ then run a local summariser. A short network-heavy audit is not a session
448
+ tool burst, a download cradle or credential exfiltration.
449
+
422
450
  - name: human-driven deletes currently fire
423
451
  session: benign_human_driven_deletes.jsonl
424
452
  expect: [destructive-delete-burst]
@@ -21,13 +21,13 @@ from agentmetry.core.config import settings
21
21
  ExternalApp = Literal[
22
22
  "cursor", "claude", "antigravity", "codex", "mcp_proxy",
23
23
  "qwen", "kimi", "qoder", "codebuddy", "trae", "crewai", "opensre", "copilot", "zcode",
24
- "cursor_cloud", "claude_cloud", "grok_build", "agentmetry",
24
+ "cursor_cloud", "claude_cloud", "grok_build", "grok_bot", "agentmetry",
25
25
  ]
26
26
 
27
27
  KNOWN_EXTERNAL_APPS = frozenset({
28
28
  "cursor", "claude", "antigravity", "codex", "mcp_proxy",
29
29
  "qwen", "kimi", "qoder", "codebuddy", "trae", "crewai", "opensre", "copilot", "zcode",
30
- "cursor_cloud", "claude_cloud", "grok_build",
30
+ "cursor_cloud", "claude_cloud", "grok_build", "grok_bot",
31
31
  })
32
32
 
33
33
  _ACTION_MAP = {
@@ -38,8 +38,20 @@ _ACTION_MAP = {
38
38
  "tool_failed": ("tool_called", "error"),
39
39
  "approval_request": ("approval_request", "pending"),
40
40
  "approval_response": ("approval_response", "success"),
41
+ # A deployment tool (an Intune remediation, a LaunchAgent) reinstalled
42
+ # hooks. That changes this machine's configuration; it is not a tool call.
43
+ # Unmapped it was stored as `tool_called`, which tool-sequence rules count.
44
+ "hook_remediation": ("config_change", "success"),
41
45
  }
42
46
 
47
+ #: The only payload fields a remediation notice keeps. Anything session- or
48
+ #: tool-shaped a client attached is dropped: it would join the notice to an
49
+ #: agent's session, or give a detection a tool to read.
50
+ _REMEDIATION_FIELDS = frozenset({
51
+ "event_type", "source_app", "outcome", "reason", "timestamp_utc", "adapter",
52
+ "triggered_by", "initiator", "operator",
53
+ })
54
+
43
55
 
44
56
  def _utc_now() -> str:
45
57
  return datetime.now(timezone.utc).isoformat()
@@ -82,8 +94,14 @@ def build_external_canonical(payload: dict[str, Any]) -> dict[str, Any]:
82
94
  source_app, claimed_app = _resolve_source_app(payload.get("source_app"))
83
95
 
84
96
  event_type = str(payload.get("event_type") or "tool_called")
97
+ if event_type == "hook_remediation":
98
+ payload = {k: v for k, v in payload.items() if k in _REMEDIATION_FIELDS}
85
99
  action_type, default_outcome = _ACTION_MAP.get(event_type, ("tool_called", "success"))
86
100
  outcome = str(payload.get("outcome") or default_outcome)
101
+ if event_type == "hook_remediation" and outcome not in ("success", "error"):
102
+ # Anything else is not a confirmed success, and a fleet alerting on
103
+ # failed remediations must not have one hidden by a spelling.
104
+ outcome = "error"
87
105
  reason = str(payload.get("reason") or "")
88
106
 
89
107
  correlation_id = str(payload.get("correlation_id") or payload.get("thread_id") or "")