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,432 @@
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
+ """Checkpointer selection for the fastapi runtime (memory or postgres), and the Postgres pool.
16
+
17
+ `CHECKPOINTER=memory` gives an `InMemorySaver` (no database, state lost on
18
+ restart); `CHECKPOINTER=postgres` gives an `AsyncPostgresSaver` on
19
+ `POSTGRES_DSN` with its schema set up. Under `langgraph-server` the server owns
20
+ persistence through `DATABASE_URI`/`REDIS_URI` and `agent.py` always exports
21
+ an unbound graph.
22
+
23
+ Pools (`open_pool`) check each connection before handing it out and replace
24
+ dead ones, so a Postgres restart or failover costs no failed requests once the
25
+ database is back. Size them with `DB_POOL_MIN_SIZE` (default 1) and
26
+ `DB_POOL_MAX_SIZE` (default 10); the fastapi runtime shares one pool between
27
+ the checkpointer and the app tables. Schema setup (the saver's migrations and
28
+ the app's DDL) runs under a Postgres advisory lock (`schema_lock`), so
29
+ replicas starting together against a fresh database take turns.
30
+
31
+ Outages are bounded on every connection the app opens (`connection_kwargs`):
32
+ a connection attempt gives up after `connect_timeout` (5 s), and TCP
33
+ keepalives on both ends notice a vanished peer in about a minute instead of the
34
+ operating system's two hours. A parameter already set in the DSN wins. While
35
+ the database is known to be unreachable (`DbHealth`), a request waits at most
36
+ `DOWN_POOL_TIMEOUT_S` for a connection instead of `POOL_TIMEOUT_S`, and the
37
+ pool retries in short cycles (`RECONNECT_TIMEOUT_S`), so requests fail fast
38
+ during an outage and the app is ready again seconds after the database is.
39
+ """
40
+
41
+ from __future__ import annotations
42
+
43
+ import asyncio
44
+ import logging
45
+ import os
46
+ import re
47
+ import time
48
+ from collections.abc import AsyncIterator, Callable
49
+ from contextlib import asynccontextmanager, suppress
50
+ from functools import cache
51
+ from typing import Any
52
+
53
+ from {{cookiecutter.agent_directory}}.app_utils import metrics
54
+ from {{cookiecutter.agent_directory}}.app_utils.limits import SettingsError
55
+
56
+ logger = logging.getLogger(__name__)
57
+
58
+ MEMORY = "memory"
59
+ POSTGRES = "postgres"
60
+
61
+ # Advisory-lock key serialising schema setup across replicas (any fixed 64-bit value).
62
+ SCHEMA_LOCK_KEY = 0x6761_7363_6865_6D61 # "gaschema"
63
+ SCHEMA_LOCK_POLL_S = 0.25
64
+ SCHEMA_LOCK_TIMEOUT_S = 300.0
65
+
66
+ DB_POOL_MIN_SIZE = ("DB_POOL_MIN_SIZE", 1)
67
+ DB_POOL_MAX_SIZE = ("DB_POOL_MAX_SIZE", 10)
68
+ # Seconds a request waits for a free pooled connection before failing (503).
69
+ POOL_TIMEOUT_S = 5.0
70
+ # The same while the database is known to be unreachable: fail fast.
71
+ DOWN_POOL_TIMEOUT_S = 2.0
72
+ # A reconnect cycle of the pool gives up after this long; the next request
73
+ # starts a new one at once, instead of waiting out an ever longer back-off.
74
+ RECONNECT_TIMEOUT_S = 5.0
75
+ # Idle connections older than this are closed (keeps NAT/proxy reaping harmless).
76
+ POOL_MAX_IDLE_S = 300.0
77
+
78
+ # libpq parameters every connection gets unless the DSN sets them.
79
+ CONNECTION_DEFAULTS: dict[str, str] = {
80
+ "connect_timeout": "5",
81
+ "keepalives": "1",
82
+ "keepalives_idle": "30",
83
+ "keepalives_interval": "10",
84
+ "keepalives_count": "3",
85
+ # Unacknowledged data for this long (ms) means the server is gone (Linux).
86
+ "tcp_user_timeout": "60000",
87
+ }
88
+ # Server-side keepalives, so Postgres ends the sessions of a replica that
89
+ # vanished without closing its connections (they hold connection slots).
90
+ SERVER_KEEPALIVE_SQL = (
91
+ "SELECT set_config('tcp_keepalives_idle', '30', false), "
92
+ "set_config('tcp_keepalives_interval', '10', false), "
93
+ "set_config('tcp_keepalives_count', '3', false)"
94
+ )
95
+
96
+
97
+ def checkpointer_kind() -> str:
98
+ """`memory` (default) or `postgres`, from `CHECKPOINTER`."""
99
+ return (os.environ.get("CHECKPOINTER") or MEMORY).strip().lower()
100
+
101
+
102
+ def postgres_dsn() -> str:
103
+ dsn = os.environ.get("POSTGRES_DSN", "").strip()
104
+ if not dsn:
105
+ raise RuntimeError(
106
+ "CHECKPOINTER=postgres requires POSTGRES_DSN (from the Secret or the chart)."
107
+ )
108
+ return dsn
109
+
110
+
111
+ def _size(spec: tuple[str, int]) -> int:
112
+ name, default = spec
113
+ raw = (os.environ.get(name) or "").strip()
114
+ if not raw:
115
+ return default
116
+ try:
117
+ value = int(raw)
118
+ except ValueError:
119
+ raise SettingsError(f"{name}={raw!r} is not an integer.") from None
120
+ if value < 0:
121
+ raise SettingsError(f"{name}={value} must be >= 0.")
122
+ return value
123
+
124
+
125
+ def pool_sizes() -> tuple[int, int]:
126
+ """`(DB_POOL_MIN_SIZE, DB_POOL_MAX_SIZE)`, validated."""
127
+ low, high = _size(DB_POOL_MIN_SIZE), _size(DB_POOL_MAX_SIZE)
128
+ if high < 1 or low > high:
129
+ raise SettingsError(f"DB_POOL_MAX_SIZE={high} must be >= 1 and >= DB_POOL_MIN_SIZE={low}.")
130
+ return low, high
131
+
132
+
133
+ def connection_kwargs(dsn: str) -> dict[str, str]:
134
+ """The `CONNECTION_DEFAULTS` the DSN does not set itself.
135
+
136
+ Raises `SettingsError` for a DSN that does not parse: a typo is a
137
+ configuration error that stops startup, not an outage to wait out.
138
+ """
139
+ from psycopg import ProgrammingError
140
+ from psycopg.conninfo import conninfo_to_dict
141
+
142
+ try:
143
+ given = conninfo_to_dict(dsn)
144
+ except ProgrammingError as exc:
145
+ # libpq quotes the part it could not read, which can be the password:
146
+ # keep what went wrong, never the text itself.
147
+ reason = re.sub(r'"[^"]*"', '"..."', str(exc).splitlines()[0] if str(exc) else "")
148
+ raise SettingsError(
149
+ f"The database URL does not parse ({reason or 'invalid'}); "
150
+ "its text is not shown because it can hold the password."
151
+ ) from None
152
+ defaults = dict(CONNECTION_DEFAULTS)
153
+ if os.environ.get("PGCONNECT_TIMEOUT"):
154
+ defaults.pop("connect_timeout")
155
+ return {k: v for k, v in defaults.items() if k not in given}
156
+
157
+
158
+ def is_connection_error(exc: BaseException) -> bool:
159
+ """True for errors that mean the database cannot be reached (not a failed statement)."""
160
+ from psycopg import InterfaceError, OperationalError, errors
161
+ from psycopg_pool import PoolTimeout
162
+
163
+ if isinstance(exc, PoolTimeout | InterfaceError | OSError | TimeoutError):
164
+ return True
165
+ if isinstance(exc, errors.QueryCanceled | errors.TransactionRollback):
166
+ return False
167
+ return isinstance(exc, OperationalError)
168
+
169
+
170
+ class DbHealth:
171
+ """Whether the database was reachable at last contact, logged once per change.
172
+
173
+ Marked down when the pool times out while its connection attempts fail,
174
+ when a pool reconnect cycle gives up, or when a connection breaks under a
175
+ query; marked up by every connection handed out or opened.
176
+ """
177
+
178
+ def __init__(self) -> None:
179
+ self.up = True
180
+ self.changed_at = time.monotonic()
181
+ self.last_error: str | None = None
182
+
183
+ def mark_up(self) -> None:
184
+ if not self.up:
185
+ self.up = True
186
+ self.changed_at = time.monotonic()
187
+ metrics.observe_database(True)
188
+ logger.info("database reachable again")
189
+
190
+ def mark_down(self, reason: BaseException | str) -> None:
191
+ text = reason if isinstance(reason, str) else f"{type(reason).__name__}: {reason}"
192
+ self.last_error = text.strip().splitlines()[0][:300] if text.strip() else text
193
+ if self.up:
194
+ self.up = False
195
+ self.changed_at = time.monotonic()
196
+ metrics.observe_database(False)
197
+ logger.warning("database unreachable: %s", self.last_error)
198
+
199
+ def checkout_timeout(self) -> float:
200
+ return POOL_TIMEOUT_S if self.up else DOWN_POOL_TIMEOUT_S
201
+
202
+
203
+ async def connect(dsn: str, *, health: DbHealth | None = None, **kwargs: Any) -> Any:
204
+ """A direct autocommit connection with the `connection_kwargs` defaults.
205
+
206
+ The server-side keepalives are set best effort (a proxy may refuse them).
207
+ """
208
+ import psycopg
209
+
210
+ try:
211
+ conn = await psycopg.AsyncConnection.connect(
212
+ dsn, autocommit=True, **{**connection_kwargs(dsn), **kwargs}
213
+ )
214
+ except Exception as exc:
215
+ if health is not None and is_connection_error(exc):
216
+ health.mark_down(exc)
217
+ raise
218
+ await _server_keepalives(conn)
219
+ if health is not None:
220
+ health.mark_up()
221
+ return conn
222
+
223
+
224
+ async def _server_keepalives(conn: Any) -> None:
225
+ try:
226
+ await conn.execute(SERVER_KEEPALIVE_SQL)
227
+ except Exception as exc: # not fatal: the connection works without them
228
+ logger.debug("could not set the server-side TCP keepalives: %s", type(exc).__name__)
229
+
230
+
231
+ @cache
232
+ def _pool_class() -> type:
233
+ """`AsyncConnectionPool` whose checkout timeout follows the database's health."""
234
+ from psycopg_pool import AsyncConnectionPool, PoolTimeout
235
+
236
+ class HealthAwarePool(AsyncConnectionPool):
237
+ health: DbHealth
238
+
239
+ async def getconn(self, timeout: float | None = None) -> Any:
240
+ if timeout is None:
241
+ timeout = self.health.checkout_timeout()
242
+ errors_before = self.get_stats().get("connections_errors", 0)
243
+ try:
244
+ conn = await super().getconn(timeout)
245
+ except PoolTimeout as exc:
246
+ # Connection attempts failed while this request waited: the
247
+ # database is down (a busy pool alone does not count).
248
+ if self.get_stats().get("connections_errors", 0) > errors_before:
249
+ self.health.mark_down(exc)
250
+ raise
251
+ self.health.mark_up()
252
+ return conn
253
+
254
+ return HealthAwarePool
255
+
256
+
257
+ @asynccontextmanager
258
+ async def open_pool(dsn: str, health: DbHealth | None = None) -> AsyncIterator[Any]:
259
+ """An open `AsyncConnectionPool` that health-checks every connection it hands out.
260
+
261
+ Opening does not wait for the database: a pool opened during an outage
262
+ connects once the database is back. A connection killed by a Postgres
263
+ restart or failover fails the check and is replaced instead of failing the
264
+ request that drew it. One dead connection usually means every idle one died
265
+ with it, so the first failed check also sweeps the whole pool
266
+ (`pool.check()`) in the background: the waiting request then gets a fresh
267
+ connection on its next try instead of drawing, and backing off from, each
268
+ dead one in turn.
269
+ """
270
+ from psycopg.rows import dict_row
271
+
272
+ health = health if health is not None else DbHealth()
273
+ extra = connection_kwargs(dsn) # a DSN that does not parse stops here
274
+ holder: dict[str, Any] = {}
275
+
276
+ async def check(conn: Any) -> None:
277
+ from psycopg_pool import AsyncConnectionPool
278
+
279
+ try:
280
+ await AsyncConnectionPool.check_connection(conn)
281
+ except Exception:
282
+ sweep = holder.get("sweep")
283
+ if holder.get("pool") is not None and (sweep is None or sweep.done()):
284
+ holder["sweep"] = asyncio.ensure_future(holder["pool"].check())
285
+ raise
286
+
287
+ async def configure(conn: Any) -> None:
288
+ await _server_keepalives(conn)
289
+ health.mark_up()
290
+
291
+ async def reconnect_failed(pool: Any) -> None:
292
+ health.mark_down(f"no connection for {RECONNECT_TIMEOUT_S:g} s")
293
+
294
+ low, high = pool_sizes()
295
+ pool = _pool_class()(
296
+ conninfo=dsn,
297
+ min_size=low,
298
+ max_size=high,
299
+ open=False,
300
+ check=check,
301
+ configure=configure,
302
+ reconnect_failed=reconnect_failed,
303
+ reconnect_timeout=RECONNECT_TIMEOUT_S,
304
+ timeout=POOL_TIMEOUT_S,
305
+ max_idle=POOL_MAX_IDLE_S,
306
+ kwargs={"autocommit": True, "row_factory": dict_row, "prepare_threshold": 0, **extra},
307
+ )
308
+ pool.health = health
309
+ holder["pool"] = pool
310
+ await pool.open()
311
+ try:
312
+ yield pool
313
+ finally:
314
+ holder.pop("pool", None)
315
+ sweep = holder.pop("sweep", None)
316
+ if sweep is not None and not sweep.done():
317
+ sweep.cancel()
318
+ await pool.close()
319
+
320
+
321
+ @asynccontextmanager
322
+ async def schema_lock(
323
+ dsn: str, *, timeout: float = SCHEMA_LOCK_TIMEOUT_S, health: DbHealth | None = None
324
+ ) -> AsyncIterator[None]:
325
+ """Hold the schema advisory lock (on a connection of its own) for the block.
326
+
327
+ Waiting replicas poll with `pg_try_advisory_lock` instead of blocking in
328
+ `pg_advisory_lock`: a session blocked in a statement holds a snapshot, and
329
+ the saver's `CREATE INDEX CONCURRENTLY` (run by the lock holder) waits for
330
+ every such snapshot, so a blocking wait would deadlock the two.
331
+ """
332
+ async with await connect(dsn, health=health) as conn:
333
+ loop = asyncio.get_running_loop()
334
+ deadline = loop.time() + timeout
335
+ while True:
336
+ cur = await conn.execute("SELECT pg_try_advisory_lock(%s)", (SCHEMA_LOCK_KEY,))
337
+ row = await cur.fetchone()
338
+ if row and row[0]:
339
+ break
340
+ if loop.time() >= deadline:
341
+ raise TimeoutError(
342
+ f"another replica held the schema setup lock for over {timeout:g} s"
343
+ )
344
+ await asyncio.sleep(SCHEMA_LOCK_POLL_S)
345
+ try:
346
+ yield
347
+ finally:
348
+ # Closing the connection releases the lock too; this just makes it prompt.
349
+ with suppress(Exception):
350
+ await conn.execute("SELECT pg_advisory_unlock(%s)", (SCHEMA_LOCK_KEY,))
351
+
352
+
353
+ # A fence raises when this process may no longer write the thread's state
354
+ # (its run lease on the thread expired or was taken over).
355
+ Fence = Callable[[str], None]
356
+
357
+
358
+ @cache
359
+ def _fenced_saver_class() -> type:
360
+ from langgraph.checkpoint.postgres.aio import AsyncPostgresSaver
361
+
362
+ class FencedPostgresSaver(AsyncPostgresSaver):
363
+ """`AsyncPostgresSaver` that asks the fence before every write.
364
+
365
+ A run whose lease on its thread could not be renewed (another replica
366
+ may have taken the thread over) then fails instead of writing a
367
+ checkpoint beside, or over, the new owner's.
368
+ """
369
+
370
+ fence: Fence | None = None
371
+
372
+ def _check(self, config: Any) -> None:
373
+ if self.fence is not None:
374
+ self.fence(str(config["configurable"]["thread_id"]))
375
+
376
+ async def aput(self, config: Any, checkpoint: Any, metadata: Any, new_versions: Any) -> Any:
377
+ self._check(config)
378
+ return await super().aput(config, checkpoint, metadata, new_versions)
379
+
380
+ async def aput_writes(
381
+ self, config: Any, writes: Any, task_id: str, task_path: str = ""
382
+ ) -> None:
383
+ self._check(config)
384
+ await super().aput_writes(config, writes, task_id, task_path)
385
+
386
+ async def adelete_thread(self, thread_id: str) -> None:
387
+ if self.fence is not None:
388
+ self.fence(str(thread_id))
389
+ await super().adelete_thread(thread_id)
390
+
391
+ return FencedPostgresSaver
392
+
393
+
394
+ def postgres_saver(pool: Any, fence: Fence | None = None) -> Any:
395
+ """An `AsyncPostgresSaver` on `pool` whose writes pass `fence(thread_id)` first.
396
+
397
+ Its schema is not set up here: call `saver.setup()` under `schema_lock`.
398
+ """
399
+ saver = _fenced_saver_class()(pool)
400
+ saver.fence = fence
401
+ return saver
402
+
403
+
404
+ @asynccontextmanager
405
+ async def get_checkpointer(pool: Any = None, fence: Fence | None = None) -> AsyncIterator[Any]:
406
+ """Yield a ready checkpointer for the configured kind.
407
+
408
+ Postgres uses `pool` when given (the app's shared pool), else a pool of
409
+ its own, and runs `setup()` (idempotent) under the schema lock so the
410
+ checkpoint tables exist before the first run.
411
+ """
412
+ kind = checkpointer_kind()
413
+ if kind == MEMORY:
414
+ from langgraph.checkpoint.memory import InMemorySaver
415
+
416
+ yield InMemorySaver()
417
+ return
418
+ if kind == POSTGRES:
419
+ dsn = postgres_dsn()
420
+ if pool is not None:
421
+ saver = postgres_saver(pool, fence)
422
+ async with schema_lock(dsn):
423
+ await saver.setup()
424
+ yield saver
425
+ return
426
+ async with open_pool(dsn) as own_pool:
427
+ saver = postgres_saver(own_pool, fence)
428
+ async with schema_lock(dsn):
429
+ await saver.setup()
430
+ yield saver
431
+ return
432
+ raise RuntimeError(f"Unknown CHECKPOINTER {kind!r}; expected 'memory' or 'postgres'.")