graph-agents-cli 0.3.1__py3-none-any.whl

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 (291) hide show
  1. graph_agents_cli/__init__.py +26 -0
  2. graph_agents_cli/_api_policy.py +2145 -0
  3. graph_agents_cli/_approvals.py +400 -0
  4. graph_agents_cli/_build.py +186 -0
  5. graph_agents_cli/_build_info.json +7 -0
  6. graph_agents_cli/_chat_client.py +462 -0
  7. graph_agents_cli/_click.py +157 -0
  8. graph_agents_cli/_defaults.py +139 -0
  9. graph_agents_cli/_experiments.py +64 -0
  10. graph_agents_cli/_http.py +192 -0
  11. graph_agents_cli/_output.py +83 -0
  12. graph_agents_cli/_project.py +462 -0
  13. graph_agents_cli/_remote.py +220 -0
  14. graph_agents_cli/_response_schema.py +264 -0
  15. graph_agents_cli/_runner.py +319 -0
  16. graph_agents_cli/_skills_check.py +274 -0
  17. graph_agents_cli/_tools.py +189 -0
  18. graph_agents_cli/_trust.py +66 -0
  19. graph_agents_cli/api/__init__.py +15 -0
  20. graph_agents_cli/api/_changes.py +506 -0
  21. graph_agents_cli/api/_files.py +658 -0
  22. graph_agents_cli/api/cmd_api.py +2480 -0
  23. graph_agents_cli/deploy/__init__.py +15 -0
  24. graph_agents_cli/deploy/_config.py +171 -0
  25. graph_agents_cli/deploy/_image.py +128 -0
  26. graph_agents_cli/deploy/_kube.py +286 -0
  27. graph_agents_cli/deploy/_modes.py +234 -0
  28. graph_agents_cli/deploy/_preflight.py +370 -0
  29. graph_agents_cli/deploy/_values.py +168 -0
  30. graph_agents_cli/deploy/cmd_deploy.py +1866 -0
  31. graph_agents_cli/deploy/gitops.py +562 -0
  32. graph_agents_cli/deploy/local_load.py +273 -0
  33. graph_agents_cli/dev/__init__.py +13 -0
  34. graph_agents_cli/dev/cmd_build.py +131 -0
  35. graph_agents_cli/dev/cmd_install.py +78 -0
  36. graph_agents_cli/dev/cmd_lint.py +119 -0
  37. graph_agents_cli/dev/cmd_playground.py +297 -0
  38. graph_agents_cli/dev/policy_check.py +1287 -0
  39. graph_agents_cli/eval/__init__.py +22 -0
  40. graph_agents_cli/eval/_client.py +670 -0
  41. graph_agents_cli/eval/_common.py +177 -0
  42. graph_agents_cli/eval/_judge.py +168 -0
  43. graph_agents_cli/eval/_judge_runner.py +238 -0
  44. graph_agents_cli/eval/_paths.py +212 -0
  45. graph_agents_cli/eval/checks.py +581 -0
  46. graph_agents_cli/eval/cmd_analyze.py +278 -0
  47. graph_agents_cli/eval/cmd_compare.py +284 -0
  48. graph_agents_cli/eval/cmd_eval_group.py +80 -0
  49. graph_agents_cli/eval/cmd_generate.py +558 -0
  50. graph_agents_cli/eval/cmd_grade.py +466 -0
  51. graph_agents_cli/eval/cmd_metric.py +156 -0
  52. graph_agents_cli/eval/cmd_run.py +370 -0
  53. graph_agents_cli/eval/cmd_submit.py +400 -0
  54. graph_agents_cli/eval/config.py +435 -0
  55. graph_agents_cli/eval/dataset.py +350 -0
  56. graph_agents_cli/eval/gate.py +420 -0
  57. graph_agents_cli/eval/transcript.py +192 -0
  58. graph_agents_cli/extension/__init__.py +13 -0
  59. graph_agents_cli/extension/_compat.py +86 -0
  60. graph_agents_cli/extension/_loader.py +293 -0
  61. graph_agents_cli/extension/_manifest.py +135 -0
  62. graph_agents_cli/extension/_overrides.py +195 -0
  63. graph_agents_cli/extension/_paths.py +91 -0
  64. graph_agents_cli/extension/_refs.py +193 -0
  65. graph_agents_cli/extension/_resolver.py +453 -0
  66. graph_agents_cli/extension/_schema.py +106 -0
  67. graph_agents_cli/extension/_spec.py +253 -0
  68. graph_agents_cli/extension/_sync.py +102 -0
  69. graph_agents_cli/extension/_trust.py +58 -0
  70. graph_agents_cli/extension/cmd_extension_add.py +259 -0
  71. graph_agents_cli/extension/cmd_extension_group.py +57 -0
  72. graph_agents_cli/extension/cmd_extension_list.py +56 -0
  73. graph_agents_cli/extension/cmd_extension_remove.py +61 -0
  74. graph_agents_cli/extension/cmd_extension_update.py +195 -0
  75. graph_agents_cli/info/__init__.py +13 -0
  76. graph_agents_cli/info/cmd_info.py +222 -0
  77. graph_agents_cli/infra/__init__.py +15 -0
  78. graph_agents_cli/infra/checks.py +1169 -0
  79. graph_agents_cli/infra/cmd_infra.py +103 -0
  80. graph_agents_cli/main.py +591 -0
  81. graph_agents_cli/peer/__init__.py +15 -0
  82. graph_agents_cli/peer/_generate.py +254 -0
  83. graph_agents_cli/peer/cmd_peer.py +1151 -0
  84. graph_agents_cli/run/__init__.py +13 -0
  85. graph_agents_cli/run/_local_server.py +1157 -0
  86. graph_agents_cli/run/_signals.py +141 -0
  87. graph_agents_cli/run/cmd_approvals.py +530 -0
  88. graph_agents_cli/run/cmd_run.py +1421 -0
  89. graph_agents_cli/scaffold/__init__.py +19 -0
  90. graph_agents_cli/scaffold/agents/README.md +24 -0
  91. graph_agents_cli/scaffold/agents/empty_py/.template/templateconfig.yaml +22 -0
  92. graph_agents_cli/scaffold/agents/langgraph/.env.example +292 -0
  93. graph_agents_cli/scaffold/agents/langgraph/.template/templateconfig.yaml +28 -0
  94. graph_agents_cli/scaffold/agents/langgraph/Dockerfile +59 -0
  95. graph_agents_cli/scaffold/agents/langgraph/Dockerfile.langgraph-server +59 -0
  96. graph_agents_cli/scaffold/agents/langgraph/README.md +571 -0
  97. graph_agents_cli/scaffold/agents/langgraph/api-policy.yaml +60 -0
  98. graph_agents_cli/scaffold/agents/langgraph/app/__init__.py +20 -0
  99. graph_agents_cli/scaffold/agents/langgraph/app/agent.py +174 -0
  100. graph_agents_cli/scaffold/agents/langgraph/app/app_utils/__init__.py +15 -0
  101. graph_agents_cli/scaffold/agents/langgraph/app/app_utils/a2a.py +2162 -0
  102. graph_agents_cli/scaffold/agents/langgraph/app/app_utils/a2a_client.py +1167 -0
  103. graph_agents_cli/scaffold/agents/langgraph/app/app_utils/api_client.py +4220 -0
  104. graph_agents_cli/scaffold/agents/langgraph/app/app_utils/approvals.py +1349 -0
  105. graph_agents_cli/scaffold/agents/langgraph/app/app_utils/auth.py +1986 -0
  106. graph_agents_cli/scaffold/agents/langgraph/app/app_utils/chat.py +2962 -0
  107. graph_agents_cli/scaffold/agents/langgraph/app/app_utils/checkpointer.py +432 -0
  108. graph_agents_cli/scaffold/agents/langgraph/app/app_utils/content.py +569 -0
  109. graph_agents_cli/scaffold/agents/langgraph/app/app_utils/db.py +580 -0
  110. graph_agents_cli/scaffold/agents/langgraph/app/app_utils/limits.py +203 -0
  111. graph_agents_cli/scaffold/agents/langgraph/app/app_utils/metrics.py +231 -0
  112. graph_agents_cli/scaffold/agents/langgraph/app/app_utils/middleware.py +361 -0
  113. graph_agents_cli/scaffold/agents/langgraph/app/app_utils/model.py +611 -0
  114. graph_agents_cli/scaffold/agents/langgraph/app/app_utils/playground.py +230 -0
  115. graph_agents_cli/scaffold/agents/langgraph/app/app_utils/run_locks.py +459 -0
  116. graph_agents_cli/scaffold/agents/langgraph/app/app_utils/structured.py +755 -0
  117. graph_agents_cli/scaffold/agents/langgraph/app/app_utils/telemetry.py +681 -0
  118. graph_agents_cli/scaffold/agents/langgraph/app/app_utils/threads.py +493 -0
  119. graph_agents_cli/scaffold/agents/langgraph/app/app_utils/token_exchange.py +959 -0
  120. graph_agents_cli/scaffold/agents/langgraph/app/fast_api_app.py +770 -0
  121. graph_agents_cli/scaffold/agents/langgraph/app/policies/__init__.py +55 -0
  122. graph_agents_cli/scaffold/agents/langgraph/app/policies/custom.py +97 -0
  123. graph_agents_cli/scaffold/agents/langgraph/app/tools/__init__.py +46 -0
  124. graph_agents_cli/scaffold/agents/langgraph/app/tools/example_api.py +92 -0
  125. graph_agents_cli/scaffold/agents/langgraph/app/tools/weather.py +33 -0
  126. graph_agents_cli/scaffold/agents/langgraph/langgraph.json +14 -0
  127. graph_agents_cli/scaffold/agents/langgraph/pyproject.toml +78 -0
  128. graph_agents_cli/scaffold/agents/langgraph/tests/conftest.py +376 -0
  129. graph_agents_cli/scaffold/agents/langgraph/tests/eval/datasets/basic-dataset.json +53 -0
  130. graph_agents_cli/scaffold/agents/langgraph/tests/eval/eval_config.yaml +32 -0
  131. graph_agents_cli/scaffold/agents/langgraph/tests/integration/approval_graph.py +137 -0
  132. graph_agents_cli/scaffold/agents/langgraph/tests/integration/fake_issuer.py +216 -0
  133. graph_agents_cli/scaffold/agents/langgraph/tests/integration/fake_openai.py +357 -0
  134. graph_agents_cli/scaffold/agents/langgraph/tests/integration/test_a2a_outcomes.py +569 -0
  135. graph_agents_cli/scaffold/agents/langgraph/tests/integration/test_a2a_relay.py +479 -0
  136. graph_agents_cli/scaffold/agents/langgraph/tests/integration/test_api_surface.py +812 -0
  137. graph_agents_cli/scaffold/agents/langgraph/tests/integration/test_approvals.py +1367 -0
  138. graph_agents_cli/scaffold/agents/langgraph/tests/integration/test_approvals_server.py +794 -0
  139. graph_agents_cli/scaffold/agents/langgraph/tests/integration/test_cross_actor.py +497 -0
  140. graph_agents_cli/scaffold/agents/langgraph/tests/integration/test_cross_actor_server.py +247 -0
  141. graph_agents_cli/scaffold/agents/langgraph/tests/integration/test_history_repair.py +278 -0
  142. graph_agents_cli/scaffold/agents/langgraph/tests/integration/test_model_apis.py +242 -0
  143. graph_agents_cli/scaffold/agents/langgraph/tests/integration/test_postgres.py +637 -0
  144. graph_agents_cli/scaffold/agents/langgraph/tests/integration/test_resilience_postgres.py +770 -0
  145. graph_agents_cli/scaffold/agents/langgraph/tests/integration/test_runtime_guardrails.py +854 -0
  146. graph_agents_cli/scaffold/agents/langgraph/tests/integration/test_server_e2e.py +340 -0
  147. graph_agents_cli/scaffold/agents/langgraph/tests/integration/test_server_runtime.py +989 -0
  148. graph_agents_cli/scaffold/agents/langgraph/tests/integration/test_structured_answers.py +584 -0
  149. graph_agents_cli/scaffold/agents/langgraph/tests/integration/test_structured_server.py +222 -0
  150. graph_agents_cli/scaffold/agents/langgraph/tests/integration/test_token_exchange_issuer.py +650 -0
  151. graph_agents_cli/scaffold/agents/langgraph/tests/load_test/.results/.placeholder +0 -0
  152. graph_agents_cli/scaffold/agents/langgraph/tests/load_test/README.md +22 -0
  153. graph_agents_cli/scaffold/agents/langgraph/tests/load_test/conftest.py +21 -0
  154. graph_agents_cli/scaffold/agents/langgraph/tests/load_test/load_test.py +81 -0
  155. graph_agents_cli/scaffold/agents/langgraph/tests/unit/test_a2a_client.py +824 -0
  156. graph_agents_cli/scaffold/agents/langgraph/tests/unit/test_a2a_scoping.py +724 -0
  157. graph_agents_cli/scaffold/agents/langgraph/tests/unit/test_api_client.py +1214 -0
  158. graph_agents_cli/scaffold/agents/langgraph/tests/unit/test_api_client_hardening.py +716 -0
  159. graph_agents_cli/scaffold/agents/langgraph/tests/unit/test_api_policy_rpc.py +767 -0
  160. graph_agents_cli/scaffold/agents/langgraph/tests/unit/test_approval_ledger.py +1536 -0
  161. graph_agents_cli/scaffold/agents/langgraph/tests/unit/test_fake_model.py +115 -0
  162. graph_agents_cli/scaffold/agents/langgraph/tests/unit/test_jwt_policy.py +991 -0
  163. graph_agents_cli/scaffold/agents/langgraph/tests/unit/test_limits.py +310 -0
  164. graph_agents_cli/scaffold/agents/langgraph/tests/unit/test_logging.py +148 -0
  165. graph_agents_cli/scaffold/agents/langgraph/tests/unit/test_logging_hardening.py +271 -0
  166. graph_agents_cli/scaffold/agents/langgraph/tests/unit/test_policy.py +378 -0
  167. graph_agents_cli/scaffold/agents/langgraph/tests/unit/test_resilience.py +610 -0
  168. graph_agents_cli/scaffold/agents/langgraph/tests/unit/test_server_auth.py +702 -0
  169. graph_agents_cli/scaffold/agents/langgraph/tests/unit/test_structured.py +673 -0
  170. graph_agents_cli/scaffold/agents/langgraph/tests/unit/test_telemetry.py +404 -0
  171. graph_agents_cli/scaffold/agents/langgraph/tests/unit/test_thread_listing.py +255 -0
  172. graph_agents_cli/scaffold/agents/langgraph/tests/unit/test_threads.py +268 -0
  173. graph_agents_cli/scaffold/agents/langgraph/tests/unit/test_token_exchange.py +1320 -0
  174. graph_agents_cli/scaffold/agents/langgraph/tests/unit/test_untrusted_content.py +393 -0
  175. graph_agents_cli/scaffold/agents/langgraph/uv-fastapi.lock +2084 -0
  176. graph_agents_cli/scaffold/agents/langgraph/uv-langgraph-server.lock +2106 -0
  177. graph_agents_cli/scaffold/agents/langgraph/{{cookiecutter.agent_guidance_filename}} +129 -0
  178. graph_agents_cli/scaffold/base_templates/_shared/graph-agents-cli-manifest.yaml +36 -0
  179. graph_agents_cli/scaffold/base_templates/python/.dockerignore +32 -0
  180. graph_agents_cli/scaffold/base_templates/python/.github/CODEOWNERS +30 -0
  181. graph_agents_cli/scaffold/base_templates/python/.github/agent.env +7 -0
  182. graph_agents_cli/scaffold/base_templates/python/.github/workflows/pr_checks.yaml +214 -0
  183. graph_agents_cli/scaffold/base_templates/python/.gitignore +209 -0
  184. graph_agents_cli/scaffold/base_templates/python/tests/unit/test_dummy.py +23 -0
  185. graph_agents_cli/scaffold/base_templates/python/{{cookiecutter.agent_guidance_filename}} +35 -0
  186. graph_agents_cli/scaffold/cmd_scaffold_group.py +49 -0
  187. graph_agents_cli/scaffold/commands/__init__.py +13 -0
  188. graph_agents_cli/scaffold/commands/create.py +1424 -0
  189. graph_agents_cli/scaffold/commands/enhance.py +1652 -0
  190. graph_agents_cli/scaffold/commands/upgrade.py +570 -0
  191. graph_agents_cli/scaffold/deployment_targets/kubernetes/python/.github/agent.env +12 -0
  192. graph_agents_cli/scaffold/deployment_targets/kubernetes/python/.github/workflows/promote-to-prod.yaml +371 -0
  193. graph_agents_cli/scaffold/deployment_targets/kubernetes/python/.github/workflows/staging.yaml +450 -0
  194. graph_agents_cli/scaffold/deployment_targets/kubernetes/python/deployment/argocd/application-dev.yaml +43 -0
  195. graph_agents_cli/scaffold/deployment_targets/kubernetes/python/deployment/argocd/application-prod.yaml +41 -0
  196. graph_agents_cli/scaffold/deployment_targets/kubernetes/python/deployment/argocd/application-staging.yaml +43 -0
  197. graph_agents_cli/scaffold/deployment_targets/kubernetes/python/deployment/helm/{{cookiecutter.project_name}}/.helmignore +14 -0
  198. graph_agents_cli/scaffold/deployment_targets/kubernetes/python/deployment/helm/{{cookiecutter.project_name}}/Chart.yaml +21 -0
  199. graph_agents_cli/scaffold/deployment_targets/kubernetes/python/deployment/helm/{{cookiecutter.project_name}}/examples/networkpolicy.yaml +103 -0
  200. graph_agents_cli/scaffold/deployment_targets/kubernetes/python/deployment/helm/{{cookiecutter.project_name}}/templates/NOTES.txt +48 -0
  201. graph_agents_cli/scaffold/deployment_targets/kubernetes/python/deployment/helm/{{cookiecutter.project_name}}/templates/_helpers.tpl +189 -0
  202. graph_agents_cli/scaffold/deployment_targets/kubernetes/python/deployment/helm/{{cookiecutter.project_name}}/templates/certificate.yaml +15 -0
  203. graph_agents_cli/scaffold/deployment_targets/kubernetes/python/deployment/helm/{{cookiecutter.project_name}}/templates/configmap.yaml +10 -0
  204. graph_agents_cli/scaffold/deployment_targets/kubernetes/python/deployment/helm/{{cookiecutter.project_name}}/templates/deployment.yaml +199 -0
  205. graph_agents_cli/scaffold/deployment_targets/kubernetes/python/deployment/helm/{{cookiecutter.project_name}}/templates/hpa.yaml +22 -0
  206. graph_agents_cli/scaffold/deployment_targets/kubernetes/python/deployment/helm/{{cookiecutter.project_name}}/templates/httproute.yaml +30 -0
  207. graph_agents_cli/scaffold/deployment_targets/kubernetes/python/deployment/helm/{{cookiecutter.project_name}}/templates/ingress.yaml +39 -0
  208. graph_agents_cli/scaffold/deployment_targets/kubernetes/python/deployment/helm/{{cookiecutter.project_name}}/templates/networkpolicy.yaml +48 -0
  209. graph_agents_cli/scaffold/deployment_targets/kubernetes/python/deployment/helm/{{cookiecutter.project_name}}/templates/pdb.yaml +13 -0
  210. graph_agents_cli/scaffold/deployment_targets/kubernetes/python/deployment/helm/{{cookiecutter.project_name}}/templates/postgresql-secret.yaml +37 -0
  211. graph_agents_cli/scaffold/deployment_targets/kubernetes/python/deployment/helm/{{cookiecutter.project_name}}/templates/service.yaml +15 -0
  212. graph_agents_cli/scaffold/deployment_targets/kubernetes/python/deployment/helm/{{cookiecutter.project_name}}/templates/serviceaccount.yaml +13 -0
  213. graph_agents_cli/scaffold/deployment_targets/kubernetes/python/deployment/helm/{{cookiecutter.project_name}}/templates/servicemonitor.yaml +42 -0
  214. graph_agents_cli/scaffold/deployment_targets/kubernetes/python/deployment/helm/{{cookiecutter.project_name}}/values-dev.yaml +22 -0
  215. graph_agents_cli/scaffold/deployment_targets/kubernetes/python/deployment/helm/{{cookiecutter.project_name}}/values-prod.yaml +45 -0
  216. graph_agents_cli/scaffold/deployment_targets/kubernetes/python/deployment/helm/{{cookiecutter.project_name}}/values-staging.yaml +29 -0
  217. graph_agents_cli/scaffold/deployment_targets/kubernetes/python/deployment/helm/{{cookiecutter.project_name}}/values.yaml +396 -0
  218. graph_agents_cli/scaffold/deployment_targets/kubernetes/python/tests/integration/test_chart.py +269 -0
  219. graph_agents_cli/scaffold/deployment_targets/none/README.md +5 -0
  220. graph_agents_cli/scaffold/deployment_targets/none/python/README.md +6 -0
  221. graph_agents_cli/scaffold/utils/__init__.py +13 -0
  222. graph_agents_cli/scaffold/utils/backup.py +212 -0
  223. graph_agents_cli/scaffold/utils/build_record.py +257 -0
  224. graph_agents_cli/scaffold/utils/cli_options.py +184 -0
  225. graph_agents_cli/scaffold/utils/fs.py +83 -0
  226. graph_agents_cli/scaffold/utils/generate_locks.py +214 -0
  227. graph_agents_cli/scaffold/utils/generation_metadata.py +88 -0
  228. graph_agents_cli/scaffold/utils/keyedit.py +768 -0
  229. graph_agents_cli/scaffold/utils/keymerge.py +537 -0
  230. graph_agents_cli/scaffold/utils/language.py +138 -0
  231. graph_agents_cli/scaffold/utils/lock_utils.py +94 -0
  232. graph_agents_cli/scaffold/utils/logging.py +77 -0
  233. graph_agents_cli/scaffold/utils/manifest.py +292 -0
  234. graph_agents_cli/scaffold/utils/merge.py +970 -0
  235. graph_agents_cli/scaffold/utils/merge3.py +216 -0
  236. graph_agents_cli/scaffold/utils/openapi_seed.py +199 -0
  237. graph_agents_cli/scaffold/utils/remote_template.py +376 -0
  238. graph_agents_cli/scaffold/utils/template.py +1352 -0
  239. graph_agents_cli/scaffold/utils/upgrade.py +894 -0
  240. graph_agents_cli/scaffold/utils/version.py +438 -0
  241. graph_agents_cli/secrets/__init__.py +15 -0
  242. graph_agents_cli/secrets/_apply.py +954 -0
  243. graph_agents_cli/secrets/_required.py +188 -0
  244. graph_agents_cli/secrets/cmd_secrets.py +211 -0
  245. graph_agents_cli/setup/__init__.py +13 -0
  246. graph_agents_cli/setup/_antigravity.py +221 -0
  247. graph_agents_cli/setup/cmd_auth.py +1030 -0
  248. graph_agents_cli/setup/cmd_dev_token.py +513 -0
  249. graph_agents_cli/setup/cmd_setup.py +428 -0
  250. graph_agents_cli/setup/cmd_update.py +140 -0
  251. graph_agents_cli/skills/__init__.py +13 -0
  252. graph_agents_cli/skills/_bundle.py +65 -0
  253. graph_agents_cli/skills/data/README.md +19 -0
  254. graph_agents_cli/skills/data/graph-agents-cli-deploy/SKILL.md +357 -0
  255. graph_agents_cli/skills/data/graph-agents-cli-deploy/references/github-settings.md +113 -0
  256. graph_agents_cli/skills/data/graph-agents-cli-deploy/references/gitops.md +137 -0
  257. graph_agents_cli/skills/data/graph-agents-cli-deploy/references/kubernetes.md +315 -0
  258. graph_agents_cli/skills/data/graph-agents-cli-deploy/references/secrets.md +160 -0
  259. graph_agents_cli/skills/data/graph-agents-cli-eval/SKILL.md +303 -0
  260. graph_agents_cli/skills/data/graph-agents-cli-eval/references/dataset_schema.md +282 -0
  261. graph_agents_cli/skills/data/graph-agents-cli-eval/references/metrics-guide.md +143 -0
  262. graph_agents_cli/skills/data/graph-agents-cli-langgraph-code/SKILL.md +659 -0
  263. graph_agents_cli/skills/data/graph-agents-cli-langgraph-code/references/langchain-models.md +124 -0
  264. graph_agents_cli/skills/data/graph-agents-cli-langgraph-code/references/langgraph.md +235 -0
  265. graph_agents_cli/skills/data/graph-agents-cli-langgraph-code/references/template-contract.md +477 -0
  266. graph_agents_cli/skills/data/graph-agents-cli-observability/SKILL.md +231 -0
  267. graph_agents_cli/skills/data/graph-agents-cli-observability/references/langsmith.md +46 -0
  268. graph_agents_cli/skills/data/graph-agents-cli-observability/references/otel.md +59 -0
  269. graph_agents_cli/skills/data/graph-agents-cli-scaffold/SKILL.md +414 -0
  270. graph_agents_cli/skills/data/graph-agents-cli-scaffold/references/flags.md +134 -0
  271. graph_agents_cli/skills/data/graph-agents-cli-workflow/SKILL.md +478 -0
  272. graph_agents_cli/skills/data/graph-agents-cli-workflow/references/brainstorming.md +118 -0
  273. graph_agents_cli/skills/data/graph-agents-cli-workflow/references/commands.md +419 -0
  274. graph_agents_cli/skills/data/graph-agents-cli-workflow/references/extension.md +156 -0
  275. graph_agents_cli/skills/data/graph-agents-cli-workflow/references/internals.md +67 -0
  276. graph_agents_cli/skills/data/graph-agents-cli-workflow/references/spec-template.md +56 -0
  277. graph_agents_cli/skills/data/graph-agents-cli-workflow/references/terminology.md +119 -0
  278. graph_agents_cli/system/__init__.py +15 -0
  279. graph_agents_cli/system/_apply.py +519 -0
  280. graph_agents_cli/system/_checks.py +1023 -0
  281. graph_agents_cli/system/_deploy.py +215 -0
  282. graph_agents_cli/system/_model.py +363 -0
  283. graph_agents_cli/system/_system.py +664 -0
  284. graph_agents_cli/system/_views.py +208 -0
  285. graph_agents_cli/system/cmd_system.py +423 -0
  286. graph_agents_cli-0.3.1.dist-info/METADATA +162 -0
  287. graph_agents_cli-0.3.1.dist-info/RECORD +291 -0
  288. graph_agents_cli-0.3.1.dist-info/WHEEL +4 -0
  289. graph_agents_cli-0.3.1.dist-info/entry_points.txt +2 -0
  290. graph_agents_cli-0.3.1.dist-info/licenses/LICENSE +201 -0
  291. graph_agents_cli-0.3.1.dist-info/licenses/NOTICE +19 -0
@@ -0,0 +1,493 @@
1
+ # Copyright 2026 graph-agents-cli contributors
2
+ #
3
+ # Licensed under the Apache License, Version 2.0 (the "License");
4
+ # you may not use this file except in compliance with the License.
5
+ # You may obtain a copy of the License at
6
+ #
7
+ # https://www.apache.org/licenses/LICENSE-2.0
8
+ #
9
+ # Unless required by applicable law or agreed to in writing, software
10
+ # distributed under the License is distributed on an "AS IS" BASIS,
11
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
+ # See the License for the specific language governing permissions and
13
+ # limitations under the License.
14
+
15
+ """App-owned thread ownership (one principal per thread) and per-thread run locks.
16
+
17
+ Under `fastapi` the `threads` table (`thread_id`, `principal_id`, `tenant`,
18
+ `actor`, `created_at`, `updated_at`) lives beside the library-owned checkpointer
19
+ schema; the ownership check runs before any checkpointer access, so a thread
20
+ id alone cannot cross a principal boundary. Claiming a new thread is one
21
+ atomic insert-if-absent, so two principals racing for the same new id cannot
22
+ both win. Under `langgraph-server` the same rule is applied by `chat.py` to
23
+ the thread metadata `{principal_id, tenant}` the app writes at creation (the
24
+ server's own `@auth.on` filters in `auth.py` protect the native Threads/Runs
25
+ API called from outside; the app's loopback SDK calls bypass them).
26
+
27
+ Only the owner may continue (write to) or delete a thread. A thread an agent
28
+ started for a user (a delegated principal, `Principal.actor`) records that
29
+ actor: the user (a direct principal with the same subject) owns it as well,
30
+ and may read, continue and delete it; the agent reaches only the threads
31
+ started under its own subject and actor, never another agent's for the same
32
+ user. A thread without an actor (a direct one, or one created before 0.3) is
33
+ never an agent's. Roles listed in
34
+ `AUTH_READ_ACROSS_ROLES` may additionally *read* other principals' threads
35
+ (direct principals only: a delegated principal's roles never read across);
36
+ listing lists the caller's own threads unless the caller asks for every
37
+ principal's (`GET /threads?scope=all`, read-across roles only). Listed rows
38
+ carry the owner as the hashed principal id (`owner`), never the raw id.
39
+
40
+ Thread ids are one namespace shared by every caller. When a request names no
41
+ thread, the server generates a random id (a UUID4). A client that picks its
42
+ own ids must make them unguessable: an id another principal sent first is
43
+ theirs (403), so a predictable id can be claimed ahead of its intended user,
44
+ and a 403 tells a caller that the id exists.
45
+
46
+ Deleting a thread (`ThreadStore.delete`, which the owner's DELETE and the
47
+ retention purge both reach) tells the listeners in `DELETE_LISTENERS` (the
48
+ A2A task store drops that conversation's tasks). An approval's outcome (a
49
+ decision, over A2A or HTTP, and the run it resumed; an expiry) is told to the
50
+ listeners in `A2A_TASK_LISTENERS` (`approval_outcome`): the A2A tasks still
51
+ waiting on that approval follow it.
52
+
53
+ One run per thread (`ThreadLocks`, `ThreadBusy`: HTTP 409 `{"code":
54
+ "thread_busy"}`) lives in `run_locks.py`: in-process locks plus Postgres
55
+ leases that every replica honours; the names are re-exported here.
56
+ """
57
+
58
+ from __future__ import annotations
59
+
60
+ import logging
61
+ import os
62
+ from collections.abc import Awaitable, Callable, Mapping
63
+ from dataclasses import dataclass, field
64
+ from datetime import datetime
65
+ from typing import Any
66
+
67
+ from fastapi import HTTPException
68
+
69
+ from {{cookiecutter.agent_directory}}.app_utils.auth import Principal
70
+ from {{cookiecutter.agent_directory}}.app_utils.db import Database, utcnow_iso
71
+ from {{cookiecutter.agent_directory}}.app_utils.run_locks import (
72
+ THREAD_BUSY,
73
+ LeaseLost,
74
+ ThreadBusy,
75
+ ThreadLease,
76
+ ThreadLocks,
77
+ )
78
+
79
+ __all__ = [
80
+ "A2A_TASK_LISTENERS",
81
+ "DELETE_LISTENERS",
82
+ "OUTCOME_COMPLETED",
83
+ "OUTCOME_FAILED",
84
+ "OUTCOME_INPUT_REQUIRED",
85
+ "SCOPES",
86
+ "SCOPE_ALL",
87
+ "SCOPE_OWN",
88
+ "THREAD_BUSY",
89
+ "ApprovalOutcome",
90
+ "LeaseLost",
91
+ "ThreadBusy",
92
+ "ThreadLease",
93
+ "ThreadLocks",
94
+ "ThreadRecord",
95
+ "ThreadStore",
96
+ "approval_outcome",
97
+ "check_scope",
98
+ "is_owner",
99
+ "own_view",
100
+ "owner_hash",
101
+ "owner_metadata_filter",
102
+ "search_server_threads",
103
+ "thread_deleted",
104
+ ]
105
+
106
+ logger = logging.getLogger(__name__)
107
+
108
+ # `GET /threads?scope=`: the caller's threads (default), or every principal's
109
+ # (read-across roles only).
110
+ SCOPE_OWN = "own"
111
+ SCOPE_ALL = "all"
112
+ SCOPES = (SCOPE_OWN, SCOPE_ALL)
113
+
114
+ # Called with a thread id after the thread's row is deleted (best effort: a
115
+ # failing listener is logged and never fails the delete).
116
+ DELETE_LISTENERS: list[Callable[[str], Awaitable[None]]] = []
117
+
118
+
119
+ # The states an A2A task waiting on an approval takes when the approval's outcome is known.
120
+ OUTCOME_COMPLETED = "completed"
121
+ OUTCOME_FAILED = "failed"
122
+ OUTCOME_INPUT_REQUIRED = "input-required"
123
+
124
+
125
+ @dataclass(frozen=True)
126
+ class ApprovalOutcome:
127
+ """What became of an approval: decided (and the run it resumed ended so), or expired.
128
+
129
+ The A2A tasks on `thread_id` that still wait on it (`input-required`, their
130
+ approval request lists `approval_id` or the decision named them in
131
+ `references`) and belong to its requester (`requester_hash`, the hashed
132
+ subject, and `requester_actor`, "" for a direct one) take `state`
133
+ (`completed`, `failed` or `input-required` with the new pending
134
+ `approvals`) and say `text`. `continued_in` is the A2A task that carried
135
+ the decision (never itself updated). `answer` is the structured answer of
136
+ a completed run (a project with a response schema), which they take whole
137
+ as a `response` artifact.
138
+ """
139
+
140
+ thread_id: str
141
+ approval_id: str
142
+ requester_hash: str
143
+ requester_actor: str
144
+ state: str
145
+ text: str
146
+ approvals: tuple[dict[str, Any], ...] = ()
147
+ references: tuple[str, ...] = ()
148
+ continued_in: str | None = None
149
+ answer: Any = None
150
+
151
+
152
+ # Called with an `ApprovalOutcome` (best effort: a failing listener is logged and
153
+ # never fails the decision, the run or the sweep).
154
+ A2A_TASK_LISTENERS: list[Callable[[ApprovalOutcome], Awaitable[None]]] = []
155
+
156
+
157
+ @dataclass
158
+ class ThreadRecord:
159
+ thread_id: str
160
+ principal_id: str
161
+ tenant: str | None = None
162
+ created_at: str = field(default_factory=utcnow_iso)
163
+ updated_at: str | None = None
164
+ # The agent that started the thread for `principal_id` (a delegated principal's
165
+ # actor); "" for a direct thread.
166
+ actor: str = ""
167
+
168
+ def __post_init__(self) -> None:
169
+ if self.updated_at is None:
170
+ self.updated_at = self.created_at
171
+
172
+ def public(self) -> dict[str, Any]:
173
+ """What `GET /threads` returns; the owner hashed (the raw id may be an email address)."""
174
+ return {
175
+ "thread_id": self.thread_id,
176
+ "owner": owner_hash(self.principal_id),
177
+ "created_at": self.created_at,
178
+ "updated_at": self.updated_at,
179
+ }
180
+
181
+
182
+ def owner_hash(principal_id: str | None) -> str | None:
183
+ """A thread owner's id as logs and listings show it (`Principal.hashed_id()`)."""
184
+ return Principal(id=principal_id).hashed_id() if principal_id else None
185
+
186
+
187
+ def read_across_roles() -> set[str]:
188
+ """Roles allowed to read other principals' threads (`AUTH_READ_ACROSS_ROLES`)."""
189
+ raw = os.environ.get("AUTH_READ_ACROSS_ROLES", "")
190
+ return {r.strip() for r in raw.split(",") if r.strip()}
191
+
192
+
193
+ def reads_across(principal: Principal) -> bool:
194
+ """A direct principal holding a read-across role (a delegated one never reads across)."""
195
+ return not principal.delegated and bool(set(principal.roles) & read_across_roles())
196
+
197
+
198
+ def is_owner(principal: Principal, record: ThreadRecord) -> bool:
199
+ """The thread's subject, directly; or the agent that started it, for that subject.
200
+
201
+ A direct principal owns every thread of its subject, those its agents
202
+ started included. A delegated one owns only the threads started under its
203
+ exact subject and actor.
204
+ """
205
+ if record.principal_id != principal.id:
206
+ return False
207
+ return principal.actor is None or (record.actor or "") == principal.actor.id
208
+
209
+
210
+ def can_access(principal: Principal, record: ThreadRecord) -> bool:
211
+ """Owner, or a principal holding one of the read-across roles."""
212
+ return is_owner(principal, record) or reads_across(principal)
213
+
214
+
215
+ def assert_access(principal: Principal, record: ThreadRecord) -> None:
216
+ """Read access: the owner or a read-across role."""
217
+ if not can_access(principal, record):
218
+ raise HTTPException(status_code=403, detail="This thread belongs to another principal.")
219
+
220
+
221
+ def assert_owner(principal: Principal, record: ThreadRecord) -> None:
222
+ """Write access (continue, delete): the owner only; read-across roles are read-only."""
223
+ if not is_owner(principal, record):
224
+ raise HTTPException(status_code=403, detail="This thread belongs to another principal.")
225
+
226
+
227
+ def check_scope(principal: Principal, scope: str) -> str:
228
+ """`scope` of a thread listing, or 403 for `all` without a read-across role (422 if unknown)."""
229
+ if scope not in SCOPES:
230
+ raise HTTPException(status_code=422, detail="scope must be 'own' or 'all'.")
231
+ if scope == SCOPE_ALL and not reads_across(principal):
232
+ raise HTTPException(
233
+ status_code=403,
234
+ detail="Listing every principal's threads needs a role in AUTH_READ_ACROSS_ROLES.",
235
+ )
236
+ return scope
237
+
238
+
239
+ def own_view(principal: Principal) -> Principal:
240
+ """`principal` without its read-across roles: what it lists as its own threads."""
241
+ across = read_across_roles()
242
+ return Principal(
243
+ id=principal.id,
244
+ roles=[r for r in principal.roles if r not in across],
245
+ permissions=set(principal.permissions),
246
+ attributes=principal.public_attributes(),
247
+ actor=principal.actor,
248
+ )
249
+
250
+
251
+ def _iso(value: Any) -> str | None:
252
+ if value is None:
253
+ return None
254
+ return value.isoformat() if isinstance(value, datetime) else str(value)
255
+
256
+
257
+ def _record_from_row(row: dict[str, Any]) -> ThreadRecord:
258
+ return ThreadRecord(
259
+ thread_id=row["thread_id"],
260
+ principal_id=row["principal_id"],
261
+ tenant=row.get("tenant"),
262
+ created_at=_iso(row.get("created_at")) or utcnow_iso(),
263
+ updated_at=_iso(row.get("updated_at")),
264
+ actor=row.get("actor") or "",
265
+ )
266
+
267
+
268
+ class ThreadStore:
269
+ """`threads` table under postgres, an in-process dict under memory."""
270
+
271
+ def __init__(self, db: Database) -> None:
272
+ self.db = db
273
+ self._memory: dict[str, ThreadRecord] = {}
274
+
275
+ async def get(self, thread_id: str) -> ThreadRecord | None:
276
+ if not self.db.is_postgres:
277
+ return self._memory.get(thread_id)
278
+ row = await self.db.fetchone("SELECT * FROM threads WHERE thread_id = %s", (thread_id,))
279
+ return _record_from_row(row) if row is not None else None
280
+
281
+ async def claim(self, thread_id: str, principal: Principal) -> tuple[ThreadRecord, bool]:
282
+ """The thread's record, created for `principal` if absent; `True` when this call created it.
283
+
284
+ One atomic insert-if-absent: when two principals claim the same new
285
+ id at once, exactly one gets `created=True` and the other reads the
286
+ winner's record (and is refused by the ownership check).
287
+ """
288
+ record = ThreadRecord(
289
+ thread_id=thread_id,
290
+ principal_id=principal.id,
291
+ tenant=principal.public_attributes().get("tenant"),
292
+ actor=principal.actor.id if principal.actor is not None else "",
293
+ )
294
+ if not self.db.is_postgres:
295
+ existing = self._memory.setdefault(thread_id, record)
296
+ return existing, existing is record
297
+ for _ in range(3):
298
+ row = await self.db.fetchone(
299
+ """
300
+ INSERT INTO threads (thread_id, principal_id, tenant, actor, created_at,
301
+ updated_at)
302
+ VALUES (%s, %s, %s, %s, %s, %s) ON CONFLICT (thread_id) DO NOTHING
303
+ RETURNING *
304
+ """,
305
+ (
306
+ record.thread_id,
307
+ record.principal_id,
308
+ record.tenant,
309
+ record.actor,
310
+ record.created_at,
311
+ record.created_at,
312
+ ),
313
+ )
314
+ if row is not None:
315
+ return _record_from_row(row), True
316
+ existing = await self.get(thread_id)
317
+ if existing is not None:
318
+ return existing, False
319
+ # Deleted between the conflict and the read: claim it again.
320
+ raise RuntimeError(f"could not claim thread {thread_id!r}")
321
+
322
+ async def create(self, thread_id: str, principal: Principal) -> ThreadRecord:
323
+ record, _created = await self.claim(thread_id, principal)
324
+ return record
325
+
326
+ async def ensure(
327
+ self,
328
+ thread_id: str,
329
+ principal: Principal,
330
+ *,
331
+ has_state: Callable[[str], Awaitable[bool]] | None = None,
332
+ ) -> ThreadRecord:
333
+ """Return the thread, creating it for `principal` when it does not exist.
334
+
335
+ This is the write path (`chat.send`): raises 403 when the thread exists
336
+ and belongs to another principal, even for a read-across role. A
337
+ continued thread's `updated_at` moves forward (retention counts idle time).
338
+
339
+ `has_state(thread_id)` tells whether the checkpointer holds state for
340
+ the id. An id with state but no owner row is never claimed (403): its
341
+ state belongs to whoever had it, not to the next caller. Rows are
342
+ removed only after their state, under the run lock, so such an id
343
+ should never exist; this is the guard if one does.
344
+ """
345
+ record = await self.get(thread_id)
346
+ if record is None:
347
+ if has_state is not None and await has_state(thread_id):
348
+ logger.warning(
349
+ "thread has state but no owner; refusing to give it a new owner",
350
+ extra={"thread_id": thread_id},
351
+ )
352
+ raise HTTPException(
353
+ status_code=403, detail="This thread belongs to another principal."
354
+ )
355
+ record, created = await self.claim(thread_id, principal)
356
+ if created:
357
+ return record
358
+ assert_owner(principal, record)
359
+ await self.touch(thread_id)
360
+ return record
361
+
362
+ async def touch(self, thread_id: str) -> None:
363
+ now = utcnow_iso()
364
+ if not self.db.is_postgres:
365
+ if thread_id in self._memory:
366
+ self._memory[thread_id].updated_at = now
367
+ return
368
+ await self.db.execute(
369
+ "UPDATE threads SET updated_at = %s WHERE thread_id = %s", (now, thread_id)
370
+ )
371
+
372
+ async def list_for(
373
+ self, principal: Principal, *, limit: int = 1000, offset: int = 0, scope: str | None = None
374
+ ) -> list[ThreadRecord]:
375
+ """Threads `principal` may read, most recent first.
376
+
377
+ `scope="own"`: its own threads only (a read-across role included);
378
+ `scope="all"`, or no scope: every principal's for a read-across role,
379
+ its own for anyone else. (`GET /threads` checks the scope and passes
380
+ the caller without its read-across roles for its own threads.) A
381
+ direct principal's own threads are every thread of its subject; a
382
+ delegated one's only those of its subject and actor.
383
+ """
384
+ if scope == SCOPE_OWN:
385
+ principal = own_view(principal)
386
+ if not self.db.is_postgres:
387
+ records = sorted(
388
+ (r for r in self._memory.values() if can_access(principal, r)),
389
+ key=lambda r: (r.updated_at or "", r.thread_id),
390
+ reverse=True,
391
+ )
392
+ return records[offset : offset + limit]
393
+ if reads_across(principal):
394
+ rows = await self.db.fetchall(
395
+ "SELECT * FROM threads ORDER BY updated_at DESC, thread_id DESC LIMIT %s OFFSET %s",
396
+ (limit, offset),
397
+ )
398
+ elif principal.actor is not None:
399
+ rows = await self.db.fetchall(
400
+ "SELECT * FROM threads WHERE principal_id = %s AND actor = %s "
401
+ "ORDER BY updated_at DESC, thread_id DESC LIMIT %s OFFSET %s",
402
+ (principal.id, principal.actor.id, limit, offset),
403
+ )
404
+ else:
405
+ rows = await self.db.fetchall(
406
+ "SELECT * FROM threads WHERE principal_id = %s "
407
+ "ORDER BY updated_at DESC, thread_id DESC LIMIT %s OFFSET %s",
408
+ (principal.id, limit, offset),
409
+ )
410
+ return [_record_from_row(r) for r in rows]
411
+
412
+ async def idle_before(self, cutoff_iso: str, *, limit: int = 500) -> list[str]:
413
+ """Ids of threads not continued since `cutoff_iso` (oldest first)."""
414
+ if not self.db.is_postgres:
415
+ idle = sorted(
416
+ (r for r in self._memory.values() if (r.updated_at or r.created_at) < cutoff_iso),
417
+ key=lambda r: r.updated_at or r.created_at,
418
+ )
419
+ return [r.thread_id for r in idle[:limit]]
420
+ rows = await self.db.fetchall(
421
+ "SELECT thread_id FROM threads WHERE updated_at < %s ORDER BY updated_at LIMIT %s",
422
+ (cutoff_iso, limit),
423
+ )
424
+ return [r["thread_id"] for r in rows]
425
+
426
+ async def delete(self, thread_id: str) -> None:
427
+ if not self.db.is_postgres:
428
+ self._memory.pop(thread_id, None)
429
+ else:
430
+ await self.db.execute("DELETE FROM threads WHERE thread_id = %s", (thread_id,))
431
+ await thread_deleted(thread_id)
432
+
433
+
434
+ def owner_metadata_filter(principal: Principal) -> dict[str, Any]:
435
+ """langgraph-server: the thread-metadata filter of a principal's own threads.
436
+
437
+ A containment filter: the subject's (direct) matches every thread stamped
438
+ with it, its agents' included; a delegated principal's names its actor
439
+ too, which a direct thread (no `actor` key) never matches.
440
+ """
441
+ if principal.actor is None:
442
+ return {"principal_id": principal.id}
443
+ return {"principal_id": principal.id, "actor": principal.actor.id}
444
+
445
+
446
+ async def thread_deleted(thread_id: str) -> None:
447
+ """Tell every `DELETE_LISTENERS` entry that `thread_id` is gone; never raises."""
448
+ for listener in list(DELETE_LISTENERS):
449
+ try:
450
+ await listener(thread_id)
451
+ except Exception:
452
+ logger.warning("a thread-delete listener failed", exc_info=True)
453
+
454
+
455
+ async def approval_outcome(outcome: ApprovalOutcome) -> None:
456
+ """Tell every `A2A_TASK_LISTENERS` entry what became of an approval; never raises."""
457
+ for listener in list(A2A_TASK_LISTENERS):
458
+ try:
459
+ await listener(outcome)
460
+ except Exception:
461
+ logger.warning("an approval-outcome listener failed", exc_info=True)
462
+
463
+
464
+ async def search_server_threads(
465
+ client: Any, principal: Principal, *, scope: str, limit: int, offset: int
466
+ ) -> list[dict[str, Any]]:
467
+ """langgraph-server: a `GET /threads` page from the server's thread search.
468
+
469
+ `scope="own"` filters on the owner the app wrote into the thread's
470
+ metadata at creation; `scope="all"` (checked by `check_scope`) lists
471
+ every thread. Rows carry the owner hashed, like `ThreadRecord.public()`.
472
+ """
473
+ filters: dict[str, Any] = {}
474
+ if scope != SCOPE_ALL:
475
+ filters["metadata"] = owner_metadata_filter(principal)
476
+ threads = await client.threads.search(
477
+ limit=limit, offset=offset, sort_by="updated_at", sort_order="desc", **filters
478
+ )
479
+ rows: list[dict[str, Any]] = []
480
+ for thread in threads:
481
+ if not isinstance(thread, Mapping):
482
+ continue
483
+ metadata = thread.get("metadata") if isinstance(thread.get("metadata"), Mapping) else {}
484
+ owner = metadata.get("principal_id")
485
+ rows.append(
486
+ {
487
+ "thread_id": str(thread.get("thread_id")),
488
+ "owner": owner_hash(owner if isinstance(owner, str) else None),
489
+ "created_at": _iso(thread.get("created_at")),
490
+ "updated_at": _iso(thread.get("updated_at")),
491
+ }
492
+ )
493
+ return rows